並查集以及應用

lxs1998513發表於2017-07-14
參考 http://blog.csdn.net/dm_vincent/article/details/7655764


動態連通圖可能的操作
查詢節點屬於的組:陣列對應位置的值即為組號
判斷兩個節點是否屬於同一組:分別得到兩個節點的組號,然後判斷組號是否相等
連線兩個節點:分別得到兩個節點的組號,組號相同時,退出操作,組號不同時,通過判斷組大小程式連線
獲取組的數目:初始化為整個陣列的大小,每次連線後,遞減一

class UF;
Union(int i,int j); 連線
int Find(int i); 查詢組號
BOOL Cnnected(int i,int j) 判斷是否為同一組
int Count();  返回組號


class UF
{
private :
int* id;
int count;
public:
int* sz;
UF(int N)
{
count = N;
id = new int[N];
sz = new int[N];
for (int i = 0; i < N; i++)
{
id[i] = i;
sz[i] = 1;
}
}


int Find(int i)  //路徑壓縮
{
while (i != id[i])
{
id[i] = id[id[i]];  //將父節點設定為爺爺節點
i = id[i];
}
return i;
}
int Count()
{
return count;
}
BOOL Connected(int i, int j)
{
return Find(i) == Find(j);
}
VOID Union(int i, int j)
{
int a;
int b;
a = Find(i);
b = Find(j);
if (Find(i) == Find(j))
{
return;
}
if (sz[i] < sz[j])   //比重
{
id[a] = b;
}
else
{
id[b] = a;
}
count--;
}
};

int main()
{
UF uf(10);
int i;
int j;
int V1[6][2] = { 1,2,4,5,7,6,3,9,7,2,3,2 };
for (i = 0; i < 6; i++)
{
uf.Union(V1[i][0], V1[i][1]);
}
j = uf.Count();
printf("組號 %d", j);


    return 0;
}


-----------------------------------------------------------------------------------------------
 
poj上一個題目  個人解題的思路 也有借鑑  

A吃B B吃C C吃A 總共樹林裡有三種動物,N個動物   現在有一定數量的話 要求判斷真假 並輸出假話個數  
解題思路:
不同於普通的並查集,這個題目節點直接存在一定的利害關係,故考慮這是一個帶權並查集,即確定每個節點所帶的數值來表示其與父節點或子節點的關係。

一,確定權值(這裡應該是相對於子節點)
現在定義如下
取 0 1 2 來表示關係,原因:第一 父節點和子節點關係有三種——同類 相對於子節點(被父節點吃 和吃父節點) 相對於父節點(吃子節點和被子節點吃)  第二 題意所需  在後續會明白

0  表示父節點和子節點同類
1  表示子節點被父節點吃或父節點吃子節點
2  表示子節點吃父節點或父節點被子節點吃

二,初始化
對每個節點進行初始化 
節點結構  
struct animal{
int relation;
int parents;}
在初始化時每個節點按序賦值 ,relation為1(自己與自己是同類),parents=id(自己)

壓縮路徑
在每一次查詢的過程中,將子節點連線到爺爺節點上  
animal[i].parents = animal[animal[i].parents].parents;

這裡就出現了這個問題的核心 在壓縮路徑後,由於破壞了原有的狀態,如何根據原有的資訊,來得到新的資訊。
即如何確定子節點和爺爺節點的relation
這裡採用列舉法觀察
子節點        父節點      子節點和爺爺節點的關係
  0              0                0
  0              1                1
  0              2                2
  1              0                1
  1              1                2
  1              2                3
  2              0                2
  2              1                0
  2              2                1

不難發現 這裡子節點和爺爺節點的關係可寫為
r[子爺] = (r[子] + r[父])%3

現在處理稍複雜情況連線  
x->y->p     x->q  現在將兩個進行合併  即將兩個根節點相連

這裡處理方法為
1. 通過x->q推出 q相對於x的關係  
這裡通過列舉
子節點        父相對於子
  0              0
  1              2
  2              1
規律為 r[父相對於子] =   (3- r[子])%3

現在過程為 
2.x->y->p  在通過x找根節點時 將x連線到p上
這裡 r[x1] = (r[x]+r[y])%3
對於x->q  
r[q相對於x] = (3-r[x])%3
現在等價為q->x->p  將q連線到p上 
r[q] =(r[x1] + r[q])
= (r[x] + r[y] + (3-r[x])%3 )%3


Union函式 
最為複雜 這裡情況多變但由於存在路徑壓縮 所以知樹的高度一般最多不會超過三層。故最長只會出現
x1->x2->x3  與y1->y2->y3 進行連線 這應該是最複雜的情況  
首先在處理時確定一個前提 那就是 只有從y到x的連線  就是每句話的後一個向前一個進行連線

現在是最簡單的 
situaion 1:
1  x1 y1 (x1 and y1 are all the roots at this situation)
solution:
Find(x1)
change relaion(x1)
Find(y1)
change relaion(y1)   //nothing happen

y1.parent = x1
y1.relation = 1-1 


situation 2:
2  x1  y1 (x1 and y1 are all the roots at this situation)
solution:
Find(x1)
change relaion(x1)
Find(y1)
change relaion(y1)   //nothing happen
y1.parent = x1
y1.relation = 2-1


situation 3:
1 x2 y1(x2->x1  y1 is the root)
solution:
Find(x2)   //xroot = x1
change relaion(x2)  
Find(y1)
change relaion(y1)   

y1.parent = x2
y1.relation = 1-1

situation 4:
2 x2 y1((x2->x1  y1 is the root)
solution:
Find(x2)   //xroot = x1
change relaion(x2)  
Find(y1)
change relaion(y1)   

yroot.parent = x2
yroot.relation = 2-1

situation 5:
1 x2 y2((x2->x1  y2->y1 )
solution:
Find(x2)   //xroot = x1
change relaion(x2)  
Find(y2) //yroot = y1
change relaion(y2)   

yroot.parent = xroot
yroot.relation = ((3-r[yroot])%3 + d - 1(話中的數) + r[x2])%3 

而對於更加複雜的情況 可用上述式子進行等效替換 故不進行贅述。

這裡是大致思想
程式碼就不進行貼上

相關文章