1.集合
数学中集合的定义:具有某种特定性质的具体的或抽象的对象汇总而成的集体。其中,构成集合的这些对象则称为该集合的元素
集合比如:一个班级的同学;一个生物群落,同样爱好的人等。但是这些集合中之间是不相交的,也就是不属于同一个集合(当然这些大家都明白,只是因为接下来的内容需要,所以提及这个概念)。
(1)多个集合
怎么判断集合a中的元素d是属于集合a的呢?
思路:其实集合中的元素组成的就像一棵树一样,既然是树的话,那么就有一个“根”节点,如果从d->b->a找到a这个根节点的话,就可以判断元素d是否属于集合a了。
怎么合并两个集合呢?
思路:只要集合b中的根节点e的父节点改为节点a即可,也就是让一个集合的根节点是另一个结合的根节点。
(2)代码的实现
根据上面提到的查找某个节点和合并两个集合的思路,实现其代码:
数组下标 | 数据元素 | parent |
0 | a | -1 |
1 | b | 0 |
2 | c | 0 |
3 | d | 1 |
4 | e | 0 |
5 | f | 4 |
6 | g | 5 |
7 | h | 5 |
8 | ||
9 |
注:由于a节点没有父节点,所以将parent设置为-1,其他的节点同样将父节点都设置为数组下标,比如b节点的父节点为a,而a的数组下标为0.
#include<stdio.h>
#include<stdlib.h>
#define maxn 100
typedef int ElemType;
int parent[maxn];
//初始化
void InitParent(int n){
for(int i=0;i<n;i++){
parent[i]=-1;
}
}
//查找某个节点从属的集合
int find(int x){
while(parent[x]>=0){
x=parent[x];
}
return x;
}
//合并两个集合
void Union(int root1,int root2){
if(root1==root2){
return;
}
parent[root2]=root1;
}
(3)合并操作的优化
可是上面存在一个缺点就是,就是在合并的时候,查找的操作find的时间复杂度最坏的情况下为O(n),并且将n个元素进行多次的合并时间复杂度可能达到O(n^2),看下图:
注:可以看到两种合并方式导致树的高度不同,如果树增高太多的话,那么会导致后面查找的时候更加的耗时,为了尽量减小合并之后树的高度,应该将较矮的树合并到较高的树中,这样树的高度不至于太高。
优化之后的合并代码:使用根节点的绝对值表示树的节点数:
数组下标 | 数据元素 | parent |
0 | a | -4 |
1 | b | 0 |
2 | c | 0 |
3 | d | 1 |
4 | e | -4 |
5 | f | 4 |
6 | g | 5 |
7 | h | 5 |
8 | ||
9 |
a和e对应的parent=-4,表示| parent |=4为树的节点数。
//合并两个集合
void Union_1(int root1,int root2){
if(root1==root2){
return;
}
if(abs(parent[root2])<abs(parent[root1])){
parent[root1]+=parent[root2];
parent[root2]=root1;
}else{
parent[root2]+=parent[root1];
parent[root1]=root2;
}
}
注:改进之后的find查找时间复杂度最坏为O(log2n),将n个独立的元素为一个集合的合并操作最坏的时间复杂度为O(nlog2n).
(4)查找操作的优化
采用路径压缩的方式进行优化:首先找到根节点,再将查找根节点路径上所经过的所有节点都挂到根节点上,这样在查找根节点的时候更少的经过中间的路径,如下图所示:
数组下标 | 数据元素 | parent |
0 | a | 4 |
1 | b | 4 |
2 | c | 0 |
3 | d | 4 |
4 | e | -1 |
5 | f | 4 |
6 | g | 5 |
7 | h | 5 |
8 | ||
9 |
这样在查找节点b,d的时候可以直接查找到根节点e了。
//查找某个节点从属的集合
int find_1(int x){
int root=x;
//首先找到根节点
while(parent[x]>=0){
root=parent[root];
}
//如果x不能直接找到根节点,那么需要对其经过路径上的
//所有节点直接指向根节点进行路径的压缩
if(x!=root){
int t=parent[x];
parent[t]=root;
x=t;
}
return root;
}
注:改进之后的find查找时间复杂度为O(a(n)),将n个独立的元素为一个集合的合并操作的时间复杂度为O(n*a(n)).