数据结构(四)树---集合的表示及查找(并查集)
一:集合运算
交,并,补,差,判断一个元素是否属于某一集合
并查集将在判断连通性和是否成环上面起到至关重要的作用
二:并查集
(一)集合并
并集间有一元素相连
(二)查某元素属于什么集合
可用树来表示集合,每个结点代表一个集合元素
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;
三:查找(根)的实现
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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"); }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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; } }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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表示结束
实现方法和上面并查集相似,只是调用方法不一样而已
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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.在连接的同时去判断连接的那条边的两个顶点对于的树的根对应的下标是不是同一位置,若是一个位置,就会生成环,不是同一个根,我们就可以正常连接