實現這個數據結構主要有三個函數:如下:
void UFset() //初始化
{
for(int i=0;i<N;i++)
parent[i]=-1;
}
int Find(int x) //返回第X節點所屬集合的根結點
{
for(int i=x;parent[i]>=0;i=parent[i]);
while(i!=x) //優化方案――壓縮路徑
{
int tmp=parent[x];
parent[x]=i;
x=tmp;
}
return i;
}
void Union(int R1,int R2) //將兩個不同集合的元素進行合併,使兩個集合中任兩個元素都連通
{
int tmp=parent[R1]+parent[R2];
if(parent[R1]>parent[R2]) //優化方案――加權法則
{
parent[R1]=R2;
parent[R2]=tmp;
}
else
{
parent[R2]=R1;
parent[R1]=tmp;
}
}
在Find函數中如果僅僅靠一個循環來直接得到節點的所屬集合根結點的話。通過多次的Union操作就會有很多節點在樹的比較深層次中,再Find起來就會很費時。通過加一個While循環(壓縮路徑)每次都把從i到集合根結點的路過結點的雙親直接設爲集合的根結點。雖然這增加了時間,但以後的Find會快。平均效能而言,這是個高效方法。
兩個集合並時,任一方可做爲另一方的孩子。怎樣來處理呢,現在一般採用加權合併,把兩個集合中元素個數少的做爲個數多的孩子。有什麼優勢呢?直觀上看,可以減少集合樹的深層元素的個數,減少Find時間。
如從0開始到N不斷合併i和i+1結點會怎樣呢?
這樣Find任一節點所屬集合的時間複雜度幾乎都是O(1)!!
不用加權規則就會得到
這就是典型的退化樹現象,再Find起來就會很費時(如找N-1節點看看)。
以下是讀入等價對後的parent[N]查找合併過程狀態:
再說一個並查集應用最完美的地方:最小生成樹的kruskal算法:
算法基本思想是:
開始把所有節點作爲各自的一個單獨集合,以爲每次從邊信息中得到一個最短的邊,如果這個邊鄰接了兩個不同集合的節點,就把這兩個節點的所屬集合結合起來,否則繼續搜索。直至所有節點都同屬一個集合,就生成了一個最小生成樹。int kruskal(int parent[],int N)
{
int i,j,k,x,y;
int min;
while(k<=N-1) //產生N-1條邊
{
min=MAX_INT;
for(i=1;i<=N;i++)
for(j=1;j<=N;j++)
{
if(sign[i][j]==0&&i!=j) //sign[N][N]是標誌節點是否被訪問過或已被排除……
{
if(arcs[i][j]<min) //arcs[N][N]存放邊的長度
{
min=arcs[i][j];
x=i;
y=j;
}//if
}//if
}//for
if(Find(parent,x)==Find(parent,y)) //如X,Y已經屬同一連通分量,則不合並,排除……
sign[x][y]=1;
else
{
Union(parent,x,y);
Sign[x][y]=1;
}
並查集學習(2)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.