数据结构——树(五)【并查集、堆】

并查集

并查集的定义

  并查集是一种维护集合的数据结构,支持合并(合并两个元素)和查找(判断两个元素是否在同一集合)的操作。

  并查集使用一个数组来实现的:

	int father[N];

  其中father[i]表示元素i的父亲结点,而父亲结点本身也是这个集合内的元素(1<=i<=N)。例如father[1] = 2表示元素1的父亲结点是元素2,以这种父系关系来表示元素所属的集合。另外如果father[i] = i,说明元素i是该集合的根节点,但对同一集合来说只存在一个根节点,且将其作为所属集合的标识

并查集的基本操作

初始化

  一开始,每个元素都是独立的一个集合,因此令所有的father[i] = i:

for(int i =1;i<=N;i++){
	father[i] = i;
}

查找

  由于规定同一个集合只能有一个根节点,因此查找操作就是对给定的结点寻找其根节点的过程。即反复寻找父亲结点,直到找到根节点(father[i] ==i)。

//findFather函数返回元素x所在集合的根节点
int findFather(int x){
	if(father[x] == x) return x;
	else return findFather(father[x]);
}

合并

  先判断两个元素是否属于同一个集合,只有当两个元素属于不同集合时才合并,而合并的过程一般是把其中一个集合的根结点的父亲指向另一个集合的根结点。

  1. 对于给定的两个元素a,b,判断两个元素是否属于同一集合。调用上述函数,对两个元素分别查找根结点,然后再判断两个根结点是否相同。
  2. 合并两个集合:在1中已经求出了ab的根结点faA和faB,只要将father[faA] = faB或者反过来都可。
void union(int a,int b){
	int faA = findFather(a);
	int faB = findFather(b);
	if(faA != faB){
		father[faA] = faB;
	}
}

堆的定义与基本操作

  堆是一棵完全二叉树,树中每个结点的值都不小于其左右孩子结点的值。如果父亲结点的值大于或等于孩子结点的值,那么称这样的堆为大顶堆,这时每个结点的值都是以它为根结点的子树的最大值;如果父亲结点的值小于或等于孩子结点的值,那么称这样的堆为小顶堆,这时每个结点的值都是以它为根结点的子树的最小值。(以下以大顶堆为例)

  使用简便的数组来存放完全二叉树,结点按层序存储于数组中,其中第一个结点将存储于数组的1号位,并且数组i号位表示的结点的左孩子就是2i号位,右孩子则是(2i+1)号位。于是用如下数组定义来表示堆:

const int maxn = 100;
//heap为堆,n为元素个数
int heap[maxn],n =10;

  建堆的调整都是从上往下调整,向下调整:总是将当前结点V与它的左右孩子比较(如果有的话),假如孩子中存在权值比结点V大的,就将其中权值最大的那个孩子结点与结点V交换;交换完毕后继续让结点V和孩子比较,直到结点V的孩子的权值都比结点V小,或是结点V不存在孩子结点。

向下调整

  向下调整的代码:

//对heap数组在[low,high]范围内进行向下调整
//其中low为欲调整结点的数组下标,high一般为堆的最后一个元素的数组下标
void downAdjust(int low,int high){
	int i = low,j = i*2;//i为欲调整结点,j为其左孩子
	while(j<=high){//存在孩子结点
		//如果右孩子存在,且右孩子的值大于左孩子
		if(j+1<=high && heap[j+1]>heap[j]){
			j = j+1;//让j储存右孩子下标
		}
		//如果孩子中最大的权值比欲调整结点i大
		if(heap[j]>heap[i]){
			swap(heap[j],heap[i]);//交换最大权值的孩子与欲调整结点i
			i = j;//保持i为欲调整结点 j为i的左孩子
			j = 2*i;
		}else{
			break;//孩子结点的权值均比欲调整结点i小,调整结束
		}
	}
}

建堆

  建堆过程就为从[n//2]开始倒着枚举结点(因为叶子结点个数为[n//2]),对每个结点进行[i,n]的调整,保证每个结点都是以其为根节点的子树中的权值最大的结点
  建堆的代码:

//建堆
void ceateHeap(){
	for(int i = n/2;i>=1;i--){
		downAdjust(i,n);
	}
}

删除堆顶元素

  如果要删除堆中最大的元素(删除堆顶元素),并让其仍然保持堆的结构,那么只需要最后一个元素覆盖堆顶元素,然后对根节点进行调整即可:

//删除堆顶元素
void deleteTop(){
	heap[1] = heap[n--];//用最后一个元素覆盖堆顶元素,并让元素个数减1
	downAdjust(1,n);//向下调整堆顶元素
}

向上调整

  如果要往堆里添加一个元素,可以把想要添加的元素放在数组的最后(也就是完全二叉树的最后一个结点的后面),然后进行向上调整操作。向上调整总是把欲调整结点与父亲结点比较,如果权值比父亲结点大,那么就交换其与父亲结点,直到到达堆顶或是父亲结点的权值较大位置。向上调整的代码:

void upAdjust(int low,int high){
	int i = high,j = i/2;
	while(j>=low){
		if(heap[j]<heap[i]){
			swap(heap[j],heap[i]);
			i = j;
			j = i/2;
		}else{
			break;
		}
	}
}

添加元素

  添加元素:

void insert(int x){
	heap[++n] = x;//让元素个数+1,然后将数组末尾赋为x
	upAdjust(1,n);
}

堆排序

  讨论递增排序的情况。
  因为堆顶元素最大,堆排序的直观思路就是取出堆顶元素,将堆的最后一个元素替换至堆顶,在进行一次针对堆顶元素的向下调整,重复直到堆中只有一个元素。(每次将最大元素移至堆尾固定)
  为了节省空间,可以倒着遍历数组,假设当前访问到第i号位置,那么将堆顶元素与i号位的元素进行交换,接着在[1,i-1]范围内对堆顶元素进行一次向下调整即可。

//堆排序
void heapSort(){
	createHeap();
	for(int i =n;i>=1;i--){//倒着枚举
		swap(heap[i],heap[1]);//交换heap[i]和堆顶
		downAdjust(1,i-1);//调整堆顶
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值