堆是一种特殊的完全二叉树(完全二叉树_百度百科),根据堆中父节点值和左右儿子节点值的大小关系,主要分为两类:大根堆和小根堆。大根堆是父节点的值总是比左右儿子节点的值大,反之为小根堆。

  当我们需要求一组值中某个最大值或者最小值时,可以通过遍历一次这组值,时间复杂度为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);
}

 

 

 

 

   圆满结束。

  

posted @ 2018-08-08 22:57  potato226  阅读(119)  评论(0编辑  收藏  举报