基本定义

​ 堆是一个完全二叉树,可以用数组来映射这个树。
​ 建立堆就是根据一个已知的未排序的数组,根据建堆的规则,将数组中的元素按照一定规则重新排列而已

堆的分类

​ 大根堆:根结点是最大的元素,每个结点都大于等于其左右孩子结点
​ 小根堆:根结点是最小的元素,每个结点都小于等于其左右孩子结点

堆的特点

​ 由于堆的第一个元素总是最大/最小的,所以每次取堆的第一个元素就能得到整个堆中的最值,并且!每次取完这个最值后,[堆可以进行调整,保持第一个元素是堆的最值],就是优先队列!

堆的建立

​ 就是不断调整/筛选的过程

  • 调整/筛选:

    ​ 要调整结点x,就把结点x和其左右结点中最大的孩子交换,对于结点x和其左右结点构成的树而言,就成了一个堆。
    ​ 从最后一个有分支的结点开始调整,直到根节点也调整了,就ok了。为什么这样就ok?这其实是一个分治的思想,要想在一个树上建立一个堆,首先要把树的最底下的子树建立成堆,然后上面再执行同样的操作,整体就成堆了。
    ​ 比如a,b,c, b,c是a的子树, b,c成堆后,b,c就是其堆的最值,这样再跟a的根节点较量一番,a的根节点更新后,整体就成堆了。

1
int heap[MAXN+1];//MAXN为堆元素个数,从heap[1]开始存元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//给出要调整元素的位置low和最后一个元素的位置high,将该元素向下调整到相应的位置,这里如果跟顶点小于子节点,则和子节点交换
void downAdjust(int low,int high)
{
int i=low,j=i*2;
while(j<=high)//只要左子节点还没超过界限,表明还能继续下调
{
//让j指向左右节点中较大的那一个
if(j+1<=high&&heap[j+1]>heap[j])
j++;
//如果该较大的子节点大于根节点
if(heap[j]>heap[i])
{
//根节点和子节点交换
swap(heap[low],heap[j]);//swap在头文件algorithm里
//i指向子节点原来的位置,继续下调
i=j;
j=i*2;
}
else//左右子节点都没有根节点大,不能继续下调了,直接结束
{
break;
}
}
}
1
2
3
4
5
6
7
8
9
//根据数组,建立堆,n为堆的元素个数
void createHeap(int n)
{
//从最后一个非叶节点到根节点,依次向下调整,想想画面
for(int i=n/2;i>=1;i--)
{
downAdjust(i,n);
}
}

这里注意heap[]数组从下标1开始存放数据,这是为了在处理完全二叉树的时候方便地索引左右子节点。

完全二叉树的最后一个非叶子结点的位置为n/2向下取整

堆排序

​ 拿大根堆来说,堆排序的实质就是把当前最大的元素(堆顶)和堆中最后一个元素交换,此时堆中的最大值排到了heap数组的最后,此时在[1,high-1]的范围内把现在堆顶(已交换)向下调整,调整完后,在[1,high-1]范围内又形成了一个大根堆,之后重复交换、调整的步骤,直到堆只有1个元素就行了。结束后,heap就会变成一个从小到大排列的数组。

1
2
3
4
5
6
7
8
9
void heapSort()
{
createHeap();//先建立一个堆
for(int i=n;i>=1;i--)
{
swap(heap[1],heap[i]);//堆顶和堆尾互换
downAdjust(1,i-1);//在[1,i-1]范围内将其重新调整为堆
}
}