【1】堆排序算法思想
堆是具有以下性质的完全二叉树
- 我们假设有一棵完全二叉树,在满足作为完全二叉树的基础上,对于任意一个拥有父节点的子节点,其数值均不小于父节点的值;这样层层递推,就是根节点的值最小,这样的树,称为小根堆。
- 同理,又有一棵完全二叉树,对于任意一个子节点来说,均不大于其父节点的值,如此递推,就是根节点的值是最大的,这样的树,称为大根堆。
我们接下来看一下如下这个数组的排序思路
数组{20,30,90,40,70,110,60,10,100,50,80} n=11
初始化堆
在堆排序算法中,首先要将待排序的数组转化成二叉堆。
下面演示将数组{20,30,90,40,70,110,60,10,100,50,80}转换为最大堆{110,100,90,40,80,20,60,10,30,50,70}的步骤。
【1】i=4时
为什么要从i=11/2-1=4开始呢?我们发现i为4,3,2,1,0的时候他们都是有孩子的结点。
a[4]左孩子是a[9],a[4]右孩子是a[10],调整时选择左右孩子中较大的一个孩子进行比较如果a[4]小则交换,否则不交换。上边例子中a[4]小于较大的a[10]=80,所以交换a[4]和a[10]
【2】i=3时
a[3]左孩子是a[7],a[3]右孩子是a[8],调整时选择左右孩子中较大的一个孩子a[8]=100进行比较如果a[4]小则交换,否则不交换。上边例子中a[3]=40小于较大的孩子a[8]=100,所以交换a[3]和a[8]
【3】i=2时
a[2]左孩子是a[5],a[2]右孩子是a[6],调整时选择左右孩子中较大的一个孩子a[5]=110进行比较如果a[2]小则交换,否则不交换。上边例子中a[2]=90小于较大的孩子a[5]=110,所以交换a[2]和a[5]
【4】i=1时
a[1]左孩子是a[3],a[1]右孩子是a[4],调整时选择左右孩子中较大的一个孩子a[3]=100进行比较如果a[1]小则交换,否则不交换。上边例子中a[1]=30小于较大的孩子a[3]=100,所以交换a[1]和a[3],交换完之后a[3]=30小于他的右孩子a[8]=40,所以继续交换完成。
【5】i=0时
a[0]左孩子是a[1],a[0]右孩子是a[2],调整时选择左右孩子中较大的一个孩子a[2]=110进行比较如果a[0]小则交换,否则不交换。上边例子中a[0]=20小于较大的孩子a[2]=110,所以交换a[0]和a[2],交换完成之后a[2]又小于他的较大孩子a[6]=90,则继续交换才能完成。
交换数据进行排序
调整完毕,就得到了最大堆。此时,数组{20,30,90,40,70,110,60,10,100,50,80}也就变成了{110,100,90,40,80,20,60,10,30,50,70}。
上面是当n=10时,交换数据的示意图。
我们知道大根堆的根节点a[0]元素永远是最大的
当n=10时,首先交换a[0]和a[10],使得a[10]是a[0]到[10]之间的最大值;然后,调整a[0]到a[9]使它称为最大堆。交换之后:a[10]是有序的!
当n=9时, 首先交换a[0]和a[9],使得a[9]是a[0]到[9]之间的最大值;然后,调整a[0]到[8]使它称为最大堆。交换之后:a[9]到[10]是有序的!
…
依此类推,直到a[0…10]是有序的。
【2】代码示例
//============================递归==================================
#include<iostream>
#include<vector>
using namespace std;
void adjust(vector<int>&vec, int len, int index) //调整函数
{
int left = 2 * index + 1; //左孩子
int right = 2 * index +