堆和堆排序

这两天突然想到了堆排序,然后就写了一下,并花点时间整理记录。

堆说白了就是一个二叉树,不过不是普通的二叉树,堆是一个完全二叉树。二叉树有普通的二叉树,完全二叉树和满二叉树。

完全二叉树:除最后一层外,所有的层数即是满节点。层间节点和层(除最后一层)之间存在:N = 2^{l-1}l 是层数。

满二叉树:除叶子节点外,所有的节点都是满子节点,即2个子节点。国内也有说是必须节点数达成当前层数最大节点数的,即是2^l-1,这里以国外教材为准(反正我们教的就是这样)。

继续来说完全二叉树,可知节点间具有以下关系:对于节点pos,左子节点为:2*pos+1;右子节点为:2*pos+2。

堆通常分为最大堆和最小堆,最大堆任意父节点都比他的子节点要大,相对的最小堆即是,任意的父节点都比他的子节点要小。对堆得操作有:建堆元素插入元素删除

建堆:给定一个无序的数组,将数组以堆的形式从新组织。

元素插入/删除:因为元素的插入/删除会影响堆的结构,所以需要进行堆的维护

下面以建立最大堆来进行分析。给定数组,建立完全二叉树

{2, 5, 3, 9, 0, 4, 6, 1, 7, 8}

        显然这不是一个最大堆,因为每个父节点都需要比子节点大。像0,5,2,3(这里只节点存储的数值,下同)这些,都需要下沉。建堆的过程,其实就是小节点下沉的过程,下沉当然从非叶子节点开始(叶子节点没法沉了)。

        节点下沉:因为是最大堆,所以需要节点和左右子节点的最大值比较(如果是最小堆则是和最小值比较)。如果父节点比子节点最大值大,则符合要求,不需要下沉。否则需要和最大值交换位置。从0开始,和8交换位置;9不需要动;以5的下沉为例:

        这里跳过了3(也就是2号节点)的下沉,实际是写代码的时候,是从第一个非叶子节点(倒数)往前依次下沉的过程。这样所有的非叶子节点都下沉完之后,我们的最大堆也就建成了。

#include <iostream>
#include <vector>
using namespace std;

void buildHeap(int *, int);        //建堆
void downadj(int *, int, int);    //节点下沉
int heapFirst(int *, int);        //删除堆顶节点,并返回

void printArray(int *array, int len)
{
	for(int i=0;i<len;i++)
		cout <<array[i] <<" ";
	cout <<endl;
}


int main()
{
	int array[10] = {2, 5, 3, 9, 0, 4, 6, 1, 7, 8};
	int len = 10;
	cout <<"original: ";
	printArray(array, len);
	// // downadj(array, 4, len);
	// // printArray(array, len);

	// downadj(array, 3, len);
	buildHeap(array, len);

	cout <<"\nbuild heap: ";
	printArray(array, len);

	return 0;
}

void buildHeap(int *array, int len)
{
	int pos = len - 1;
	int par = (pos - 1)/2;
	for(int i=par;i>=0;i--){
		cout <<"I am in" <<" pos is:" <<i <<endl;
		downadj(array, i, len);
	}
}

void downadj(int *array, int pos, int len)
{
	int tem = array[pos];
	int childleft = 2*pos + 1;
	int childright = 2*pos + 2;
	while(childleft < len){
		if(childright < len && tem < array[childright] && array[childleft] < array[childright]){
			array[pos] = array[childright];
			pos = childright;
		} else if(tem < array[childleft]){
			array[pos] = array[childleft];
			pos = childleft;
		} else {
			break;
		}
		childleft = 2*pos + 1;
		childright = 2*pos + 2;
	}
	array[pos] = tem;
}

结果如下:

       建成最大堆离我们的堆排序也就是差不多是完成了,既然最大堆的根节点是最大元素,那每次取最大堆的堆顶,然后删除根节点,进行最大堆的维护。那依次取出的不就是从大到小排序的元素吗?删除根节点,堆元素减1,所以节点数做响应的减少,最简单的方法就是直接将最后一个节点(叶节点)和根节点进行交换,然后做根节点(由最后的叶节点交换而来)的下沉

懒得分析了,有时间再补充吧。看添加代码:

// 在上面代码基础上添加
int main()
{
	int array[10] = {2, 5, 3, 9, 0, 4, 6, 1, 7, 8};
	int len = 10;
	cout <<"original: ";
	printArray(array, len);
	// // downadj(array, 4, len);
	// // printArray(array, len);

	// downadj(array, 3, len);
	buildHeap(array, len);

	cout <<"\nbuild heap: ";
	printArray(array, len);


	// get the maxMember one by one
	cout <<"\n\nheap sort print: ";
	int tem;
	while(len){
		tem = heapFirst(array, len);
		cout <<tem <<" ";
		len--;
	}
	cout <<endl;
	return 0;
}

int heapFirst(int *array, int len)
{
	if(len == 0) return -1;
	if(len == 1) return array[0];
	int res = array[0]; // return the heap header
	array[0] = array[len-1];	// swap the first with the last
	array[len-1] = res;
	len--;
	downadj(array, 0, len);		// down the 

	return res;
}

结果如下:

总结:所以堆排序就是几个操作,建堆,堆顶数据的弹出(堆删除),堆的维护。这几个操作会写了,堆也就会了。然后建堆过程简单的总结就是一个非叶子节点的下沉过程,堆删除就是堆顶和末元素交换,然后堆顶下沉的过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值