理論
考慮這樣一道題,判斷圖中是否有環:
我們該如何去做呢?
利用並查集的方法,過程如下:
- 0->1有邊,所以0和1加入一個連通集合中:
- 1->2有邊,同樣加入到連通集合中。
- 接着檢查3->4有邊,3和4作爲一個集合:
- 1->3有邊,把上面兩個集合合併,形成0,1,2,3,4集合。(此時已經使用的邊有(0,1), (1,2), (3,4), (1,3))
- 此時並查集中任意兩點,都有可達路徑。當接着來一個新的邊(2,4)時,由於沒有(2,4)這條邊時,它們已經連通,再加上(2,4)就會形成一個環,此時判斷結束。
代碼
我們要如何表示節點之間的關係呢,同時如何進行並查集合並呢?
首先我們來看並查集的關係如何表示:
- 初始化一個大小爲6的數組,用來表示節點i的父節點,並賦初始值爲-1。
那麼(0,1)和(1,2)邊的表示如圖:
- 首先根據(0,1)一條邊,將0的父親賦值爲1(反過來也可以),代表1和0是連通的
- 接着根據邊(1,2),將2的父親賦值爲1的父親(就是1),如上圖。
合併過程
當進行到如上一步。遇到(1,3)這條邊時,要進行並查集合並:
- 顯然我們可以將1直接指向3,但這樣會使樹過長
- 因此,我們將1(父節點)指向3的父節點4,並更新parent數組
代碼
對於任意兩個節點x和y,我們需要分別獲取它們的父節點x_root, y_root,然後將parent[x_root]指向y_root。
代碼可以分解爲兩步:
- find_root(x)
- union(x, y)
#include <iostream>
#include <stdio.h>
#include <math.h>
using namespace std;
#define VERTICES 6
void init(int parent[])
{
fill(parent, parent+VERTICES, -1);
}
int find_root(int x, int parent[])
{
int x_root = x;
while(parent[x_root] != -1){
x_root = parent[x_root];
}
return x_root;
}
/*1 - union successfully, 0 - failed(兩個節點在同一結合)*/
int union_vertices(int x, int y, int parent[]){
int x_root = find_root(x, parent);
int y_root = find_root(y, parent);
if(x_root != y_root)
{
parent[x_root] = y_root;//把x的父節點複製成y
return 1;
}
return 0;
}
int main()
{
int parent[VERTICES];/*Vertices代表節點*/
int edges[5][2] = {{0,1}, {1,2}, {1,3}, {3,4}, {2,5}};
init(parent);
for(int i = 0; i < 5; i++)
{/*遍歷邊,合併*/
int x = edges[i][0];
int y = edges[i][1];
if(union_vertices(x, y, parent) == 0){
printf("Cycle detected!\n");
return 0;
}
}
printf("No cycles found.\n");
return 0;
}
其實上面代碼,有一個缺陷,就是一直是將x_root的parent指向y_root,對於極端數據,可能會使得查找父節點的鏈過長,時間複雜度最差退化爲o(n),我們可以加上一個層數Rank,根據Rank來判斷如何添加:
/*1 - union successfully, 0 - failed(兩個節點在同一結合)*/
int union_vertices(int x, int y, int parent[], int Rank[]){
int x_root = find_root(x, parent);
int y_root = find_root(y, parent);
if(x_root != y_root)
{
//parent[x_root] = y_root;//把x的父節點複製成y
if(Rank[x_root] > Rank[y_root])
{
parent[y_root] = x_root;
}
else if(Rank[x_root] < Rank[y_root])
{
parent[x_root] = y_root;
}
else
{
parent[x_root] = y_root;
Rank[y_root]++;//加上一層
}
return 1;
}
return 0;
}
具體就不展開了。
參考B站大佬講解視頻:https://www.bilibili.com/video/av38498175?from=search&seid=12476310733420597220