最大堆是我在看浙大陈越老师的数据结构视频学到的,用的数组实现的
如何描述堆呢?用我自己的话说就是:
根是这颗树最大的值,每个 根节点都比 左右子节点的值大, 对左右子树仍然成立;
那么最小堆呢正好相反:
根是这颗树的最小的值, 每个 根节点都比左右子节点的值小, 同样对左右子树成立;
图能最直观的体现:
我们很容易看出来,堆的左右子树仍然是堆。
(图摘自CSDN)所以无论对于最大堆,最小堆,同一层之间的大小顺序是不确定的
那么现在说,如何建立一颗这样的堆呢?
1、在说堆之前相必大家都了解完全二叉树,以及数组下标(从1开始)和完全二叉树节点位置的技巧(即假如说根节点是i,那么它的左儿子=2*i, 右儿子=2*i+1),堆就是利用完全二叉树建立的,这里不理解没关系,因为答案在下边。
现在,我们说假如说有这么一棵树:
假如说现在在右下角插入100,那么,很明显就不符合堆的概念了,怎么调整呢?
- 如果 它比根节点大,就让它和根节点调换位置
- 那么这颗树如果是上面大根堆里面的一颗子树呢?继续进行:
如果 它比根节点大,就让它和根节点调换位置
直到调换到整棵树 根的位置,或者现在已经比根节点要小了
(已经满足堆的概念了)
当然最小堆道理是相同的
以上是 当前一颗最大堆,如何往里面插值,那么现在我最想知道的是如何构建一个最大堆,你跟我扯插入扯这么半天干什么呢?那么如果咱们是把一组数据往一个空堆里面插呢?哈哈哈
2、咱们继续往下说:
如果现在给你一个最大堆:
删除堆的最大值:你怎么办呢?
把堆顶删了(也就是根),为了保证他还是一颗完全二叉树,就把最下面一层最右边一个放到堆顶。
为了要让它还满足堆的要求,那么就跟它的左右子节点中最大的交换值(就是说现在80在树顶,10为左儿子)
现在目标转移到左子树即:
重复操作。
也就是实现了数据的删操作
那么我又是在扯皮了,建树呢?建树呢?!建树呢?!!!
3、现在正式来说一下建树的问题(。。。你终于说到建树了):
现在给你一组数据,如果你直接放到数组里面是这个样子(从下标1开始):
根据数组下标与完全二叉树节点的关系,构成的二叉树是这个样子:
我们知道删除堆顶时把最下面的值覆盖堆顶元素,经过操作让能成为堆,是因为它的左右子树仍然是堆。
那么我们现在对26这个节点做如下调整:
~~1、如果 它比左右子节点的最大值小, 就跟较大子节点交换值
~~2、目标移到交换的子节点,继续~~1
~~3、否则满足堆的概念跳出循环。
然后对6节点做上面的~~ 1 ~~2 ~~3 调整,
然后对15节点做~~ 1 ~~2 ~~3 调整。。。(可自行画一个更大的堆模拟一下这三步,就更清晰了)
最后就行成如下堆:
over,以下是代码实现:
#include<cstdio>
#include<iostream>
#include<cstdlib>
#define MAXDATA 100000000
#define MAXSIZE 10005
using namespace std;
typedef struct HeapChain
{
int *Element;
int sizee;
int capacity;
} HeapChain, *HeapChain_idx;
HeapChain_idx H;
///建造一个空的堆
void Creat_Heap(HeapChain_idx &H)
{
H = (HeapChain_idx) malloc(sizeof(HeapChain));
H->Element = (int *)malloc(sizeof(MAXSIZE * sizeof(int)));
H->Element[0] = MAXDATA; ///Element[0]的位置做哨兵,防止插入时越界
H->sizee = 0;
H->capacity = MAXSIZE - 3;
return ;
}
int In_Heap(HeapChain_idx H, int x)
{
if(H->sizee >= H->capacity)
return 0;
int i = ++H->sizee;
for( ; H->Element[i/2] < x; i /= 2) ///插入的数只要比父节点大,父节点的值就往下移
H->Element[i] = H->Element[i/2];
H->Element[i] = x;
return 1;
}
int Remove_Heap(HeapChain_idx H, int &e)
{
if(H->sizee == 0)
return 0;
e = H->Element[1];
H->Element[1] = H->Element[H->sizee--]; ///用最后一个数将堆顶覆盖
int parent, child;
for(parent = 1; parent * 2 <= H->sizee; parent = child) ///将堆顶数往满足堆定义的位置移动
{
child = parent * 2;
if(child < H->sizee && H->Element[child] < H->Element[child+1]) ///找出左右子节点的较大者
child++;
if(H->Element[parent] < H->Element[child]) ///如果比子节点最大者小就往下移动
{
int t;
t = H->Element[parent], H->Element[parent] = H->Element[child], H->Element[child] = t;
}
else ///否则就比两个子节点都大或相等,满足堆定义。调整完成
break;
}
return 1;
}
int Adjust_Heap(HeapChain_idx H)
{
if(H->sizee == 0)
return 0;
for(int k = H->sizee/2; k > 0; k--) ///从最后一个节点的父节点调整使满足堆定义,调整至堆顶
{
int parent, child;
for(parent = k; parent * 2 <= H->sizee; parent = child) ///如果当前节点要比最大的子节点要小,就往下移,相等不用移动
{
child = parent * 2;
if(child < H->sizee && H->Element[child] < H->Element[child+1])
child++;
if(H->Element[parent] < H->Element[child])
{
int t;
t = H->Element[parent], H->Element[parent] = H->Element[child], H->Element[child] = t;
}
else
break;
}
}
return 1;
}
int main()
{
int n, e;
HeapChain_idx H;
///测试建堆
scanf("%d", &n);
Creat_Heap(H);
for(int i = 1; i <= n; i++)
scanf("%d", &H->Element[i]);
H->sizee = n;
int flag;
flag = Adjust_Heap(H);
if(flag == 0)
printf("堆为空\n");
else
for(int i = 1; i <= H->sizee; i++)
printf("%d ", H->Element[i]);
printf("\n\n\n\n");
///测试删除的堆顶元素:
flag = Remove_Heap(H, e);
printf("被删除的数字为:%d\n", e);
if(flag == 0)
printf("堆为空\n");
else
for(int i = 1; i <= H->sizee; i++)
printf("%d ", H->Element[i]);
printf("\n\n\n\n");
///测试插入一个数:
printf("请输入要插入的数字\n");
scanf("%d", &e);
flag = In_Heap(H, e);
if(flag == 0)
printf("堆已经满了\n");
else
for(int i = 1; i <= H->sizee; i++)
printf("%d ", H->Element[i]);
printf("\n\n\n\n");
return 0;
}