堆得简单介绍以及堆排序

首先看一下堆的定义:

对于n个元素的序列{k1,k2,k3,……,kn},当且仅当满足下列关系时,称之为堆:

K(i) <= K(2*i) && K(i) <= K(2*i+1)      此时的堆为小顶堆

K(i) >= K(2*i) && K(i) >= K(2*i+1)      此时的堆为大顶堆

(i = 1,2,……,n/2(下取整))

注意:堆得存储是用一维数组来存储的。

若将堆对应的序列看成是一个完全二叉树,则堆得含义表明:

完全二叉树中所有非终端结点的值均不大于(或不小于)其左右孩子结点的值。

因此,若序列{K1,K2,……,Kn} 是大顶堆,则堆顶元素必为序列中n个元素的最大值;反之,若序列是小顶堆,则堆顶元素必为序列中n个元素的最小值。

堆排序就是利用的这个性质。

堆排序的过程如下:

假设要从小到大排序,我们构建一个大顶堆,则堆顶元素是最大值。将堆顶元素和最后一个元素互换,则最后一个元素变成了n个元素中的最大值。之后再将剩下的 n-1 个元素调整成为大顶堆,将堆顶元素和第n-1 个元素互换,则第n-1 个元素变成了n个元素中的次大值……循环这个过程,不断调整堆,最后得到一个有序的序列。

在上面堆排序的过程中,有两个问题需要解决:

(1)如何将一个初始的序列构建成一个大顶堆?

(2)再得到最大元素后,剩下的n-1个元素如何再次调整成为一个大顶堆?

实际上,初始序列构建大顶堆也是一个不断调整堆得过程。因此,只要解决第二个问题就可以。

下图是一个大顶堆:

 

当把堆顶元素20和最后一个元素互换之后,最后一个元素变成了序列中的最大值。如下图:

 

但是,此时堆顶元素违反了大顶堆的性质,堆顶元素的左右孩子仍旧满足大顶堆的性质。因此,此时需要对堆进行调整。因为左子树的值大于右子树的值,所以将3和17互换,如下图:

此时,左子树又违反了大顶堆得性质,所以需要调整左子树,如下图:

至此,一次调整完毕,堆顶元素成为了次大元素。

实际上,调整堆就是这样一个不断筛选比较的过程,不断的和左右子树比较,一直到不需要交换为止。

将一个无序序列构建成一个大顶堆的过程就是一个反复筛选的过程。将此序列看成是一个完全二叉树,则最后一个非叶子节点是第 n/2(下取整)个元素,因此,筛选只需从第 n/2(下取整)个元素开始。

假设有序列:{49,38,65,97,76,13,27},初始二叉树是:

 

从第3个元素,也就是65开始调整堆,65大于左右子树的值,因此不需要调整。

然后是第2个元素,也就是从38开始调整堆,38和左右子树比较,将97和38互换,调整后如下图:

 

然后是第1个元素,也就是从49开始调整堆,49和左右子树比较,将97和49互换,互换之后,因为49破坏了左子树大顶堆的性质,因此需要继续调整,将49和左右子树比较,然后将49和76互换,调整过程如下图:

 

至此,将一个无序的序列调整成为了一个大顶堆。

同理,堆排序也分为两个过程:(1)将初始化序列调整成为一个大顶堆(2)用最后一个元素和堆顶元素交换,然后不断调整剩下的元素成为一个新的大顶堆。

代码如下:

 

const int N = 8;
int num[N] = {-1,49,38,65,97,76,13,27};  //从第一个元素开始存储

//调整堆的函数
void heapAdjust(int pos,int total){
	int temp = num[pos];
	for(int j = 2 * pos; j <= total; j *= 2){
		if(j < total){  //说明还有右子树
			if(num[j] < num[j + 1]){   //筛选出左右子树中较大的值
				j += 1;
			}
		}
		if(temp >= num[j])  //不需要再继续向下调整了
			break;
		num[pos] = num[j];
		pos = j;
	}
	num[pos] = temp;
}

void heapSort(){
	//首先将数组构建成一个大顶堆
	for(int i = (N-1) / 2;i >=1;--i){
		heapAdjust(i,N - 1);
	}
	//开始堆排序
	for(int i = N-1; i > 1; --i){
		int temp = num[i];   //交换第一个元素和最后一个元素
		num[i] = num[1];
		num[1] = temp;
		heapAdjust(1,i - 1);  //交换完之后,重新调整堆
	}
}

 

  

 

转载于:https://www.cnblogs.com/acBool/p/4842151.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值