堆的教学(原理,实现,应用)

堆数据结构详解
本文深入讲解堆这种数据结构,特别是完全二叉树形式的最小堆和最大堆的特点及应用。探讨了如何通过调整节点位置来维护堆的特性,并提供了具体的代码实现。此外,还介绍了两种建立最小堆的方法及其时间复杂度。

堆:和队列还有栈,一样是一种基础的数据结构。但是栈和队列是线性的数据结构。而堆是一棵树,并且是一个完全二叉树,不仅如此。这颗二叉树还有一些小特殊。举一个例子把。

堆(特殊的完全二叉树):观察这棵树,我们会发现所有的父节点都比子节点要小。符合这样的特点的完全二叉树我们称之为最小堆。反之。如果所有父节点都比子节点要大,这样的二叉树我们称之为最大堆。

知道了一些基本概念。我们来考虑一些问题。

最小堆的堆顶是不是一个这些数字的最小值。很明显是的。而且最小堆的各个子树也是最小堆。因为每个子节点都没父亲节点大,所以堆顶一定是最小值。

现在在考虑一个问题。如何维护这个最小堆呢。

那我们想象一下一个操作:把这些数字中最小数字的删除。然后加入一个值23。在求出最小值。

若用遍历的方法的话。从头到尾遍历一边删除最小的。然后加入23再遍历一边。n方的复杂度。

那用堆的话我们只需要把堆顶元素删除,然后把23放到堆顶。然后不断让23和自己子节点比较大小。若比子节点大和较小的子节点交换位置。就可以了。看一下过程

我们会发现这些竟然只用3步操作。比起n²的复杂的要快太多了!!!。顺带提一下这个是logn的时间复杂度。

说了这么多,再看一下代码实现。

void siftdown(int i){
	int t,flag = 0;
	while(i * 2 <= n && flag == 0){
		if(h[i] > h[i*2])
			t = i*2;
		else 
			t= i;
		if(i*2+1 <= n)
			if(h[t] > h[2*i+1])
				t =2*i+1;
		if(i != t){
			swap(t,i);
			i = t;
		}
		else 
			flag = 1;
	}
	return ;
}

代码也很短,也很好理解,就不解释。

在考虑一种操作:我们向最小堆加入一个元素3,该如何实现。这个和上一部的操作有点不同。这次没有要删除最小的数字。这不就没办法从放入堆顶了么?该怎么办?

放不了堆顶我们放到最后最后不就行了,然后和自己位置的父节点比较,若比父节点大就交换值。(思考一下为什么不用和自己的兄弟节点比较。因为父亲节点一定比子节点大,若你比父亲节点大,那你一定比兄弟节点大,是不是?)。这里就不画图描述过程了。

看一下代码。

void siftup(int i){
	int flag = 0;
	if(i == 1)
	while(i != 1 && flag == 0){
		if(h[i] < h[i/2])
			swap(i,i/2);
		else
			flag = 1;
		i = i / 2;
	}
	return ;
}


代码一个比一个短,是不是特别简单。

知道了如何维护最小堆了。我们还有一个重要的问题,就是如何建堆。

这时候,比较机智的同学会说。那我们依次把要压进最小堆的数,放到最后,然后通过之前的siftup函数向上调整。

完全O98K!这是一种可行的方法。

那我们看一下代码实现过程:

int n = 0;
for(int i = 1 ; i <= m ; i ++){
	n ++;
	cin >> x;
	h[n] = x;
	siftup(n);
}

简短。

分析一下这个的复杂度,是nlogn的。

那有么有更简单的一点的呢。有!

我们直接把数字存入一个一维数组(我们二叉树的就是用数组模拟的)。然后我们从最后一个节点开始,依次判断以这个节点的为根的子树是不是最小堆。然后若所有的子树都是最小堆。那么整棵树就是最小堆了。

这个是有点类似于贪心的思想。一点点递推。

不懂没关系看下代码:

void creat(){
	for(int i = n/2 ; i >= 1 ; i--)
		siftdown(i);
	return ;
}


是不是特别惊喜,特别意外!!,这代码竟然一个比一个短!!!。

两种方法二选一就行了,不过后者的代码的时间复杂度是n,线性的时间,就很快,也更酷!

现在再看一下,能解决那些问题

因为最小堆的能维护最小值。所以堆排序是完全可行的,看代码把:

#include<cstdio>
int h[101];
int n;
void swap(int x,int y){
	int t;
	t = h[x];
	h[x] = h[y];
	h[y] = t;
	return ;
}
void siftdown(int i){
	int t,flag = 0;
	while(i * 2 <= n && flag == 0){
		if(h[i] > h[i*2])
			t = i*2;
		else 
			t= i;
		if(i*2+1 <= n)
			if(h[t] > h[2*i+1])
				t =2*i+1;
		if(i != t){
			swap(t,i);
			i = t;
		}
		else 
			flag = 1;
	}
	return ;
}
void creat(){
	for(int i = n/2 ; i >= 1 ; i--)
		siftdown(i);
	return ;
}
int deletemax(){
	int t;
	t = h[1];
	h[1] = h[n];
	n--;
	siftdown(1);
	return t;
}
int main(){
	int i,num;
	scanf("%d",&num);
	for(int i = 1; i <= num ; i ++)
		scanf("%d",&h[i]);
	n = num ;
	creat();
	for(int i = 1; i <= num ; i ++)
		printf("%d ",deletemax());
	return 0;
}

最后说一些,堆实现起来比较麻烦。虽然代码短,但是也麻烦,大家也可以学习一下STL优先队列的实现。

以上教学出自博主对哈啊算法上堆教学的总结。若有不对可以私聊博主。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值