使用find和Union检测Graph中的循环

时间:2017-03-28 18:12:29

标签: c++ algorithm graph union-find

    int main()
    {

    char line[100];
    int N = 5;
    vector<int>adj[N];
    FILE *in = fopen("test.txt", "r");

    for (int i = 1; i <= N; i++) // Accepting the graph from file
    {
        fgets(line, 100, in);

        char *pch = strtok(line, "\t \n");
        int u = atoi(pch);

        pch = strtok(NULL, "\t \n");
        while (pch != NULL)
        {
            int v = atoi(pch);
            adj[u-1].push_back(v);
            pch = strtok(NULL, "\t \n");
        }

    }
        for( int i = 0; i < 5; i++ )  // printing the graph
        {
           for( int p = 0 ; p < adj[i].size(); p++ )
           {
                cout<< i+1 << " , "<< adj[i][p]<<endl;
           }
        }

        if (isCycle(adj))
             cout << endl << "graph contains cycle" ;
        else
             cout << endl << "graph  does not contain cycle" ;

        return 0;
    }

    int isCycle( vector<int> adj[] )
    {
        // Allocate memory for creating V subsets
        int *parent = (int*) malloc( 5 * sizeof(int) );

       // Initialize all subsets as single element sets
        memset(parent, -1, sizeof(int) * 5);
        for(int i = 0; i < 5; i++)
        {
           for( int p = 0 ; p < adj[i].size(); p++ )
           {    
                int x = find(parent,i);
                int y = find(parent, adj[i][p]-1);  // I think problem is here

                if (x == y)
                return 1;

            Union(parent, x, y);
            }
        }
        return 0;
    }   

    // A utility function to find the subset of an element i
    int find(int parent[], int i)
    {    
        if (parent[i] == -1)
            return i;
        return find(parent, parent[i]);
    }

    // A utility function to do union of two subsets
    void Union(int parent[], int x, int y)
    {
        int xset = find(parent, x);
        int yset = find(parent, y);
        parent[xset] = yset;
    }

test.txt文件包含以下输入:

1 2 3
2 1 4 5
3 1
4 2 
5 2

第一列包含顶点(1 - 5)

1 2 3 

上一行(第一行)表示Node 1已连接到Node 2Node 3

2 1 4 5 

上一行(第二行)表示Node 2已连接到Node 1Node 4Node 5

现在问题在于,它总是说任何输入:图形包含循环。(虽然图形不包含循环) 现在在上面的输入图中不包含循环而是说图包含循环。 我哪里错了?谁能帮我 ??

1 个答案:

答案 0 :(得分:3)

问题在于您的输入,但首先是背景:

使用Union-Find发现Cycles的背景

Union-Find算法需要一个无向图。

它基本上如下工作:

  • 创建一组边,这些边基本上是节点ID对
    • e.g。 (1,2), (2,3)
  • 每个边缘:
    • 找到&#34;父母&#34;左侧(查找部分)
    • 找到&#34;父母&#34;右侧(查找部分)
    • 如果父母是相同的,那么你就有一个周期
    • 否则,左侧的父级现在等于右侧的父级(联盟部分)

&#34; Parent&#34;:是两个无向节点之间的任意指定。我们任意地说,一个是另一个的父母,反之亦然。

  • 首先,没有节点有父节点(-1的标记值用于。
  • 然后,当您遍历边缘时,您将分配这些父项
    • 如果父母不存在,则节点是其自己的父节点(0是0的父节点,1是1的父节点等)
    • 在计算边缘两侧的父母(例如12边缘(1, 2)之后,我们首先会看到他们的父母不一样(1&#39;父母是1和2的父母是2)。
    • 此时,我们联合父母使他们一样
      • 1的父级变为2,2的父级保持2
      • 想到&#34; Union&#34; part为&#34;将两个节点子集合并在一个共同父节点#34;下,因此子集1和2变为(1,2),其父节点为2。

但是,编写算法的方式是假设如果我们首先收到边(1, 2),那么我们以后会收到边{{1} }}。你的意见不一致。因此你有周期。

如果删除这些冗余边缘并提供如下输入:

(2, 1)

It will work(顺便说一句,我是C ++ - 从你的代码中解脱出来)。但是,否则it will correctly report a cycle

你的挑战

因此,要考虑到您的输入与算法所期望的不同。也就是说,如果已存在边缘,则可能不应创建边。

我会推荐什么:   - 由于图形是无向的,因此存储具有较小id的边缘始终位于左侧。您可以维护一个有序的边列表,也不要插入重复的边(使用std::set)来表示边列表。

结果代码看起来像这样(使用1 2 3 2 4 5 3 4 5 作为输入):

cin

And now it no longer reports a cycle in your input!

但是,如果我们像这样提供输入(注意using edge_t = std::pair<int, int>; using edge_list_t = std::set<edge_t>; using parent_child_map_t = std::map<int, int>; // find the parent for an id int find(const parent_child_map_t& idToParent, int id) { auto iter = idToParent.find(id); if (iter == idToParent.end()) return id; return find(idToParent, iter->second); } // lhsId and rhsId are two sides to an edge // this Union function will determine who is the "parent" // arbitrarily choosing the rhsId's parent as lhsId's parent void ComputeUnion(parent_child_map_t* idToParent, int lhsId, int rhsId) { if (!idToParent) return; int xsubset = find(*idToParent, lhsId); int ysubset = find(*idToParent, rhsId); (*idToParent)[xsubset] = ysubset; } bool HasCycle(const edge_list_t& edges ) { // determine parents parent_child_map_t idToParent; for (auto&& nextEdge : edges) { int x = find(idToParent, nextEdge.first); int y = find(idToParent, nextEdge.second); if (x == y) return true; ComputeUnion(&idToParent, x, y); } return false; } int main() { edge_list_t edges; std::string nextline; while(std::getline(std::cin, nextline)) { std::istringstream nextLineStream(nextline); int id; nextLineStream >> id; int nextNeighbor; while(nextLineStream >> nextNeighbor) { int lhs = std::min(id, nextNeighbor); int rhs = std::max(id, nextNeighbor); edges.insert(std::make_pair(lhs, rhs)); } } if (HasCycle(edges)) std::cout << "Graph contains cycle\n"; else std::cout << "Graph does not contain cycle\n"; return 0; } 的附加边缘):

(4,1)

Then it correctly reports a cycle!