並查集的使用及其實現
並查集
概述
詳細教程參考之前轉載的並查集詳解
性質
並查集演算法(union_find sets)不支援分割一個集合,求連通子圖、求最小生成樹
用法
並查集是由一個陣列pre[]
,和兩個函式構成的,一個函式為find()
函式,用於尋找前導點的,第二個函式是join()
用於合併路線的
int find(int x)
{
int r=x;
while(pre[r]!=r)
r=pre[r];//找到他的前導結點
int i=x,j;
while(i!=r)//路徑壓縮演算法
{
j=pre[i];//記錄x的前導結點
pre[i]=r;//將i的前導結點設定為r根節點
i=j;
}
return r;
}
路徑壓縮為了加快查詢的速度,將x點與其根節點直接相連,構造成類似於只有葉子結點而沒有分支結點的樹
join()函式
void join(int x,int y)
{
int a=find(x);//x的根節點為a
int b=find(y);//y的根節點為b
if(a!=b)//如果a,b不是相同的根節點,則說明ab不是連通的
{
pre[a]=b;//我們將ab相連 將a的前導結點設定為b
}
}
初始化
我們將每一個結點的前導結點設定為自己,如果在join函式時未能形成連通,將獨立成點
for(int i=0;i<n;i++)//n表示輸入的結點的個數
{
pre[i]=i;//將每一個結點的前導點設定為自己
}
用法
試題來自第八屆藍橋杯試題
第三次編輯這道題目
標題:風險度量
X星系的的防衛體系包含 n 個空間站。這 n 個空間站間有 m 條通訊鏈路,構成通訊網。
兩個空間站間可能直接通訊,也可能通過其它空間站中轉。
對於兩個站點x和y (x != y), 如果能找到一個站點z,使得:
當z被破壞後,x和y無法通訊,則稱z為關於x,y的關鍵站點。
顯然,對於給定的兩個站點,關於它們的關鍵點的個數越多,通訊風險越大。
你的任務是:已知網路結構,求兩站點之間的通訊風險度,即:它們之間的關鍵點的個數。
輸入資料第一行包含2個整數n(2 <= n <= 1000), m(0 <= m <= 2000),分別代表站點數,鏈路數。
空間站的編號從1到n。通訊鏈路用其兩端的站點編號表示。
接下來m行,每行兩個整數 u,v (1 <= u, v <= n; u != v)代表一條鏈路。
最後1行,兩個數u,v,代表被詢問通訊風險度的兩個站點。
輸出:一個整數,如果詢問的兩點不連通則輸出-1.
例如:
使用者輸入:
7 6
1 3
2 3
3 4
3 5
4 5
5 6
1 6
應該輸出:
2
我的錯誤
在進行分析的時候,我考慮了去邊去點,去點的話,首先逐個去掉除了詢問的點以外的點,同時去點的同時我們同樣需要去掉與該點之間關聯的邊,查詢與該點關聯的邊需要從整個資料中尋找資料量太大,一定會超時,還是需要考慮去邊的辦法,
//並查集
#include<iostream>
using namespace std;
int pre[1005];//每個點的前導點
int route[2005][2];
//可以配對的路線
int sum = 0;
//符合條件的 即關鍵點的數量
//查詢
int find(int x)
{
int r = x;
while (pre[r] != r)
r = pre[r];
int i = x, j;
while (i != r)//路徑壓縮演算法
{
j = pre[i];//在改變他的前導點時,儲存他的值
pre[i] = r;
i = j;//改變他的前導點為根節點
}
return r;
}
void join(int x, int y)
//組合
{
int fx = find(x), fy = find(y);//分別記錄x,y的根節點
if (fx != fy)//如果他們的根節點相同,則說明他們不是連通圖
pre[fx] = fy;//將x的根結點 同 相連線
}
int main()
{
int n, m;
cin >> n>>m;//n表示站點的個數,m表示鏈路的個數
for (int i = 0; i < m; i++)
{
cin >> route[i][0] >> route[i][1];
join(route[i][0], route[i][1]);//將資料相互連線
}
int q1,q2;//待詢問的兩個點
cin >> q1 >> q2;
for (int ii = 0; ii < n; ii++)pre[ii] = ii;
for (int j = 0; j < m; j++)
{
join(route[j][0], route[j][1]);
}
int a = find(q1);
int b = find(q2);
//如果邊全部存在時不可達,則輸出 -1;
if (a != b)
{
cout << "-1" << endl;
}
else
{
for (int i = 1; i <= n; i++)
//列舉每一個點
{
if (i == q1 || i == q2)continue;
//如果是被詢問的點,跳過,無需遍歷 此處是最關鍵的部分
for (int j = 1; j <= n; j++)pre[j] = j;
//將每一個初始化
for (int j = 0; j < m; j++)
{
if (route[j][0] == i || route[j][1]==i)continue;
//去除當前點互相關聯的邊 解決問題的關鍵
int a = find(route[j][0]);
int b = find(route[j][1]);
if (a > b) { a ^= b; b ^= a; a ^= b; };//交換
if (a != b)pre[b] = a;
//以較小的點作為父節點
}
int a = find(q1);
int b = find(q2);
if (a != b)sum++;
}
cout<<sum<<endl;
}
return 0;
}
看到網上好多人在寫並查集時,使用while(~sacnf("%d",&a))
scanf()函式的返回值是正確獲得變數的個數
~scanf()函式就是沒有得到正確的輸入,總體上講如果有正確結果輸入,就退出迴圈,如果沒有正確輸入,就執行迴圈
看似沒有什麼區別,其實這種while()迴圈更加安全,保證不會因為非法的數字的輸入執行程式的使用
測試資料
按照我之前對於資料的統計,現在給大家提供幾組資料
- 4 0
1 2
測試結果 -1
- 4 3
2 3
3 4
2 4
1 4
測試結果 -1 - 3 2
1 2
2 3
1 3
測試結果 1 - 使用題目的資料 以及評論區的那個資料
- 4 3
測試資料的分析,對於第一組測試資料,有且僅有四個點 ,測試程式是否會進行連通性判斷;第二組資料 1 是獨立點 234是三個連通分量,程式需要判斷是否1與其他的點能構成連通圖;第三組資料,我們測試一條直線,所有的關鍵點全部在該直線上,判斷程式記錄的到底是關鍵點的個數還是邊的個數;第四組我們測試任意情況下對於資料的處理我們可以開啟畫板,對我們的資料進行驗證即可
反向並查集
題目
來自藍橋杯系統歷屆試題庫中的試題
問題描述
C國由n個小島組成,為了方便小島之間聯絡,C國在小島間建立了m座大橋,每座大橋連線兩座小島。兩個小島間可能存在多座橋連線。然而,由於海水沖刷,有一些大橋面臨著不能使用的危險。
如果兩個小島間的所有大橋都不能使用,則這兩座小島就不能直接到達了。然而,只要這兩座小島的居民能通過其他的橋或者其他的小島互相到達,他們就會安然無事。但是,如果前一天兩個小島之間還有方法可以到達,後一天卻不能到達了,居民們就會一起抗議。
現在C國的國王已經知道了每座橋能使用的天數,超過這個天數就不能使用了。現在他想知道居民們會有多少天進行抗議。
輸入格式
輸入的第一行包含兩個整數n, m,分別表示小島的個數和橋的數量。
接下來m行,每行三個整數a, b, t,分別表示該座橋連線a號和b號兩個小島,能使用t天。小島的編號從1開始遞增。
輸出格式
輸出一個整數,表示居民們會抗議的天數。
樣例輸入
4 4
1 2 2
1 3 2
2 3 1
3 4 3
樣例輸出
2
樣例說明
第一天後2和3之間的橋不能使用,不影響。
第二天後1和2之間,以及1和3之間的橋不能使用,居民們會抗議。
第三天後3和4之間的橋不能使用,居民們會抗議。
資料規模和約定
對於30%的資料,1<=n<=20,1<=m<=100;
對於50%的資料,1<=n<=500,1<=m<=10000;
對於100%的0<=n<=10000,1<=m<=10000。
,1<=a, b<=n, 1<=t<=100000。
#include<iostream>
#include<algorithm>
using namespace std;
struct node
{
int x, y, d;//d表示剩餘的時間 x,y分別表示橋的兩端的端點
}bridge[10005];//建立橋的數量
int pre[10005];//前導結點的個數
bool cmp(node a, node b)//時間比較 如果第一個引數大於第二個引數,則返回true
{
return a.d>b.d;
}
int find(int x)//查詢根節點
{
int r=x;
while (pre[r] != r)
r = pre[r];
//嘗試嘗試路徑壓縮演算法
return r;
}
bool join(int x, int y)//合併鏈路
{
int fx = find(x);
int fy = find(y);
if (fx != fy)
{
pre[fx] = fy;
return 1;//沒有橋,我們將直接構造形成橋
}
return 0;//有橋無需構造
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++)
{
cin >> bridge[i].x >> bridge[i].y >> bridge[i].d;//輸入橋頭兩側 使用天數
}
//
for (int i = 0; i < n; i++)
{
pre[i] = i;//初始化每個小島,使其獨立
}
//按照使用時間排序進行整合
sort(bridge, bridge + m, cmp);//天數從大到小排列
int fight = 0;//表示反抗的日子
int pre = -1;//
for (int i = 0; i < m; i++)//此時的時間已經是從大到小的排序
{
int way = join(bridge[i].x, bridge[i].y);//從時間從大到小重新構造橋
if (way == 1 && bridge[i].d != pre)//如果系統構造的橋並且天數不等於-1
{
fight++;
pre = bridge[i].d;
}
}
cout << fight << endl;
return 0;
}
這是我的程式碼在通過系統的時候由於超出限制時間,只有40%的分數,其中由於一次需要輸入三個變數,並且我們在之後的操作中需要對時間進行排序,所以我們採取結構體命名變數我們建時間按照從大到小的順序排列,將每個島嶼全部獨立分開重新構建來連通圖,我們將按照時間天數優先,針對測試樣例,以及之前的順序優先準則來重新構建,從第一條邊開始,如果不是連通圖,就呼叫一次fight++
,直到所有的結點全部構成連通圖,結束執行,無論後面還有多少條未加入的邊
未完待續
相關文章
- 並查集(一)並查集的幾種實現並查集
- 並查集java實現並查集Java
- 並查集-Java實現並查集Java
- 並查集的使用並查集
- 並查集的概念與演算法實現並查集演算法
- 【並查集】【帶偏移的並查集】食物鏈並查集
- 並查集到帶權並查集並查集
- 簡單易懂的並查集演算法以及並查集實戰演練並查集演算法
- 使用 NineData 實現備份集的實時查詢
- 查並集
- 並查集(二)並查集的演算法應用案例上並查集演算法
- 3.1並查集並查集
- 並查集(小白)並查集
- [leetcode] 並查集(Ⅱ)LeetCode並查集
- [leetcode] 並查集(Ⅲ)LeetCode並查集
- [leetcode] 並查集(Ⅰ)LeetCode並查集
- 並查集演算法Union-Find的思想、實現以及應用並查集演算法
- 使用並查集處理集合的合併和查詢問題並查集
- 並查集的應用2並查集
- 基於並查集的六度分隔理論的驗證與實現並查集
- 淺談並查集並查集
- 食物鏈(並查集)並查集
- 並查集跳躍並查集
- 各種並查集並查集
- 寫模板, 並查集。並查集
- 並查集(Union Find)並查集
- The Door Problem 並查集並查集
- 並查集練習並查集
- 並查集應用並查集
- 並查集在實際問題中的應用並查集
- Soso 的並查集寫掛了並查集
- 並查集的簡單應用並查集
- JAVA--set介面及其實現類的使用Java
- 並查集擴充套件並查集套件
- (Day3)並查集並查集
- 【轉】種類並查集並查集
- The Suspects-並查集(4)並查集
- 並查集演算法並查集演算法
- 並查集題目合集並查集