DFS:如何在c++中表示连接组件的节点

DFS: How to indicate the nodes of the connected components in C++

本文关键字:连接 表示 组件 节点 c++ DFS      更新时间:2023-10-16

我正在做一个ACM竞赛的问题,以确定具有无向图G和属于每个组件的顶点的连接组件的数量。我已经完成了一个DFS算法,计算无向图的连接组件的数量(问题的难点),但我想不出任何东西来表示属于每个组件的节点或有节点的记录。

Input: 第一行输入将是一个整数C,它表示测试用例的数量。每个测试用例的第一行包含两个整数N和E,其中N表示图中的节点数,E表示图中的边数。然后沿着E条线,每条线有2个整数I和J,其中I和J表示节点I和节点J之间存在一条边(0≤I, J)

输出: 在每个测试用例的第一行必须显示以下字符串"case G: p component (s) connected (s)",其中G表示测试用例的数量(从1开始),p表示图中连接的组件的数量。然后是X行,每一行包含属于连接组件的节点(按从小到大的顺序),用空格分隔。在每个测试用例之后应该打印一个空行。输出应该写入"output.out"。"

的例子:输入:

2
6 9
0 1
0 2
1 2
5 4
3 1
2 4
2 5
3 4
3 5
8 7
0 1
2 1
2 0
3 4
4 5
5 3
7 6
输出:

Case 1: 1 component (s) connected (s)
0 1 2 3 4 5
Case 2: 3 component (s) connected (s)
0 1 2
3 4 5
6 7

下面是我的代码:

#include <stdio.h>
#include <vector>
#include <stdlib.h>
#include <string.h>
using namespace std;
vector<int> adjacency[10000];
bool visited[10000];
/// @param Standard algorithm DFS
void dfs(int u){
    visited[ u ] = true;
    for( int v = 0 ; v < adjacency[u].size(); ++v ){
        if( !visited[ adjacency[u][v] ] ){
            dfs( adjacency[u][v] );
        }
    }
}
    int main(int argc, char *argv []){
    #ifndef ONLINE_JUDGE
    #pragma warning(disable: 4996)
        freopen("input.in", "r", stdin);
            freopen("output.out", "w", stdout);
    #endif
         ///enumerate vertices from 1 to vertex
        int vertex, edges , originNode ,destinationNode, i, j,cont =1;
        ///number of test cases
        int testCases;
        int totalComponents;
        scanf ("%d", &testCases);
        for (i=0; i<testCases; i++){
        memset( visited , 0 , sizeof( visited ) );
        scanf("%d %d" , &vertex , &edges );
        for (j=0; j<edges; j++){
            scanf("%d %d" , &originNode ,&destinationNode );
            adjacency[ originNode ].push_back( destinationNode );
            adjacency[ destinationNode ].push_back( originNode );
        }
            totalComponents =0;
            for( int i = 0 ; i < vertex ; ++i ){    // Loop through all possible vertex
                if( !visited[ i ] ){          //if we have not visited any one component from that node
                    dfs( i );                  //we travel from node i the entire graph is formed
                    totalComponents++;                   //increased amount of components
                }
            }
            printf("Case %d: %d component (s) connected (s)n" ,cont++, totalComponents);
            for (j=0;j<total;j++){
        /*here should indicate the vertices of each connected component*/
    }
        memset( adjacency , 0 , sizeof( adjacency ) );
        }
    return 0;
    }

我对如何携带属于每个连接组件或结构的节点的内存应该用来存储有疑问,我应该如何修改我的代码来做到这一点?,我想听到的建议,想法或任何实现的伪代码。感谢所有

算法大致如下:

  • 获取一个图形节点
  • 查找与它直接或间接相连的所有节点(双向)。
  • 将它们全部标记为"遍历",并将它们放入新组件中。
  • 查找未遍历的下一个节点并重复此过程。

结果是一组"组件"数据结构(在我的实现中是std::vector s),每个包含一组完全相互连接的节点。

注意事项:

  • 我们需要将图存储在一个结构中,该结构可以有效地"向下"(从父节点到子节点)和"向上"(从子节点到父节点)遍历,并递归地找到所有连接的节点(在两个方向上),在我们进行时将节点标记为"遍历"。由于节点是由连续的整数范围来识别的,我们可以通过使用随机访问属性std::vector来有效地构建这个结构。
  • 边和节点的概念是分开的,所以单个"遍历"标志可以存在于节点级别,无论有多少其他节点连接到它(即无论有多少父边和子边)。这使我们能够有效地减少已经到达节点的递归。

下面是工作代码。注意,这里使用了一些c++ 11的特性,但如果使用较旧的编译器,它们应该很容易替换。错误处理留给读者作为练习。

#include <iostream>
#include <vector>
#include <algorithm>
// A set of inter-connected nodes.
typedef std::vector<unsigned> Component;
// Graph node.
struct Node {
    Node() : Traversed(false) {
    }
    std::vector<unsigned> Children;
    std::vector<unsigned> Parents;
    bool Traversed;
};
// Recursive portion of the FindGraphComponents implementation.
//   graph: The graph constructed in FindGraphComponents().
//   node_id: The index of the current element of graph.
//   component: Will receive nodes that comprise the current component.
static void FindConnectedNodes(std::vector<Node>& graph, unsigned node_id, Component& component) {
    Node& node = graph[node_id];
    if (!node.Traversed) {
        node.Traversed = true;
        component.push_back(node_id);
        for (auto i = node.Children.begin(); i != node.Children.end(); ++i)
            FindConnectedNodes(graph, *i, component);
        for (auto i = node.Parents.begin(); i != node.Parents.end(); ++i)
            FindConnectedNodes(graph, *i, component);
    }
}
// Finds self-connected sub-graphs (i.e. "components") on already-prepared graph.
std::vector<Component> FindGraphComponents(std::vector<Node>& graph) {
    std::vector<Component> components;
    for (unsigned node_id = 0; node_id < graph.size(); ++node_id) {
        if (!graph[node_id].Traversed) {
            components.push_back(Component());
            FindConnectedNodes(graph, node_id, components.back());
        }
    }
    return components;
}
// Finds self-connected sub-graphs (i.e. "components") on graph that should be read from the input stream.
//   in: The input test case.
std::vector<Component> FindGraphComponents(std::istream& in) {
    unsigned node_count, edge_count;
    std::cin >> node_count >> edge_count;
    // First build the structure that can be traversed recursively in an efficient way.
    std::vector<Node> graph(node_count); // Index in this vector corresponds to node ID.
    for (unsigned i = 0; i < edge_count; ++i) {
        unsigned from, to;
        in >> from >> to;
        graph[from].Children.push_back(to);
        graph[to].Parents.push_back(from);
    }
    return FindGraphComponents(graph);
}
void main() {
    size_t test_case_count;
    std::cin >> test_case_count;
    for (size_t test_case_i = 1; test_case_i <= test_case_count; ++test_case_i) {
        auto components = FindGraphComponents(std::cin);
        // Sort components by descending size and print them.
        std::sort(
            components.begin(),
            components.end(),
            [] (const Component& a, const Component& b) { return a.size() > b.size(); }
        );
        std::cout << "Case " << test_case_i <<  ": " << components.size() << " component (s) connected (s)" << std::endl;
        for (auto components_i = components.begin(); components_i != components.end(); ++components_i) {
            for (auto edge_i = components_i->begin(); edge_i != components_i->end(); ++edge_i)
                std::cout << *edge_i << ' ';
            std::cout << std::endl;
        }
        std::cout << std::endl;
    }
}

调用这个程序为…

GraphComponents.exe < input.in > output.out

…其中input.in包含您的问题中描述的格式的数据,它将在output.out中产生所需的结果。

解决方案要容易得多,您必须声明两个大小为顶点数的数组

int vertexNodes  [vertex] / / / array to store the nodes
int vertexComponents [vertex] / / / array to store the number of components

然后,当你调用DFS时,每个顶点都存储在顶点数组中,并存储在属于

的组件中。
for( int i = 0 ; i < vertex ; ++i ) //iterate on all vertices
        {
                vertexNodes [i]=i;  //fill the array with the vertices of the graph
            if( !visited[ i ] )
            { ///If any node is visited DFS call
                    dfs(i);
                totalComponents++; ///increment number of components
            }
            vertexComponents [i]=totalComponents; ///is stored at each node component belongs to
        }

最后,它打印所有的组件,并创建一个标记,其中包含第一个组件的值,该值与每个顶点的组件进行比较

printf("Case %d: %d component (s) connected (s)n" ,cont++, totalComponents);
int flag = vertexComponents[0]; ///Create a flag with the value of the first component
            for (k=0; k <totalComponents; ++k) ///do a cycle length of the number of components
            {
                if (flag == vertexComponents [k] ) ///check if the vertex belongs to the first component
                {
                    printf ("%d ", vertexComponents[k]); ///print on the same line as belonging to the same component
                }else {
                    printf ("n"); ///else  we make newline and update the flag to the next component
                    flag = vertexComponents[k];
                    printf ("%d ", vertexComponents[k]);///and print the vertices of the new connected component
                }
            }

您可以这样存储组件:

typedef vector<int> Component;
vector<Component> components;

并修改代码:

void dfs(int u){
    components.back().push_back(u);
    visited[ u ] = true;
    for( int v = 0 ; v < adjacency[u].size(); ++v ){
        if( !visited[ adjacency[u][v] ] ){
            dfs( adjacency[u][v] );
        }
    }
}
for( int i = 0 ; i < vertex ; ++i ){    // Loop through all possible vertex
    if( !visited[ i ] ){          //if we have not visited any one component from that node
        components.push_back(Component());
        dfs( i );                  //we travel from node i the entire graph is formed
    }
}

totalComponents = components.size():

printf("Case %d: %d component (s) connected (s)n" ,cont++, components.size());
        for (j=0;j<components.size();j++){
           Component& component = components[j];
           std::sort(component.begin(), component.end());
           for(int k=0; k<component.size(); k++) {
             printf("%d ", component[k]);
           }
           printf("n");
        }
        components.clear();

注意,代码没有经过测试。包含<algorithm>来获得排序函数

测试2个节点是否连通的通用算法:

  1. 将整个图分割成边。将每条边添加到集合中。
  2. 在下一次迭代中,在步骤2中绘制的边缘的两个外部节点之间绘制边缘。这意味着将新节点(及其相应的集合)添加到原始边所在的集合中。(基本设置合并)
  3. 重复2,直到您正在寻找的2个节点在同一集合中。你还需要在第1步之后做一个检查(以防两个节点相邻)。

一开始,每个节点都在它的集合中,

o   o1   o   o   o   o   o   o2
  /      /      /      /
 o o     o o     o o     o o
        /              /
   o o o o         o o o o 
                     /
       o o1 o o o o o o2

随着算法的进展和集合的合并,它相对地将输入减半。

在上面的例子中,我想看看o1和o2之间是否有一条路径。我在合并所有边后才找到这条路径。有些图形可能有单独的组件(断开连接),这意味着您将无法在最后拥有一组组件。在这种情况下,您可以使用此算法来测试连通性,甚至计算图中组件的数量。组件的数量是当算法完成时你能够得到的集合的数量。

一个可能的图(对于上面的树):

o-o1-o-o-o2
  |    |
  o    o
       |
       o