Disjoint Sets
Disjoint Sets
Disjoint Sets的意思是一堆集合們,它們相互之間都沒有交集。沒有交集是指:各個集合之間沒有擁有共同、相同的元素。中文稱作「分離集」。
Disjoint Sets的性質相當特殊。資訊學家仔細觀察其特性後,精心設計出一套優雅美觀的資料結構,可以快速的做集合運算。
Union、Find、Split
由於每個Disjoint Sets指的就是集合們都沒有交集,我們就不用考慮交集、差集等等的運算,因為結果很明顯。所以只需要考慮union、find、split這三個集合運算:
union就是將兩個集合做聯集,合併成一個集合。find就是找找看一個元素是在哪個集合裡面。split就是把一個元素從一個集合中分離出來。
【split此處暫不介紹,俟編者讀過書後再來寫。】
Disjoint Sets的應用範例:分組
現在教室裡有十個學生,他們為了做報告,要進行自由分組──每組的人數是不固定的,有些人是自己一個人一組,也有可能這十個學生都同一組。學生聲稱已經分好組了,但是你還是很擔心分組的情況,所以去找了幾個學生來問話。學生會告訴你他和誰誰誰一組,但是他可能不知道他們那組全部的人到底有誰,因為同一組的組員可能又去拉別人入組,但是他不知情。現在你知道了一些學生告訴你的資訊,你要怎麼知道到底誰和誰同組,誰和誰不同組呢?
要解決這樣的問題,便可以使用Disjoint Sets。不管學生怎麼分組,任何一個學生除了是自己組的組員之外,不可能是別組的組員。所以這些學生分出來的組(集合),必定是Disjoint Sets。
Disjoint Sets: 簡單的資料結構
簡單的資料結構
讓一條int陣列的第x格代表第x人,格子裡填上這個人所屬的團體編號。若兩個人在同一團體,他們的格子裡就會有相同的團體編號。這是很直觀的方式。
- int g[10]; // 每個人所屬的團體編號
- g[0] = 0; // 第 0 人在第 0 團
- g[1] = 0; // 第 1 人在第 0 團
- g[2] = 1; // 第 2 人在第 1 團
- …
- g[9] = 7; // 第 9 人在第 7 團
int g[10]; // 每個人所屬的團體編號
g[0] = 0; // 第 0 人在第 0 團
g[1] = 0; // 第 1 人在第 0 團
g[2] = 1; // 第 2 人在第 1 團
…
g[9] = 7; // 第 9 人在第 7 團
初始化
一開始大家還沒開始分團的時候,其實可以想做是:每個人都不同團,每個人都是自己一人一團。有個方便的初始值設定方法,就是將第x格的值設成x,這樣每個人就都是不同團體的了。
- void initialize()
- {
- for (int x=0; x<10; x++)
- g[x] = x;
- }
void initialize()
{
for (int x=0; x<10; x++)
g[x] = x;
}
Union: 兩個人想合併自己所屬團體
現在有兩團想要合併成一團,交涉的人分別是x和y。x y想要合併成一團,只要把所有與x y同團的人,都填上同一個團體編號就行了。可以找x y其中一團的團體編號,作為新的團體編號,這樣就不需要額外的編號了。(這裡我們不考慮會不會有人不服氣的問題。)
- void union(int x, int y)
- {
- // 要是 x y 不同團,才設成同團,以節省時間。
- if (g[x] != g[y])
- {
- int gmax = max(g[x], g[y]); // 團體編號比較大那個
- int gmin = min(g[x], g[y]); // 團體編號比較小那個
- // 把所有與 x y 同團的人,都填上同一個團體編號。
- for (int i=0; i<10; i++)
- if (g[i] == gmax)
- g[i] = gmin;
- }
- }
void union(int x, int y)
{
// 要是 x y 不同團,才設成同團,以節省時間。
if (g[x] != g[y])
{
int gmax = max(g[x], g[y]); // 團體編號比較大那個
int gmin = min(g[x], g[y]); // 團體編號比較小那個
// 把所有與 x y 同團的人,都填上同一個團體編號。
for (int i=0; i<10; i++)
if (g[i] == gmax)
g[i] = gmin;
}
}
- void union(int x, int y)
- {
- if (g[x] == g[y]) return;
- x = g[x]; y = g[y];
- for (int i=0; i<10; i++)
- if (g[i] == x)
- g[i] = y;
- }
void union(int x, int y)
{
if (g[x] == g[y]) return;
x = g[x]; y = g[y];
for (int i=0; i<10; i++)
if (g[i] == x)
g[i] = y;
}
Find: 找出一個人在哪一團?
直接看團體編號即可。
- int find(int x)
- {
- return g[x];
- }
int find(int x)
{
return g[x];
}
Equivalent Relation: 兩個人是否同團?
直接看團體編號即可。
- bool equivlence(int x, int y)
- {
- return g[x] == g[y];
- }
bool equivlence(int x, int y)
{
return g[x] == g[y];
}
Number of Sets: 全部總共有幾個團體?
兩團合併成一團後,總團體數就會減少一團。所以只要修改一下union的程式碼就可以了。
- int groups = 10; // 團體數
- void union(int x, int y)
- {
- if (g[x] == g[y]) return;
- groups--; // 兩團合併成一團,總團體數就會減少一團。
- int gmax = max(g[x], g[y]), gmin = min(g[x], g[y]);
- for (int i=0; i<10; i++)
- if (g[i] == gmax)
- g[i] = gmin;
- }
int groups = 10; // 團體數
void union(int x, int y)
{
if (g[x] == g[y]) return;
groups--; // 兩團合併成一團,總團體數就會減少一團。
int gmax = max(g[x], g[y]), gmin = min(g[x], g[y]);
for (int i=0; i<10; i++)
if (g[i] == gmax)
g[i] = gmin;
}
Cardinality of a Set: 一個團體總共有幾個人?
一個一個數是差勁的方法:
- // 計算出團體編號為 gn 的人數
- int cardinality(int gn)
- {
- int people = 0;
- for (int i=0; i<10; i++)
- if (g[i] == gn)
- people++;
- return people;
- }
// 計算出團體編號為 gn 的人數
int cardinality(int gn)
{
int people = 0;
for (int i=0; i<10; i++)
if (g[i] == gn)
people++;
return people;
}
比較好的方法是:另外開一條陣列去紀錄每個團體的人數吧!陣列第x格填入團體編號為x的人數。要找出一個團體的人數,就直接從陣列裡面找。
以團體的角度來看:兩團合併成一團後,團體人數就會改變。以人的角度來看:當一個人所屬的團體被改變時,就調整人數。所以只要修改一下union的程式碼就可以了。
- int n[10]; // 每個團體的人數
- void initialize()
- {
- for (int i=0; i<10; i++)
- {
- g[i] = i;
- n[i] = 1; // 團體編號從 0 到 9,每團都是一個人。
- }
- }
- void union(int x, int y)
- {
- if (g[x] == g[y]) return;
- groups--;
- int gmax = max(g[x], g[y]), gmin = min(g[x], g[y]);
- for (int i=0; i<10; i++)
- if (g[i] == gmax)
- {
- g[i] = gmin;
- n[gmax]--; // 團體編號為 gmax 的人數減少一人
- n[gmin]++; // 團體編號為 gmin 的人數增加一人
- }
- }
- int cardinality(int gn)
- {
- return n[gn];
- }
int n[10]; // 每個團體的人數
void initialize()
{
for (int i=0; i<10; i++)
{
g[i] = i;
n[i] = 1; // 團體編號從 0 到 9,每團都是一個人。
}
}
void union(int x, int y)
{
if (g[x] == g[y]) return;
groups--;
int gmax = max(g[x], g[y]), gmin = min(g[x], g[y]);
for (int i=0; i<10; i++)
if (g[i] == gmax)
{
g[i] = gmin;
n[gmax]--; // 團體編號為 gmax 的人數減少一人
n[gmin]++; // 團體編號為 gmin 的人數增加一人
}
}
int cardinality(int gn)
{
return n[gn];
}
根據團體的人數多寡來做union
合併團體時,將小的團體併入大的團體,可以節省一點點設定團體和增減人數所需的時間。
- void union(int x, int y)
- {
- if (g[x] == g[y]) return;
- groups--;
- int gmax = x, gmin = y;
- if (n[x] > n[y]) swap(gmax, gmin);
- for (int i=0; i<10; i++)
- if (g[i] == gmax)
- {
- g[i] = gmin;
- n[gmax]--;
- n[gmin]++;
- }
- }
void union(int x, int y)
{
if (g[x] == g[y]) return;
groups--;
int gmax = x, gmin = y;
if (n[x] > n[y]) swap(gmax, gmin);
for (int i=0; i<10; i++)
if (g[i] == gmax)
{
g[i] = gmin;
n[gmax]--;
n[gmin]++;
}
}
Singleton Set: 團體是否合併過?
自己一個人一組,沒有union過。
- bool singleton(int x)
- {
- return member[x] == 1;
- }
bool singleton(int x)
{
return member[x] == 1;
}
時間複雜度
union為O(N),find、equivalence、cardinality、singleton為O(1)。
如果有N個人,全部的人都union過一遍,每次union要花O(N)時間,總共是花O(N^2)時間。
空間複雜度
如果有N個人,就需要一條N格的陣列,為O(N)。
UVa 10608
Disjoint Sets: Disjoint-set Linked Lists
Disjoint-set Linked Lists
和Set Linked Lists的方式是一樣的。【待補文字】
Disjoint Sets: Disjoint-set Forest
Disjoint-set Forest
讓一條int陣列的第x格代表第x人──不過,格子裡改成填上x的老大是誰:
- int g[10]; // 紀錄每個人的老大
- g[0] = 0; // 第 0 人的老大是第 0 人
- g[1] = 0; // 第 1 人的老大是第 0 人
- g[2] = 1; // 第 2 人在老大是第 1 人
- …
- g[9] = 3; // 第 9 人在老大是第 8 人
int g[10]; // 紀錄每個人的老大
g[0] = 0; // 第 0 人的老大是第 0 人
g[1] = 0; // 第 1 人的老大是第 0 人
g[2] = 1; // 第 2 人在老大是第 1 人
…
g[9] = 3; // 第 9 人在老大是第 8 人
有一點像是老鼠會,也可以看作是圖論所提到的有根樹(rooted tree)。各位可能會有一個疑問:一個團體之中,每個人都有一個老大,那麼某君的老大是誰呢?可以暫且設定成自己:
以無法向上追溯的設定,來代表這個人是團體的大頭目。團體的所有成員,他們往上追溯之後,會是同一個頭目。一個團體中,也只會有一個頭目,由他來支配團體、做為團體的代表。
一個團體就像是一棵分支很複雜的有根樹,不過卻是萬流歸宗的。這些團體構成了一叢森林,故名Disjoint-set Forest。
初始化
一開始大家還沒開始分團的時候,其實可以想做是:每個人都不同團,每個人都是自己一人一團,而且自己當頭目。根據上述的設定方是,要將第x格的值設成x,這樣每個人就都是不同團體的頭目了。
- int p[10];
- void initialize()
- {
- for (int x=0; x<10; x++)
- p[x] = x;
- }
int p[10];
void initialize()
{
for (int x=0; x<10; x++)
p[x] = x;
}
Find: 找出一個人在哪一團?
接下來談談頭目吧。頭目在一個團體之中扮演舉足輕重的角色,一個團體只會有一個頭目,所以可以用頭目做為一個團體的代表。
- int find(int x)
- {
- // 當 x 不是頭目,就繼續追本溯源,直到找到頭目。
- while (x != p[x])
- x = p[x];
- return x;
- }
int find(int x)
{
// 當 x 不是頭目,就繼續追本溯源,直到找到頭目。
while (x != p[x])
x = p[x];
return x;
}
- int find(int x)
- {
- return x == p[x] ? x : find(p[x]);
- }
int find(int x)
{
return x == p[x] ? x : find(p[x]);
}
find的時候可以順便把遇到的人,將其老大都設為頭目。如此一來下次find的時候就會變更快了。
- int find(int x)
- {
- return x == p[x] ? x : p[x] = find(p[x]);
- }
int find(int x)
{
return x == p[x] ? x : p[x] = find(p[x]);
}
Union: 兩個人想合併自己所屬團體
目標是將x y兩個團體做合併,並重新選出一個頭目。最簡單的方式是:讓x的頭目帶著他所有小弟,投靠y團體的隨便一個人之下,如此一來兩個團體就擁有共同的頭目了,也依然保持著老鼠會的架構。
- void union(int x, int y)
- {
- return p[find(x)] = y;
- }
void union(int x, int y)
{
return p[find(x)] = y;
}
union的時候,直接投靠對方的老大,可以讓樹的深度增加最少。如此一來下次find的時候就會變更快了。
- void union(int x, int y)
- {
- return p[find(x)] = find(y);
- }
void union(int x, int y)
{
return p[find(x)] = find(y);
}
實做小叮嚀:union要確保投奔的人是頭目,投奔後頭目只有一個。另外也要避免同團體的人互相設定彼此是頭目,否則find會無限循環。
Equivalent Relation: 兩個人是否同團?
同一個團體中的成員,他們的頭目都是同一個人。要看兩個人是不是同一團,看看他們的頭目是不是同一人就行了。
- bool equivalence(int x, int y)
- {
- return find(x) == find(y);
- }
bool equivalence(int x, int y)
{
return find(x) == find(y);
}
Number of Sets: 全部總共有幾個團體?
兩團合併成一團後,總團體數就會減少一團。所以只要修改一下union的程式碼就可以了。
- int groups = 10; // 團體數
- void union(int x, int y)
- {
- x = find(x); y = find(y);
- if (x == y) return;
- groups--; // 兩團合併成一團,總團體數就會減少一團。
- p[x] = y;
- }
int groups = 10; // 團體數
void union(int x, int y)
{
x = find(x); y = find(y);
if (x == y) return;
groups--; // 兩團合併成一團,總團體數就會減少一團。
p[x] = y;
}
Cardinality of a Set: 一個團體總共有幾個人?
先前提到頭目可以支配、代表一個團體,所以把焦點放在頭目上吧。嘗試開一個陣列來記錄頭目帶領的人數,n[頭目] = 頭目帶領的人數。
以團體的角度來看:兩團合併成一團後,團體人數就會改變。以人的角度來看:當一個人所屬的團體被改變時,就調整人數。所以只要修改一下union的程式碼就可以了。
- int n[10]; // 每個頭目帶領的人數
- void initialize()
- {
- for (int i=0; i<10; i++)
- {
- g[i] = i;
- n[i] = 1; // 頭目有第 0 到第 9 人,每團都是一個人。
- }
- }
- void union(int x, int y)
- {
- x = find(x); y = find(y);
- if (x == y) return;
- groups--;
- n[y] += n[x]; // 新頭目吸收人數
- n[x] = 0; // 舊頭目不再帶領人
- p[x] = y;
- }
- int cardinality(int x)
- {
- return n[find(x)];
- }
int n[10]; // 每個頭目帶領的人數
void initialize()
{
for (int i=0; i<10; i++)
{
g[i] = i;
n[i] = 1; // 頭目有第 0 到第 9 人,每團都是一個人。
}
}
void union(int x, int y)
{
x = find(x); y = find(y);
if (x == y) return;
groups--;
n[y] += n[x]; // 新頭目吸收人數
n[x] = 0; // 舊頭目不再帶領人
p[x] = y;
}
int cardinality(int x)
{
return n[find(x)];
}
Singleton Set: 團體是否合併過?
自己一個人一組,沒有union過。
- bool singleton(int x)
- {
- return n[find(x)] == 1;
- }
bool singleton(int x)
{
return n[find(x)] == 1;
}
也就是自已當頭目的意思。
- bool singleton(int x)
- {
- return p[x] == x;
- }
bool singleton(int x)
{
return p[x] == x;
}
時間複雜度
union、find、singleton、equivalence的平均時間是Ω(α(N)),無法更低了,cardinality為O(1)。其中α(N)是Ackermann function f(N,N)的反函數。我不會證。【待補文字】
空間複雜度
如果有N個人,就需要一條N格的陣列,為O(N)。
UVa 793 879 10158 10505 10583 10608 10685
Empty Set: 空集合
之前我們都未處理空集合。現在我們要改良原本的方法,讓它可以處理空集合,而效率仍然保持一樣。
先將資料結構做點改變。現在將陣列的第0格當作是一個空集合,不代表任何人。總人數如果有100人,那麼就要開101格的陣列。第0格是空集合,第1格到第100格,分別代表著100個人。
現在既然有了空集合,便可將頭目的老大設定為空集合,更具義理。也就是說,初始化時要將陣列的初始值都改成0。
- int g[10+1];
- void initialize()
- {
- for (int x=0; x<10+1; x++)
- p[x] = 0;
- }
int g[10+1];
void initialize()
{
for (int x=0; x<10+1; x++)
p[x] = 0;
}
- void empty(int x)
- {
- return x == 0;
- }
void empty(int x)
{
return x == 0;
}
多了空集合,就要另外考慮空集合做聯集時的影響。不管什麼集合,只要和空集合作聯集,集合都不會改變。所以,凡是遇到空集合,就不必做聯集了。
- void union(int x, int y)
- {
- x = find(x); y = find(y);
- if (x == y || x == 0 || y == 0) return;
- return p[x] = y;
- }
void union(int x, int y)
{
x = find(x); y = find(y);
if (x == y || x == 0 || y == 0) return;
return p[x] = y;
}
其他部分大致都不變,就不另外說明了。
Disjoint Sets進階應用
Disjoint Sets進階應用(UVa 10608)
我們可將Disjoint Set運用在結交朋友上面。但是樹立敵人該怎麼做呢?
先來整理一下Disjoint-set Forest的概念:
一、每個團體的頭目只有一個。每個團體的成員,他們的頭目都一樣,頭目可以視為一個團體的代表。
二、紀錄每個團體的人數時,我們利用了一條陣列,令n[頭目] = 頭目帶領的人數。
靈感來了:如法炮製,開一條類似記錄團體人數的陣列,用來紀錄頭目的敵人是哪一個頭目,意義同於紀錄敵對團體。
初始化
一開始大家都是沒有敵人的。
- int enemy[10+1];
- void intialize()
- {
- // 一開始每個人的敵人都是空集合
- for (int i=0; i<10+1; i++)
- enemy[i] = 0;
- }
int enemy[10+1];
void intialize()
{
// 一開始每個人的敵人都是空集合
for (int i=0; i<10+1; i++)
enemy[i] = 0;
}
樹立敵人
兩個團體的頭目互相設定彼此是敵人即可。
- void set_enemy(int x, int y)
- {
- int a = find(x), b = find(y);
- enemy[a] = b; // a 的敵人是 b
- enemy[b] = a; // b 的敵人是 a
- }
void set_enemy(int x, int y)
{
int a = find(x), b = find(y);
enemy[a] = b; // a 的敵人是 b
enemy[b] = a; // b 的敵人是 a
}
有一些意外狀況要考慮清楚。一、在a b互相設定為敵人之前,a b其實都各自有敵人了;二、a本來沒有敵人,b本來沒有敵人,或者a b本來都沒有敵人;三、a的敵人可能早就是b了,b的敵人也可能早就是a了,或者早就互為敵人了;四、a b互為朋友,不可能互為敵人。
第一點:在a b互相設定為敵人之前,a b其實都各自有敵人了。按照題意,a的敵人,是b的朋友,所以a b互設敵人,要將a既有的敵人,併入b的團體中;同理,也要將b既有的敵人,併入a的團體中。
- void set_enemy(int x, int y)
- {
- int a = find(x), b = find(y);
- union(enemy[a], b); // a 的敵人,是 b 的朋友
- union(enemy[b], a); // b 的敵人,是 a 的朋友
- enemy[a] = b;
- enemy[b] = a;
- }
void set_enemy(int x, int y)
{
int a = find(x), b = find(y);
union(enemy[a], b); // a 的敵人,是 b 的朋友
union(enemy[b], a); // b 的敵人,是 a 的朋友
enemy[a] = b;
enemy[b] = a;
}
第二點:a本來沒有敵人,b本來沒有敵人,或者a b本來都沒有敵人。我們的聯集程式碼可以處理空集合,沒有問題。
- void set_enemy(int x, int y)
- {
- int a = find(x), b = find(y);
- union(enemy[a], b); // 空集合做聯集,沒有問題
- union(enemy[b], a);
- enemy[a] = b;
- enemy[b] = a;
- }
void set_enemy(int x, int y)
{
int a = find(x), b = find(y);
union(enemy[a], b); // 空集合做聯集,沒有問題
union(enemy[b], a);
enemy[a] = b;
enemy[b] = a;
}
第三點:a的敵人可能早就是b了,b的敵人也可能早就是a了,或者早就互為敵人了。我們的聯集程式碼可以處理相同集合做聯集,沒有問題。
- void set_enemy(int x, int y)
- {
- int a = find(x), b = find(y);
- union(enemy[a], b); // 相同集合做聯集,沒有問題
- union(enemy[b], a);
- enemy[a] = b;
- enemy[b] = a;
- }
void set_enemy(int x, int y)
{
int a = find(x), b = find(y);
union(enemy[a], b); // 相同集合做聯集,沒有問題
union(enemy[b], a);
enemy[a] = b;
enemy[b] = a;
}
若是 enemy[a] == b,按照union的程式碼,enemy[a]和b不會真的做union。所以沒有必要增加程式碼。維持第二點的程式碼即可。
第四點:a b互為朋友,不可能互為敵人。這會導致矛盾,所以要修改一下程式碼,預防這個情況。
- void set_enemy(int x, int y)
- {
- int a = find(x), b = find(y);
- if (a == b) // a 和 b 在同一個團體
- cout << "發生了矛盾" << endl;
- else
- {
- union(enemy[a], b);
- union(enemy[b], a);
- enemy[a] = b;
- enemy[b] = a;
- }
- }
void set_enemy(int x, int y)
{
int a = find(x), b = find(y);
if (a == b) // a 和 b 在同一個團體
cout << "發生了矛盾" << endl;
else
{
union(enemy[a], b);
union(enemy[b], a);
enemy[a] = b;
enemy[b] = a;
}
}
結交朋友
由於現在多了enemy陣列的關係,交友就不能單純的只做union了。
如同樹立敵人,有一些意外狀況要考慮清楚。一、在a b互相設定為朋友之前,a b其實都各自有朋友了;二、a本來沒有朋友,b本來沒有朋友,或者a b本來都沒有朋友;三、a的朋友可能早就是b了,b的朋友也可能早就是a了,或者早就互為朋友了;四、a b互為敵人,不可能互為朋友。
第一點。其實就是union的概念。
- void set_friend(int x, int y)
- {
- union(x, y);
- }
void set_friend(int x, int y)
{
union(x, y);
}
第二點。仍是union的概念。
第三點。我們的聯集程式碼可以處理相同集合做聯集,沒有問題。
第四點。這會導致矛盾,所以要修改一下程式碼。
- void set_friend(int x, int y)
- {
- int a = find(x), b = find(y);
- if (enemy[a] == b || enemy[b] == a)
- cout << "發生了矛盾" << endl;
- else
- union(a, b);
- }
Disjoint Sets的更多相关文章
- 算法实践--不相交集合(Disjoint Sets)
什么是不相交集合(Disjoint Sets) 是这样的一组set,任何元素最多只能在一个set中 至少支持查找Find和合并Union操作 实现方式(基于树) 每个set都是一棵树 每棵树都由树的根 ...
- The Tree-planting Day and Simple Disjoint Sets
First I have to say: I have poor English. I am too young, too simple, sometimes naïve. It was tree-p ...
- 数据结构与算法分析 – Disjoint Set(并查集)
什么是并查集?并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题. 并查集的主要操作1.合并两个不相交集合2.判断两个元素是否属于同一集合 主要操作的解释 ...
- 并查集(Disjoint Set)
在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中.这一类问题其特点是看似并不复杂, ...
- 数据结构之并查集Union-Find Sets
1. 概述 并查集(Disjoint set或者Union-find set)是一种树型的数据结构,常用于处理一些不相交集合(Disjoint Sets)的合并及查询问题. 2. 基本操作 并查集 ...
- 并查集 (Union-Find Sets)及其应用
定义 并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题.常常在使用中以森林来表示. 集就是让每个元素构成一个单元素的集合,也就是按一定顺序将属于同一组的 ...
- 笔试算法题(38):并查集(Union-Find Sets)
议题:并查集(Union-Find Sets) 分析: 一种树型数据结构,用于处理不相交集合(Disjoint Sets)的合并以及查询:一开始让所有元素独立成树,也就是只有根节点的树:然后根据需要将 ...
- 并查集(disjoint set)的实现及应用
这里有一篇十分精彩.生动的 并查集详解 (转): "朋友的朋友就是朋友"⇒ 传递性,建立连通关系 disjoint set,并查集(一种集合),也叫不相交集,同时也是一种树型的数据 ...
- 【数据结构】【计算机视觉】并查集(disjoint set)结构介绍
1.简述 在实现多图像无序输入的拼接中,我们先使用surf算法对任意两幅图像进行特征点匹配,每对图像的匹配都有一个置信度confidence参数,来衡量两幅图匹配的可信度,当confidence> ...
随机推荐
- [51nod1503]猪和回文 DP
---题面--- 题解: 首先观察到题目要求的是合法回文串的个数,而回文串要求从前往后和从后往前是一样的,因此我们假设有两只猪,分别从左上和右下开始走,走相同的步数最后相遇,那么它们走的路能拼在一起构 ...
- JQuery选择器$()的工作原理浅析
每次申明一个jQuery对象的时候,返回的是jQuery.prototype.init对象,很多人就会不明白,init明明是jQuery.fn的方法啊,实际上这里不是方法,而是init的构造函数,因为 ...
- Hadoop 学习之MapReduce
MapReduce充分利用了分而治之,主要就是将一个数据量比较大的作业拆分为多个小作业的框架,而用户需要做的就是决定拆成多少份,以及定义作业本身,用户所要做的操作少了又少,真是Very Good! 一 ...
- cloudera manager配置
cloudera manager的数据库配置文件位置: /etc/cloudera-scm-server/db.properties
- O(n^2)以及O(nlogn)时间复杂度的排序算法
O(n^2)的算法 都是做的升序. 简单选择排序 思路:每次选择还未排序的区间的最小值和未排序区间的第一个值交换. function selectSort(arr){ for(let i = 0; i ...
- 使用state模块部署lamp架构
install_httpd: pkg.installed: - name: httpd httpd_running: service.running: - name: httpd - enable: ...
- [bzoj1770][Usaco2009 Nov]lights 燈——Gauss消元法
题意 给定一个无向图,初始状态所有点均为黑,如果更改一个点,那么它和与它相邻的点全部会被更改.一个点被更改当它的颜色与之前相反. 题解 第一道Gauss消元题.所谓gauss消元,就是使用初等行列式变 ...
- [bzoj1015][JSOI2008]星球大战——并查集+离线处理
题解 给定一张图,支持删点和询问连通块个数 按操作顺序处理的话要在删除点的同时维护图的形态(即图具体的连边情况),这是几乎不可做的 我们发现,这道题可以先读入操作,把没删的点的边先连上,然后再倒序处理 ...
- hdu 2817 A sequence of numbers(快速幂取余)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2817 题目大意:给出三个数,来判断是等差还是等比数列,再输入一个n,来计算第n个数的值. #inclu ...
- 【Mysql优化】索引碎片与维护
在长期的数据更改过程中, 索引文件和数据文件,都将产生空洞,形成碎片.(不停的删除修改导致) 解决办法: (1)我们可以通过一个nop操作(不产生对数据实质影响的操作), 来修改表. 比如: 表的引擎 ...