樹型集合的LINK和FIND DEPTH操作

2021-06-01 17:44:27 字數 3560 閱讀 5876

考慮如下兩類操作:

link(v,r): v是一棵樹中的結點,r是另一棵樹的根,link的執行使得r成為v的子結點,從而實現兩樹的合併。

find-depth(v):求出結點v的當前深度。

現在的問題是,對於若干個集合1……n,集合的資料結構採用樹來表示,對於這些集合可以進行以上兩類操作,現在考慮設計乙個效能良好的演算法來實現這兩類操作。特別對於編譯器的實現是有十分大得幫助的。

如果採用一般的樹型結構的話,find-depth操作可能面對的集合的樹結構退化成了鍊錶,所以最壞情況下執行o(n)條find-depth指令的時間複雜度可能是o(n2)。正如上篇文章所述的,這裡為了使得效能更好,我們需要進行路徑壓縮。路徑壓縮簡單來講就是將乙個節點到根節點的路徑上的所有節點都直接指向根節點。

但是,問題來了,當使用路徑壓縮的時候,整個樹中的節點(除了根節點)的高度都發生了變化,這又導致了find-depth的結果出錯。並且,使用路徑壓縮還對link指令有影響,由於以r為根的子樹中結點的深度全部發生了變化,則勢必要修改該子樹中所有結點的深度字段。此工作量很大,最壞情況下,o(n)條link指令的時間複雜度也為o(n2)。看來,這裡不僅僅需要路徑壓縮,還需要一些輔助的措施。

具體的方法:給每個結點v增加2個字段(count[v]和weight[v]),並把改造過的此類結點和樹所構成的森林稱為d-森林。(下文討論的操作都是在d-森林中進行的。)

count[v]: 記錄d-森林中以v為根的子樹中結點的個數。

weight[v]滿足下述性質:設在d-森林中執行指令find-depth(v),從v找到根結點a路徑上的結點是vi1(=v)vi2vi3…vik(=a), 則

在上例中,對左圖中兩子樹執行link(r,u),在原先的森林裡就成為了中圖的形狀;  為減小時間複雜度,實際執行時,link(r,u)不在原先的森林裡執行,而在d-森林中執行,執行後成為右圖的形狀。雖然形狀變了,但只要保證在d-森林中執行fink-depth時,能夠返回所有結點在原先森林中的正確深度即可。對上例而言,必須能夠得到fink-depth (r)=0, fink-depth (u)=1, fink-depth (w)=2。

初始時即任何link指令尚未執行(合併)時,不論是在原森林還是在d-森林中,所有結點均為單結點樹,故所有結點的weight值均為0即其深度為0。

為了保證weight值所具有的性質, 當在d-森林中執行find指令實施路徑壓縮時(即把vi1、vi2、vi3、…vi,k-2變為根結點a(即vik)的子結點時,注意vi,k-1原本就是根結點a的子結點),

要使每乙個結點vip (1≤p≤k-2)的新權變為

① find(p, i, w, weight):/*p、weight為陣列名,w是輸出引數*/

else  w=0;

return p[i] }

② find-depth(p,i,weight)

link指令執行的時候的原則是:把結點少的樹接到結點多的樹根下實現兩樹的合併,同時修改所有應發生變化的結點的weight值。

按原始定義, 在原森林中, v是一棵樹中的結點, r是另一棵樹的根,link的執行使得r成為v的子結點,從而實現兩樹的合併。在d-森林中,把含有v和r的2棵樹分別記為tv和tr。在d-森林中執行link時,並不直接把r做成v的子結點。正確反映在原森林中把r接在v之下時各結點的深度。設在d-森林中,tv和tr的根分別是v』和r』,合併時按count[v』]和count[r』]值的大小分為兩種情況:count[r』] ≤ count[v』]和count[v』]< count[r』]。若在d-森林中count[r』] ≤ count[v』]則v』作為根, 反之則r』作為根。

<1> count[r』] ≤ count[v』]

因為要把結點少的樹併入結點多的樹,故把r』作為v』的兒子。

可想而知,tv中的任一條路徑也沒有受到該接入的任何影響,即link執行時tv樹中各結點的weight值無需修改。tr中的除了根節點的其它各結點的weight值也均無需改變。只需要修改weight[r』]。如何修改?

由於r是根,故原先有depth(r)=0(也是值)。執行link指令後,把r作為v的兒子,則此時在原先的森林中,r的深度為depth(v)+1。設在d-森林中,r』的兒子是s。由weight的性質,在d-森林中的2棵樹合併之前有:

從r到s路徑上各結點的權和+舊weight[r』]=0(r在原森林中的深度),

即舊weight[r』]=-(從r到s路徑上各結點的權和)                                             (1)

按上述分析,在原森林中的2棵樹合併之後,r的深度為depth(v)+1;

另一方面,d-森林中對應的2棵樹合併後,按weight的性質,r在原森林中的深度為:從r到s的權和+新weight[r』]+weight[v』],因此有:

從r到s的權和+新weight[r』]+weight[v』]= depth(v)+1                                   (2)

聯立上述式子(1) (2)可得:

新weight[r』]=depth[v]+1-weight[v』]+舊weight[r』]。

<2> count[r』] < count[v』]

因為要把結點少的樹併入結點多的樹,故把v』作為r』的兒子。

合併後應有:從r到s的權和+新weight[r』]= depth(v)+1。所以新weight[r』]= depth(v)+1+舊weight[r』]。並且tr中的其它各結點的weight值均無需改變。

另外,weight[v』]也要修改。設在d-森林中,v』的兒子是t,

則合併前有:從v到t的權和+舊weight[v』]=depth(v) (v在原森林裡的深度)。

而合併以後應有:從v到t的權和+新weight[v』]+新weight[r』]=depth(v)(合併時是將r接到v之下,故v在原森林裡的深度並不改變。)2式聯立得:新weight[v』』]=舊weight[v』]-新weight[r』]。

最後,不論哪一種情況,weight值修改完後,都要將新根的count值改為count[v』]+count[r』]。

link(v,r,p,weight,count)   /*v是tv的乙個節點,r是tr的原樹的根節點*/

v』=find(p, v, wv, weight)

r』=find(p, r, wr, weight)

if count[r』] ≤ count[v』]

then

else

#include #include #define		n	17

typedef struct set_nodeset_node;

void init_set(set_node *set)

}int find(int n_num,int *w,set_node* set)

else *w=0;

return set[n_num].parent;

}int find_depth(int n_num,set_node* set)

void link(int n_num,int s_num,set_node *set)

else

}int main()

if(ins==-2) }

return 0;

}

that's all~~~

泛型和集合

集合 arraylist類,是命名空間system.collections下的一部分,它是使用大小可按需動態增加的陣列實現ilist介面,但是arraylist不是型別安全的。泛型集合 list類,是對arraylist的乙個完善。通常情況下建議使用泛型集合,因為這樣可以獲得型別安全的直接優點。il...

C 中的非泛型集合和泛型集合

今天學習了c 中的非泛型集合arraylist和泛型集合list 先來看非泛型集合 使用非泛型集合之前注意呼叫system.collections using system using system.collections namespace lesson21 2 清空陣列 a.clear flag...

泛型和map集合

1 泛型 jdk1.5出現的安全機制 好處 1 將執行時期的問題classcastexception轉換為編譯時期。2 避免了強制轉換的麻煩 什麼時候用?當操作的引用資料型別不確定的時候,就使用 將要操作的引用資料型別傳入即可,其實 就是乙個用於接收具體引用資料型別的引數範圍。泛型技術是給編譯器使用...