數據結構(四)樹---集合的表示及查找(並查集)
阿新 • • 發佈:2018-08-17
點數據 如何 某個結點 efault .data nts 結構 問題 amp
一:集合運算
交,並,補,差,判斷一個元素是否屬於某一集合
並查集將在判斷連通性和是否成環上面起到至關重要的作用
二:並查集
(一)集合並
並集間有一元素相連
(二)查某元素屬於什麽集合
可用樹來表示集合,每個結點代表一個集合元素
S1={1,2,4,7} S2={3,5,8} S3={6,9,10}
我們可以使用雙親表示法來判斷某個結點的根結點,從而判斷是否是使用某個集合
#define MAX_TREE_SIZE 100 typedef int TElemType; typedef struct PTNode //結點結構{ TElemType data; //結點數據 int parent; //雙親位置 }PTNode; typedef struct //樹結構 { PTNode nodes[MAX_TREE_SIZE]; //結點數組 int r, n; //r是根位置,n是結點數 }PTree;
三:查找(根)的實現
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #define MAX_SIZE 100 typedef int TElemType; typedef結構定義struct _PTNode { int data; int parent; }PTNode; typedef struct { PTNode set[MAX_SIZE]; int n; }SetType;
void CreateSets(SetType *Set) { //輸入數據,使第一個數變為根節點 int d=0,parent=0; int i = 0; Set->n = 0; printf("input number(data,parent) to create set Number(<-1,-1> to exit):\n創建集合"); while (1) { scanf("%d,%d", &d,&parent); if (d == -1&&parent==-1) break; Set->set[i].data = d; Set->set[i].parent = parent; Set->n++; i++; } printf("create set finish\n"); }
void ShowSet(SetType s) { int i; for (i = 0; i < s.n; i++) printf("%d %d %d\n", i, s.set[i].data, s.set[i].parent); }顯示集合數據
int Find(SetType s, TElemType e) { int i; for (i = 0; i < s.n&&e != s.set[i].data; i++) ; if (i >= s.n) return -1; for (; s.set[i].parent >= 0; i = s.set[i].parent) ; return i; }
int main() { SetType set; TElemType n; int root; CreateSets(&set); ShowSet(set); printf("input number to find root:\n"); scanf("%d", &n); root=Find(set, n); printf("Finf Root in %d\n", root); system("pause"); return 0; }
在下面創建了兩個集合,我們判斷其中一個數是屬於哪個集合
四:並運算
(一)步驟
1.找到x1,x2兩個元素根節點 2.將其中一個根添加到另一個樹下面
(二)改進
用小樹合入大數,盡可能將新合並的樹的高度變小,提高查找效率,如果樹太高,find效率會變低
1.大樹合入小樹:我們對於新合並的數據查找根的效率會很低
2.小樹合入大樹:效率並未降低多少
(三)問題
樹高我們應該如何獲取?是在每次合並前去計算一遍嗎?似乎計算量還不小。那我們應該如何做? 想到根節點了嗎?原來是存放-1的,我們現在將其數據存放為"-高",這樣獲取高度將變得十分方便
(四)代碼實現
1.修改代碼,在創建集合後面,更新根節點的樹高度
void CreateSets(SetType *Set) { //輸入數據,使第一個數變為根節點 int d=0,parent=0; int j,i = 0; int min,high; Set->n = 0; printf("input number(data,parent) to create set Number(<-1,-1> to exit):\n"); while (1) { scanf("%d,%d", &d,&parent); if (d == -1&&parent==-1) break; Set->set[i].data = d; Set->set[i].parent = parent; Set->n++; i++; } //更新每個根節點的數據 for (i = Set->n; i >= 0;i--) { j = i; high = 1; for (; Set->set[j].parent >= 0; j = Set->set[j].parent) high++; if (-high<Set->set[j].parent) Set->set[j].parent = -high; } printf("create set finish\n"); }
2.根據高度實現合並操作
void Union(SetType* s, SetName Root1, SetName Root2) { if (s->set[Root1].parent<s->set[Root2].parent)//由於高度存放是負數,所以我們比較大小是不一樣的 { //說明Root1高 s->set[Root2].parent = Root1; } else if (s->set[Root1].parent == s->set[Root2].parent) { //隨便合並,不過高度加一 s->set[Root2].parent = Root1; s->set[Root1].parent -= 1; } else { //說明Root2高 s->set[Root1].parent = Root2; } }
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #define MAX_SIZE 100 typedef int TElemType; typedef int SetName; //默認根節點下標作為集合名稱 typedef struct _PTNode { int data; int parent; }PTNode; typedef struct { PTNode set[MAX_SIZE]; int n; }SetType; void CreateSets(SetType *Set) { //輸入數據,使第一個數變為根節點 int d=0,parent=0; int j,i = 0; int min,high; Set->n = 0; printf("input number(data,parent) to create set Number(<-1,-1> to exit):\n"); while (1) { scanf("%d,%d", &d,&parent); if (d == -1&&parent==-1) break; Set->set[i].data = d; Set->set[i].parent = parent; Set->n++; i++; } //更新每個根節點的數據 for (i = Set->n; i >= 0;i--) { j = i; high = 1; for (; Set->set[j].parent >= 0; j = Set->set[j].parent) high++; if (-high<Set->set[j].parent) Set->set[j].parent = -high; } printf("create set finish\n"); } void Union(SetType* s, SetName Root1, SetName Root2) { if (s->set[Root1].parent<s->set[Root2].parent)//由於高度存放是負數,所以我們比較大小是不一樣的 { //說明Root1高 s->set[Root2].parent = Root1; } else if (s->set[Root1].parent == s->set[Root2].parent) { //隨便合並,不過高度加一 s->set[Root2].parent = Root1; s->set[Root1].parent -= 1; } else { //說明Root2高 s->set[Root1].parent = Root2; } } int Find(SetType s, TElemType e) { int i; for (i = 0; i < s.n&&e != s.set[i].data; i++) ; if (i >= s.n) return -1; for (; s.set[i].parent >= 0; i = s.set[i].parent) ; return i; } void ShowSet(SetType s) { int i; for (i = 0; i < s.n; i++) printf("%d %d %d\n", i, s.set[i].data, s.set[i].parent); } int main() { SetType set; TElemType n; int root; CreateSets(&set); ShowSet(set); printf("input number to find root:\n"); scanf("%d", &n); root=Find(set, n); printf("Finf Root in %d\n", root); printf("Union root1 and root2:\n"); Union(&set, Find(set, 5), Find(set, 9)); ShowSet(set); printf("Union root1 and root2 finished\n"); system("pause"); return 0; }全部代碼
五:實例一:檢測網絡是否連通
局域網中有5臺主機,我們要檢測這五臺主機之間是否聯網,並實現實時連接和檢測
C代表檢測,I代表連接 C 3 2代表檢測3,2號兩臺主機是否聯網 I 3 2代表將3,2兩臺主機連接 C 1 5 I 4 5 I 2 4 C 3 5 S表示結束
實現方法和上面並查集相似,只是調用方法不一樣而已
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #define MAX_SIZE 100 typedef int TElemType; typedef int SetName; //默認根節點下標作為集合名稱 typedef struct _PTNode { int data; int parent; }PTNode; typedef struct { PTNode set[MAX_SIZE]; int n; }SetType; void CreateSets(SetType *Set) { //輸入數據,使第一個數變為根節點 int d=0,parent=0; int i = 0; Set->n = 0; printf("input number of hosts to operate:\n"); scanf("%d", &d); while (d) { Set->set[i].data = i + 1; Set->set[i].parent = -1; Set->n++; i++; d--; } //由於是初始n臺獨立的主機,所以我們不需要去初始其高 printf("create hosts finish\n"); } void Union(SetType* s, SetName Root1, SetName Root2) { if (s->set[Root1].parent<s->set[Root2].parent)//由於高度存放是負數,所以我們比較大小是不一樣的 { //說明Root1高 s->set[Root2].parent = Root1; } else if (s->set[Root1].parent == s->set[Root2].parent) { //隨便合並,不過高度加一 s->set[Root2].parent = Root1; s->set[Root1].parent -= 1; } else { //說明Root2高 s->set[Root1].parent = Root2; } } int Find(SetType s, TElemType e) { int i; for (i = 0; i < s.n&&e != s.set[i].data; i++) ; if (i >= s.n) return -1; for (; s.set[i].parent >= 0; i = s.set[i].parent) ; return i; } void ShowSet(SetType s) { int i; for (i = 0; i < s.n; i++) printf("%d %d %d\n", i, s.set[i].data, s.set[i].parent); } void OperateNetwork(SetType *set) { TElemType n; int host1, host2; char op; CreateSets(set); ShowSet(*set); while (1) { scanf("%c %d %d", &op, &host1, &host2); switch (op) { case ‘C‘: if (Find(*set, host1) != Find(*set, host2)) printf("No\n"); else printf("Yes\n"); break; case ‘I‘: Union(set, Find(*set, host1), Find(*set, host2)); break; case ‘S‘: return; default: break; } } } void CheckNetWork(SetType s) { int i,n=0; for (i = 0; i < s.n; i++) if (s.set[i].parent < 0) n++; if (n == 1) printf("The network is connection\n"); else printf("There are %d components\n", n); } int main() { SetType set; OperateNetwork(&set); printf("CheckNetwork Finished\n"); ShowSet(set); CheckNetWork(set); system("pause"); return 0; }全部代碼
六:實例二:在克魯斯卡爾算法中判斷一棵生成樹中若是加入一條邊是否形成回路
問:我們現在連接邊v4-v2是否形成回路?如何判斷?我們若是連接v4-v6是否還是一棵樹?
解決思路:
使用並查集
我們通過判斷我們要加入的邊的兩個頂點是否在同一集合來決定是否生成環,若是不在一個集合,我們連接後,依舊是一個生成樹,但是如果在一個集合中,連接兩頂點,一定是形成回路
實現代碼基本和上面是一致的,不再贅述
1.將各個結點的parent置為-1
2.開始連接各個樹,使用並操作
3.在連接的同時去判斷連接的那條邊的兩個頂點對於的樹的根對應的下標是不是同一位置,若是一個位置,就會生成環,不是同一個根,我們就可以正常連接
數據結構(四)樹---集合的表示及查找(並查集)