並查集

理論

考慮這樣一道題,判斷圖中是否有環:

在這裏插入圖片描述

我們該如何去做呢?

利用並查集的方法,過程如下:

  1. 0->1有邊,所以0和1加入一個連通集合中:
    在這裏插入圖片描述
  2. 1->2有邊,同樣加入到連通集合中。
  3. 接着檢查3->4有邊,3和4作爲一個集合:

在這裏插入圖片描述

  1. 1->3有邊,把上面兩個集合合併,形成0,1,2,3,4集合。(此時已經使用的邊有(0,1), (1,2), (3,4), (1,3))
  2. 此時並查集中任意兩點,都有可達路徑。當接着來一個新的邊(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。
在這裏插入圖片描述

代碼可以分解爲兩步:

  1. find_root(x)
  2. 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章