1.堆的定义
//堆是一颗完全二叉树,堆一般由优先队列来实现
堆分为两种:
1.大顶堆中父亲结点的值大于或者等于孩子结点的值,以它为根结点的子树,它是最大值
(顶点是最大值,顶点指的是树的根结点或者子树的根结点)
2.小顶堆的父亲结点的值小于或者等于孩子结点的值,以它为根结点的子树,它是最小值
//不管是大顶堆和小顶堆都无法直接通过堆的结构比较出左右孩子的大小关系
即root->data >= root->lchild->data, root->data >= root->rchild->data
但无法直接得出root->lchild->data和root->rchild->data的大小关系
怎么建成一个堆
(1)表示一个堆
//堆是一颗完全二叉树,最简洁的方法就是使用数组进行表示(根结点下标为1,左孩子为2*X, 右孩子为2*X+1)
(2)如何调整一个二叉树,使其成为一个堆
//先左右孩子进行比较,找出最大的再和要调整的父亲结点进行比较,如果大于就交换二者的值,交换后处理的结点的下标要改成原来被交换孩子的下标,逐层向下调整,直到该结点的值是其子树的最大值为止。
代码:
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;//左右孩子都比处理结点小,则说明处理结点的位置是对了,可以退出了;
}
}
}
调整完全二叉树,使其成为一个大顶堆
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以后的都是叶子结点;
从下往上进行调整, 这样上一层就可以利用我下一层已经调整好的该子树的最大值进行比对,再接着往上推,直到遍历完堆的根结点。
完全二叉树->从最后一个非叶子结点向根结点进行调整->大/小顶堆
栈的基本操作:
(1)删除栈顶元素
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);
}
全部代码:
#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);
}
}
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);
}
int main() {
create(heap, n);
for(int i = 1; i <= n; i++)
cout << heap[i] << " ";
cout <<"\n";
insert(100);
for(int i = 1; i <= n; i++)
cout << heap[i] << " ";
cout <<"\n";
return 0;
}