详解数组实现隐式二叉堆及堆排序

一,堆的性质
1.顶部总是保存着最小或者最大的元素
2.有弹出操作,插入操作,合并操作,并且无论进行什么操作,都保持堆的性质1不变。
二,堆的实现
我用的是数组来实现隐式二叉堆,数组实现的二叉堆主要的是二叉堆的节点到数组下标的映射。比如堆中的第 i 个节点,对应数组下标也为i,然后通过下标的映射来找父节点,左儿子,右儿子。父节点的下标为i/2,左儿子为2i,右儿子为2i+1
三,堆最重要的一个性质是能够保持性质不变,也就是说我们需要维护堆的性质,对于用数组表示的二叉堆,给定任意索引 i 的节点,我们检查它的两个子节点,判断是否符合堆的性质,不符合就交换,使它符合堆的性质,这种维护的算法,是基于两个子树是合法堆的条件的

//最小堆的维护算法示例
void heapify(int *A, int i ,int size){//A为数组,i为节点,size为数组长度
    int left,right,smallest_index;//左子,右子,最小值点的下标
    while ( 1 ) {//迭代到符合堆的性质
    left = 2i;
    right = 2i+1;
    smalles_index = i;
    //两个if判断,找出3个节点中值最小的节点的下标
    if(left < size && A[i] > A[left])
        smallest_index = left;
    if(right < size && A[smallest_index] > A[right])
        smallest_index = right;
    //如果父节点不是值最小的,就将值最小的节点的值与父节点交换
    if(smallest_index != i){
        swap(A[i],A[smallest_index]);
        i = smallest_index//将i迭代为发生交换的子节点,以便下次循环使用
            }
    else return;//如果父节点是最小节点,说明堆的性质正确
        }
    }

如果是维护最大堆的性质,只要将数值比较那一部分修改一下就可以了。
三,堆的构造
我们可以用heapify算法从任意数组构造堆,相当于维护一个完全无序的堆,这样我们就不能自顶向下来修正堆的构造了,而要从下至上修正。由于堆的叶子节点没有子节点,所以它们是视为符合堆的性质的,所以从最后一个有分支的节点开始修正,这个节点是最后一个节点的父节点,所以索引为n/2,n为数组长度。
这样分析,我们可以写出构造堆的算法如下:

void build-heap(int *A,int size){//A为数组,size为数组长度
    int i;
    for(i = size/2;i > 0;i--)
        heapify(A,i,size);
    return;
    }

四,堆的基本操作
堆的通用定义通常要求我们提供一些基本操作,使用户可以获取或者修改数据:

  1. 获取顶部元素
int top(int *A){
    return A[0];
    }

2.弹出堆顶元素
我们需要在移除顶部元素后,通过执行heapify算法来维护堆的性质。最好的办法使记录下数组的首个元素,然后与最后一个元素交换,然后用heapify维护堆的性质。

int pop(int *A,int size){
    swap(A[0],A[--size]);
    heapify(A,0,size);//注意此时的size已经是减一了的
    return A[++size];
  1. 寻找前k个元素
    使用堆的结构可以很方便的查找前k个大小的元素,只需要不断执行pop操作k次就可以了。
int* top-k(int *A,int k, int size){//返回一个指针
    int *p;
    p=(int*)malloc(k*sizeof(int));
    for(int i = 0;i < k;i++)
        p[i]=pop(A,size);
    return p;

4.插入
对于插入操作,我们可以直接将元素插到数组末尾,然后自底向上恢复堆的性质

void insert(int *A,int size,int k){
     A[size++]=k;
     int i;
     for(i = size/2;i >= 0;i--)
         heapify(A,i,size);
     return;
  1. 有趣的堆排序
    根据堆的性质,我们可以很容易获得最大或最小的元素,利用这个性质,不断pop堆的顶元素,存入另一个数组,就可以得到序列,pop最大堆得到的是从大到小的序列,pop最小堆得到的是从小到大的序列。
int* heap-sort(int *A,int size){//A为待排序数组,size是数组长度
    int *p;
    p=(int *)malloc(size*sizeof(int));
    //先构造堆出来
    buile-heap(A,size);
    //然后将顶部元素pop出来储存到另一数组中
    for(int i = 0;i<size;i++)
        p[i]=pop(A,size);
    return p;
    }

这个算法需要额外的空间复杂度O(n),Robert W.Floyd给出了一个原地排序,无需创建额外数组的方法:

void great-heap-sort(int *A,int size){
    build-heap(A,size);
    while(size>1){
    swap(A[0],A[--size]);
    heapify(A,size,0);
        }
    }

你可能感兴趣的:(C语言学习笔记)