堆
堆是一种特殊的完全二叉树(完全二叉树_百度百科),根据堆中父节点值和左右儿子节点值的大小关系,主要分为两类:大根堆和小根堆。大根堆是父节点的值总是比左右儿子节点的值大,反之为小根堆。
当我们需要求一组值中某个最大值或者最小值时,可以通过遍历一次这组值,时间复杂度为O(N),这时我们就可以使用堆来优化这个过程,堆优化后的时间复杂度为O(logN)。因为很显然的是,我们可以发现,小根堆堆顶为堆中最小元素,大根堆堆顶为堆中最大元素
为了保证堆的特性,我们时常需要维护一个堆,常用操作包含上浮和下沉两种。以小根堆为例:假如有一个小根堆,现在新增一个元素被放在堆顶,如果此时不符合小根堆的特性,可以通过不断进行下沉操作,使得其重新变成一个小根堆。
我们使用一个一维数组来存储堆中的值,如下:
const int MAX = 1001; int h[MAX];
下面来介绍下沉操作的步骤:
1、传入待下沉的节点编号now。
2、判断now是否有左儿子,即判断now * 2 <= n,如果是,进一步判断h[now]和h[now * 2]的大小关系是否符合最小堆的特性,如果不符合则标记需要交换h[now]和h[now * 2]中值的位置,同时进入步骤3;如果否,则下沉结束调整完毕。
3、判断now是否有右儿子,即判断now * 2 + 1 <= n,如果是,进一步判断h[now]和h[now * 2 + 1]的大小关系是否符合最小堆的特性,如果不符合则标记需要交换h[now]和h[now * 2 + 1]中值的位置。
4、重复步骤2、3直到整体符合最小堆的特性。
接下来介绍上浮操作的步骤:
1、传入待上浮的节点编号now。
2、判断now是否为最终的根节点,即判断now == 1,如果是,则说明上浮完成。如果否,进入步骤3。
3、进一步判断h[now]和h[now / 2]的值的大小关系是否符合最小堆的特性,如果不符合则交换h[now]和h[now * 2 ]中值的位置,将now上浮为now / 2,即 now = now / 2。
4、重复步骤2、3。
下沉、上浮操作实现如下(p为堆中元素数量):
void swap(int a, int b) { int tmp = heap[a]; heap[a] = heap[b]; heap[b] = tmp; } void up(int now) { while((now != 1) && (heap[now] < heap[now / 2])) { swap(now, now / 2); now /= 2; } } void down(int now) { int i = now * 2; while(i <= p) { if((i + 1 <= p) && (heap[i + 1] < heap[i])) { i++; } if(heap[now] > heap[i]) { swap(now, i); now = i; i = now * 2; } else { break; } } }
接下来介绍堆中元素删除与添加的操作。对于添加新元素到堆中,只需要将新元素添加到堆的末尾,再对新元素进行上浮操作;对于删除堆顶元素,只需要将堆顶元素用堆中最后一个元素替换掉,再对新的堆顶进行下沉操作即可。
添加、删除操作代码如下:
int del() { int t = h[1]; h[1] = h[p]; p--; down(1); return t; } int add(int i) { p++; h[p] = i; up(p); }
圆满结束。