图的存储和遍历

图的存储

在leetcode中图的存储形式如下,这种形式的图只能适合用来存储是连通图的情况,且根据leetcode提供的_{0,1,2#1,2#2,2}_格式的字符串通过程序来自动构造图比较麻烦,预知字符串的含义请移步到leetcode的解释

1
2
3
4
5
6
7
/**
 * Definition for undirected graph.
 * struct UndirectedGraphNode {
 *     int label;
 *     vector<UndirectedGraphNode *> neighbors;
 *     UndirectedGraphNode(int x) : label(x) {};
 * };

本文为了能够用字符串表示所有图,并且便于程序的构造,使用了邻接表的形式来对图进行存储,即可以用来存储有向图,有可以存储无向图。图一个节点的结构如下:

1
2
3
4
5
6
7
8
9
/**
 * 图节点的邻接表表示形式
 */
struct GraphNode {
    std::string label;
    std::vector<GraphNode *> neighbors;
    bool visited;			// 深度优先搜索和广度优先搜索的遍历都需要visited数组,为了简化程序,直接在节点的存储结构中设置visited变量
    GraphNode(std::string x) : label(x), visited(false) {};
};

图的创建方面为了简化算法实现,对程序的效率没做太多关注,算法复杂度稍高。本算法的难点在于对字符串的拆解,并根据字符串找到对应的节点指针。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/**
 * 图节点的邻接表表示形式
 */
struct GraphNode {
    std::string label;
    std::vector<GraphNode *> neighbors;
    bool visited;
    GraphNode(std::string x) : label(x), visited(false) {};
};

/**
 * 通过字符串的值,找到该字符串对应的图节点
 */ 
GraphNode *get_one_node(const std::vector<GraphNode *> &node_vector, std::string str)
{
    for (std::vector<GraphNode *>::const_iterator iter = node_vector.begin(); iter != node_vector.end(); iter++)
    {
        if ((*iter)->label == str)
        {
            return *iter;
        }
    }
    return NULL;
}

/**
 * 时间复杂度高,对图的构建一般效率要求较低
 * 对于查找某个节点的邻接点的指针操作可以使用map来提高查询效率
 * 或者可以通过不需要初始化所有节点的方式来构造图,而是采用需要哪个节点构造哪个节点的方式
 */
std::vector<GraphNode *> create_graph(std::string str)
{
    std::vector<GraphNode *> node_vector;
   
    // init all nodes
    for (size_t pos = 0; pos < str.length() - 1;)
    {
        size_t end = str.find(',', pos);
        if (end != std::string::npos)
        {
            GraphNode *node = new GraphNode(str.substr(pos, end - pos));
            node_vector.push_back(node);
        }
        else
        {
            break;
        }

        pos = str.find('#', pos);
        if (pos == std::string::npos)
        {
            break;
        }
        else
        {
            pos++;
        }
    }

    // add neighbors in every node
    for (size_t pos = 0; pos < str.length() - 1; )
    {
        GraphNode *current_node = NULL;
        size_t current_end = str.find(',', pos);
        if (current_end != std::string::npos)
        {
            current_node = get_one_node(node_vector, str.substr(pos, current_end - pos));
            pos = current_end + 1;
        }
        else
        {
            break;
        }

        size_t node_end = str.find('#', pos);   // 当前节点的字符串的结束位置
        if (node_end == std::string::npos)
        {
            node_end = str.length();
        }
        else
        {
            node_end--;
        }

        for ( ; ; )
        {
            current_end = str.find(',', pos);
            if (current_end > node_end || current_end == std::string::npos)
            {
                // 一个节点的最后一个邻接点
                current_end = node_end;
            }
            else 
            {
                current_end--;
            }

            GraphNode *node = get_one_node(node_vector, str.substr(pos, current_end - pos + 1)); 
            if (node != NULL)
            {
                current_node->neighbors.push_back(node);
                std::cout << current_node->label << " add " << node->label << std::endl;
            }
            if (current_end == node_end)
            {
                // 一个节点的最后一个邻接点
                break;
            }
            else
            {
                pos = current_end + 2;  // 该节点之后还有其他邻接点
            }
        }
        pos = node_end + 2;
    }
    return node_vector;
}

深度优先搜索

深度优先搜索遵循贪心算法的原理,如果孩子节点不为空,则一直遍历下去。

递归算法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void DFS_traverse_recursion(GraphNode *node)
{
    if (!node->visited)
    {
        std::cout << node->label << '\t';
        node->visited = true;
    }
    for (std::vector<GraphNode *>::iterator iter = node->neighbors.begin(); iter != node->neighbors.end(); iter++)
    {
        if (!(*iter)->visited)
        {
            DFS_traverse_recursion(*iter);
        }
    }
}

/**
 * 图的深度优先搜索的递归形式
 */
void DFS_traverse_recursion(std::vector<GraphNode *> &graph)
{
    for (std::vector<GraphNode *>::iterator iter = graph.begin(); iter != graph.end(); iter++)
    {
        (*iter)->visited = false;
    }
    for (std::vector<GraphNode *>::iterator iter = graph.begin(); iter != graph.end(); iter++)
    {
        if (!(*iter)->visited)
            DFS_traverse_recursion(*iter);
    }
}

非递归算法

使用栈来实现非递归。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
 * 图的深度优先搜索的非递归形式
 */
void DFS_traverse_not_recursion(std::vector<GraphNode *> &graph)
{
    for (std::vector<GraphNode *>::iterator iter = graph.begin(); iter != graph.end(); iter++)
    {
        (*iter)->visited = false;
    }

    for (std::vector<GraphNode *>::iterator iter = graph.begin(); iter != graph.end(); iter++)
    {
        std::stack<GraphNode *> node_stack;
        if ((*iter)->visited)
        {
            continue;
        }
        node_stack.push(*iter);
        while (!node_stack.empty())
        {
            GraphNode *node = node_stack.top();
            node_stack.pop();
            if (node->visited)
                continue;
            std::cout << node->label << '\t';
            node->visited = true;
            /* 使用反向迭代器遍历后将节点加入到栈中 */
            for (std::vector<GraphNode *>::reverse_iterator iter2 = node->neighbors.rbegin(); iter2 != node->neighbors.rend(); iter2++)
            {
                if (!(*iter2)->visited)
                {
                    node_stack.push(*iter2);
                }
            }
        }
    }
}

广度优先搜索

该算法不存在递归算法,仅有非递归版本。需要利用队列来保存需要遍历的节点,占用的存储空间稍多。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
 * 图的广度优先搜索的非递归形式
 */
void BFS_traverse_not_recursion(std::vector<GraphNode *> &graph)
{
    for (std::vector<GraphNode *>::iterator iter = graph.begin(); iter != graph.end(); iter++)
    {
        (*iter)->visited = false;
    }

    for (std::vector<GraphNode *>::iterator iter = graph.begin(); iter != graph.end(); iter++)
    {
        std::queue<GraphNode *> node_queue;
        if ((*iter)->visited)
        {
            continue;
        }
        node_queue.push(*iter);
        while (!node_queue.empty())
        {
            GraphNode *node = node_queue.front();
            node_queue.pop();
            if (node->visited)
                continue;
            std::cout << node->label << '\t';
            node->visited = true;
            for (std::vector<GraphNode *>::iterator iter2 = node->neighbors.begin(); iter2 != node->neighbors.end(); iter2++)
            {
                if (!(*iter2)->visited)
                {
                    node_queue.push(*iter2);
                }
            }
        }
    }
}

相关下载

本文相关源码