並查集學習(2)

 實現這個數據結構主要有三個函數:如下:

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;
    }

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