引入
什么是堆
堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。
堆的定义
去做风吧,去做不被定义的风,我们堆拒绝“标签”,不被定义
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
堆的分类
大根堆:
9
/ \
6 8
/\ /\
3 2 1 4
很明显,任何一个子节点都不小于它的父节点,所以根节点最大
小根堆:
1
/ \
2 8
/\ /\
3 7 8 9
任何一个子节点都不大于它的父节点,所以根节点最小
堆的操作(小根堆为例)
堆的存储和遍历
堆用数组存储,顺序是二叉树的层序遍历
为什么呢?
拿上边的小根堆举例:
1
/ \
2 8
/\ /\
3 7 8 9
对应数组:
我们发现这样存储,会有:
节点下标/2 = 父节点下标
父节点下标 * 2 = 左孩子
父节点下标 * 2 + 1 = 右孩子
那么我们可以根据下标作为索引,找到任何一个节点的孩子和父亲(前提是存在)。
那我们怎么判断有的结点是不是有孩子或者父节点呢?
-
很简单,判断父节点就是判断是不是根节点,因为只有根节点没有父节点。
-
孩子呢?我们加入一个size变量,标记当前数组的元素个数和作为存入堆操作的指针下标,岂不妙哉?
即:
insertEnd(int x){
heap[++size] = x;
}
上边操作只是将数据插入最后,并不是真正插入,因为我们需要时刻维护这堆的特殊性质。
堆的维护
元素的下沉
元素什么时候会下沉?仅当一个节点的子节点都大于等于它时,才会下沉:
while(heap[x] >= heap[x * 2] && x * 2 <= n) || (heap[x] >= heap[2 * x + 1] && x * 2 + 1 <= n){}
那接下来就判断是哪个结点不符合这个性质,然后对两个节点进行交换,并且让指针移到新坐标
void down(int x) //x为下标
{
while(heap[x] >= heap[x * 2] && x * 2 <= n) || (heap[x] >= heap[2 * x + 1] && x * 2 + 1 <= n){
//只要找到有一个点小于它,那么就下沉,直无法下沉为止。
if(heap[x * 2] <= heap[x * 2 + 1])//两个子节点,往更小的地方下沉。
{
swap(heap[x], heap[x * 2]);
x *= 2;//需要改变下标。
}
else
{
swap(heap[x], heap[x * 2 + 1]);
x = 2 * x + 1;
}
}
}
元素的上浮
和下沉比起来思想一致,代码很少,因为每个节点只有一个父节点(除了根节点)
void up(int x)
{
while(heap[x] < heap[x / 2] && x > 1)
//注意这个x>1,我们说过 x == 1 就成为根节点了,此时上浮无意义
{
swap(heap[x],heap[x / 2]);
x /= 2;
}
}
ok主要操作就这点,其他的操作,都是基于上边的两个操作。
堆的基本操作
插入一个数
void insert(int x){
heap[++size] = x;
up(x); //在最后了,只能向上爬
}
删除根节点(最大值/最小值)
void Delete_root(){
heap[1] = heap[size--];
//size-- 更新边界
down(1);
}
删除任意结点也是这个办法,只不过比根节点多加一个
up(x);
因为根节点不需要去 up(1);
修改任意结点元素
void modify(int idx, int x){
heap[idx] = x;
down(idx);
up(idx);
}