1.堆的定义
![](https://i-blog.csdnimg.cn/blog_migrate/f5d57efad0615f49afe3d0942c63f44b.png)
//堆是一颗完全二叉树,堆一般由优先队列来实现
堆分为两种:
1.大顶堆中父亲结点的值大于或者等于孩子结点的值,以它为根结点的子树,它是最大值
(顶点是最大值,顶点指的是树的根结点或者子树的根结点)
2.小顶堆的父亲结点的值小于或者等于孩子结点的值,以它为根结点的子树,它是最小值
//不管是大顶堆和小顶堆都无法直接通过堆的结构比较出左右孩子的大小关系
即root->data >= root->lchild->data, root->data >= root->rchild->data
但无法直接得出root->lchild->data和root->rchild->data的大小关系
2.怎么建成一个堆
(1)表示一个堆
![](https://i-blog.csdnimg.cn/blog_migrate/657fe0445ce31581bbefbcb1cfe08bf0.png)
//堆是一颗完全二叉树,最简洁的方法就是使用数组进行表示(根结点下标为1,左孩子为2*X, 右孩子为2*X+1)
(2)如何调整一个二叉树,使其成为一个堆
![](https://i-blog.csdnimg.cn/blog_migrate/375a70de7217d20b1080421acc620ca0.png)
//先左右孩子进行比较,找出最大的再和要调整的父亲结点进行比较,如果大于就交换二者的值,交换后处理的结点的下标要改成原来被交换孩子的下标,逐层向下调整,直到该结点的值是其子树的最大值为止。
![](https://i-blog.csdnimg.cn/blog_migrate/bc6fbd1d871102a6a0ecdb347b7075f1.png)
代码:
void downAdjust(int low, int high) {//low为父亲结点,high为二叉树的边界;
int i = low, j = 2 * low;//i为要处理的结点,j为要处理结点的左孩子(也可以看出要进行对比的孩子);
while(j <= high) {
//先对比左右孩子;
if(j + 1 <= high) {
if(j + 1 <= high && heap[j + 1] > heap[j]) {//首先右孩子要存在,右孩子更大;
j = j + 1;
}
}
if(heap[j] > heap[i]) {//右孩子或者左孩子比父亲结点还大;
swap(heap[j], heap[i]);
i = j;//结构改变,更改下标;
j = i * 2;//记得更换左孩子,不能让j还指在上层;
} else {
break;//左右孩子都比处理结点小,则说明处理结点的位置是对了,可以退出了;
}
}
}
调整完全二叉树,使其成为一个大顶堆
![](https://i-blog.csdnimg.cn/blog_migrate/0e7b6f38fff17da5d0eda74886695f55.png)
void create(int heap[], int n) {
for(int i = n / 2; i >= 1; i--) {
downAdjust(n/2, n);
}
}
完全二叉树的叶子结点个数等于n/2(偶数等于n/2, 奇数等于n/2+1,因为这里不影响,n/2求出来的是最后一个非叶子结点的下标),下标1到n/2全部都是非叶子结点,n/2以后的都是叶子结点;
从下往上进行调整, 这样上一层就可以利用我下一层已经调整好的该子树的最大值进行比对,再接着往上推,直到遍历完堆的根结点。
完全二叉树->从最后一个非叶子结点向根结点进行调整->大/小顶堆
3.栈的基本操作:
(1)删除栈顶元素
![](https://i-blog.csdnimg.cn/blog_migrate/5583d85bf0e8860ba5e0682cec00868a.png)
void DeleteTop() {
heap[1] = heap[n--];//用最后一个元素覆盖,并让元素个数减1;
downAdjust(1, n);//从根结点从下调整;
}
(2)插入元素
插入元素是先把它放到堆的最后面,然后再往上比较,如果比父亲结点小了就不用动了,就留在哪里
即插入元素是要向上调整,删除元素是向下调整,直到遇到自己的位置而停下来。
向上调整和向下调整的代码差不多,向下调整比向上调整简单,顶上的元素被换下来肯定比原来的元素要大的,所以被换下来的元素不需要调整。
代码:
void upAdjust(int low, int high) {//low一般是1,根结点,high是插入结点的下标;
int i = high, j = n/2;//j是i的父亲结点, i不管是左孩子还是右孩子算出来都是其父结点;
while(j >= low) {
if(heap[i] > heap[j]) {//不想向下调整这么麻烦,一定有父结点;
swap(heap[i], heap[j]);
i = j;
j = i / 2;
} else {
break;
}
}
}
void insert(int x) {
heap[++n] = x;
upAdjust(1, n);
}
4.堆排序
![](https://i-blog.csdnimg.cn/blog_migrate/d548f77742fa73876f76dbaef412072d.png)
//实际上就是一个不断删除堆顶元素,并将堆顶元素放到堆底,从此做到从小到大排序的过程
![](https://i-blog.csdnimg.cn/blog_migrate/0627009dce443855fdf2dedb2d4203a6.png)
//也就是还是使用heap这个数组,让堆顶元素和堆底元素交换,再向下调整,注意这里相当于把栈顶元素给删了,不算在堆中因此向下调整的范围是[1, i-1];
堆排序的代码:
void heapSort() {
for(int i = n; i > 1; i--) {//删除堆顶元素的次数,只剩一个元素的时候,该元素是小的,不需要删除;
//最后一个大顶堆就变成按序号从小到达排序的序列;
swap(heap[i], heap[1]);//将堆顶元素换下来;
downAdjust(1, i-1);//记得是i-1;//已经不包含最后一个元素了,因为已经换上去了;
}
}
复习堆的一个方法就是从头到尾建立一个堆。
数组为:
int heap[Maxn] = {0, 85, 55, 82, 57, 68, 92, 99, 98, 66, 56};
输出为:(层序遍历,完全二叉树按序号输出就是层序遍历)//没有插入前的层序遍历。
99 98 92 66 68 85 82 57 55 56
//删除堆顶后的层序遍历:
98 68 92 66 56 85 82 57 55
//删除堆顶后,插入元素100后的层序遍历:
100 98 92 66 68 85 82 57 55 56
全部代码:
#include<iostream>
#include<queue>
using namespace std;
const int Maxn = 10010;
int heap[Maxn] = {0, 85, 55, 82, 57, 68, 92, 99, 98, 66, 56};
int n = 10;
void downAdjust(int low, int high) {//low为父亲结点,high为二叉树的边界;
int i = low, j = i * 2;//i为要处理的结点,j为要处理结点的左孩子(也可以看出要进行对比的孩子);
while(j <= high) {
//先对比左右孩子;
if(j + 1 <= high && heap[j + 1] > heap[j]) {//首先右孩子要存在,右孩子更大;
j = j + 1;
}
if(heap[j] > heap[i]) {//右孩子或者左孩子比父亲结点还大;
swap(heap[j], heap[i]);
i = j;//结构改变,更改下标;
j = i * 2;//记得更换左孩子,不能让j还指在上层;
} else {
break;//左右孩子都比处理结点小,则说明处理结点的位置是对了,可以退出了;
}
}
}
void create(int heap[], int n) {
for(int i = n / 2; i >= 1; i--) {
downAdjust(i, n);//n/2是第一个非叶子结点的下标,一直到根结点的遍历。
}
}
void DeleteTop() {
heap[1] = heap[n--];//用最后一个元素覆盖,并让元素个数减1;
downAdjust(1, n);//从根结点从下调整;
}
void upAdjust(int low, int high) {//low一般是1,根结点,high是插入结点的下标;
int i = high, j = n/2;//j是i的父亲结点, i不管是左孩子还是右孩子算出来都是其父结点;
while(j >= low) {
if(heap[i] > heap[j]) {//不想向下调整这么麻烦,一定有父结点;
swap(heap[i], heap[j]);
i = j;
j = i / 2;
} else {
break;
}
}
}
void insert(int x) {
heap[++n] = x;
upAdjust(1, n);
}
void heapSort() {
for(int i = n; i > 1; i--) {//删除堆顶元素的次数,只剩一个元素的时候,该元素是小的,不需要删除;
//最后一个大顶堆就变成按序号从小到达排序的序列;
swap(heap[i], heap[1]);//将堆顶元素换下来;
downAdjust(1, i-1);//记得是i-1;//已经不包含最后一个元素了,因为已经换上去了;
}
}
void print() {
for(int i = 1; i <= n; i++) {
if(i != 1)
cout << " ";
cout << heap[i];
}
cout << "\n";
}
int main() {
create(heap, n);
print();
DeleteTop();
print();
insert(100);
print();
heapSort();
print();
return 0;
}