使用 find 和 Union 检测 Graph 中的循环
Posted
技术标签:
【中文标题】使用 find 和 Union 检测 Graph 中的循环【英文标题】:Detection of cycle in Graph using find and Union 【发布时间】:2017-03-28 18:12:29 【问题描述】: 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 2
和Node 3
2 1 4 5
上排(第 2 排)表示,Node 2
连接到 Node 1
、Node 4
和 Node 5
现在的问题是,接受它总是说的任何输入:图形包含循环。(虽然图形不包含循环) 现在在上面的输入图中不包含循环,但说图包含循环。 我哪里错了??谁能帮我 ??
【问题讨论】:
vector<int>adj[N];
是一个可变长度数组,不是 C++ 标准的一部分。避开他们
我在这里也看到了很多 C。这段代码可以受益于更现代的 C++
@AndyG 是的,它混合了 C 和 C++。我没有得到 C++ 代码来读取图形文件,所以我使用了我知道的 C 代码。我应该使用“new”而不是“malloc”对不起,但现在我的目标不是纯 C++ 代码。
@AndyG 你能检查一下我的 isCycle() 函数,这似乎是错误的。 union() 和 find() 以及接受图,打印图都经过测试。
你有malloc
,没有free
。您使用 1 和 0 代替 true
和 false
。您使用文件句柄而不是文件流。有可变长度数组。你有一个 c 风格的向量数组。您正在使用字符缓冲区而不是 std::string。考虑到所有这些事情,难怪你没有时间专注于逻辑错误。请稍等,我会为您更新。
【参考方案1】:
问题在于您的输入,但首先是一些背景知识:
使用 Union-Find 发现 Cycles 的背景
Union-Find 算法需要一个无向图。
它的工作原理如下:
创建一组边,这些边基本上是节点 ID 对 例如(1,2), (2,3)
对于每条边:
查找左侧的“父级”(查找部分)
找到右侧的“父”(查找部分)
如果父母是相同的,你有一个循环
否则,左侧的父级现在等于右侧的父级(联合部分)
“父”:是两个无向节点之间的任意指定。我们武断地说一个是另一个的父母,反之亦然。
起初,没有节点有父节点(-1
的标记值用于。
然后,当您遍历边时,您将分配这些父项
如果父节点不存在,则节点是它自己的父节点(0 是 0 的父节点,1 是 1 的父节点,等等)
在计算边两边的父母后(例如,1
和 2
边 (1, 2)
我们首先会看到他们的父母不一样(1 的父母是 1,2 的父母是 2) .
此时,我们联合父母以使他们相同
1 的父级变为 2,2 的父级仍为 2
将“Union”部分视为“联合同一父节点下的两个节点子集”,因此子集 1 和 2 变为 (1, 2),其父节点为 2。
但是,按照您的算法编写方式,它假定如果我们首先收到边缘(1, 2)
,那么我们将不会稍后收到边缘(2, 1)
。你的意见不同意。因此你有周期。
如果您删除这些多余的边缘并提供如下输入:
1 2 3
2 4 5
3
4
5
It will work(顺便说一句,我在这里对你的代码进行了 C++ 化)。但是,否则it will correctly report a cycle
你的挑战
因此要考虑到您的输入与您的算法所期望的不同。也就是说,如果边已经存在,您可能不应该创建边。
我会推荐:
- 由于图形是无向的,因此将具有较小 id 的边始终存储在左侧。您可以维护一个排序的边列表,并且不要插入重复的边(使用std::set
)来表示您的边列表)。
生成的代码如下所示(使用cin
作为输入):
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;
And now it no longer reports a cycle in your input!
但是,如果我们像这样提供输入(注意(4,1)
的附加边):
1 2 3
1 2 3
2 1 4 5
3 1
4 2 1
5 2
Then it correctly reports a cycle!
【讨论】:
以上是关于使用 find 和 Union 检测 Graph 中的循环的主要内容,如果未能解决你的问题,请参考以下文章
leetcode 261-Graph Valid Tree(medium)(BFS, DFS, Union find)
如何检测社交网络中两个人是否是朋友关系(union-find算法)
Union-Find(并查集): Quick union improvements