堆和堆排序

  二叉堆是一种基于数组的数据结构。它可以被视为一棵完全二叉树
  二叉堆可以分为大根堆和小根堆,大根堆是指父节点的key总是大于或等于任何一个子节点的key,因此堆的根节点为最大值。而小根堆则相反,因此小根堆的根节点为所有元素中的最小值。
  二叉堆必须满足两个特性:
  1)父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值
  2)每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)
  
  二叉堆一般都是基于数组实现的, 假设有数组A。则A.length是数组中所有元素的总个数,A.heapSize为存放在数组A中的堆的元素个数。因此A.heapSize<=A.length
  一个二叉堆的示意图如下:
  这里写图片描述
  数组下标是从0开始的,可以发现对下标为i的节点,其左右孩子节点的下标分别为2i+1、 2i+2.
  对下标为i(i不为0)的节点,其父节点下标为(i-1)/2。
  由于计算左右孩子节点和父节点下标这三个操作在整个堆的建立、维护过程中会被非常频繁的调用,因此一般将这三个操作通过宏定义或内联函数的方式实现。并且通过位移操作来实现乘、除2.
  因此可以定义:

#define PARENT(i) ((i)-1)>>1
#define LEFT(i) ((i)<<1)+1
#define RIGHT(i) ((i)<<1)+2 

  任何数据结构涉及到的主要操作不外乎构造、插入、删除、查找、修改,堆也一样。下面将以大根堆为例,来分别说明堆的这些操作的具体实现。

保持堆的性质

    void heapify(int i){
        int l=LEFT(i);
        int r=RIGHT(i);
        int largest=i;
        if(l<heapSize&&heap[l]>heap[i]){
            largest=l;
        }
        if(r<heapSize&&heap[r]>heap[largest]){
            largest=r;
        }
        if(largest!=i){
            swap(heap[i],heap[largest]);
            heapify(largest);
        }
    }

  heapify是对最大堆进行操作的重要子程序,输入为下标i。当调用heapify时,已经保证其左子堆和右子堆都已经是最大堆了,但是A[i]可能比左、右孩子小,因此要让A[i]在最大堆中“下降”,使得以i为根节点的子树成为最大堆。
  找出节点i、LEFT(i)、RIGHT(i)三个节点中的最大值作为根节点,然后递归向下进行调整。

建堆:  

    void build(){
        int begin=PARENT(heapSize-1);
        for(int i=begin;i>=0;--i){
            heapify(i);
        }
    }

  从最后一个非叶子节点开始,因此从下标为PARENT(heapSize-1)的节点开始,自底向上建堆。
  由于叶子节点没有孩子节点,因此不需要进行调整。只有当一个节点比它的孩子节点小时,就需要进行调整,使得以该节点为根节点的子树是一个大根堆。最终使得所有元素形成以A[0]为根节点的大根堆。

插入:  

    void insert(int key){
        heap.push_back(key);
        ++heapSize;
        build();
    }

  将元素加入到数组末尾,然后对整个堆进行重新构造过程。

移除:  

    void remove(){
        heap[0]=heap[heapSize-1];
        --heapSize;
        build();
    }

  对于堆,移除一般都是移除堆顶元素,因此可以将数组的最后一个节点移动到根节点的位置,然后对剩下的(heapSize-1)个元素重新建堆

堆排序  

    void sort(){
        while(heapSize>0){
            swap(heap[0],heap[heapSize-1]);
            --heapSize;
            build();
        }
    }

  由于大根堆的堆顶元素已经是所有元素中的最大值,因此将堆顶元素和最后一个元素互换位置,然后对剩下的(heapSize-1)建堆,此时建立的堆的堆顶元素即为所有元素中的第二大的值。然后重复上述操作,最终数组中的元素将是从小到大排列的。
  

堆排序的应用

  堆排序的时间复杂度始终都是O(nlgn)。
  堆排序由于要自己构建维护一个数组,操作比一般的排序要稍微复杂,因此一般的排序都没有采用堆排序来实现。但是堆排序在海量数据处理中非常方便。
  在海量数据处理中,经常需要获得前k个或第k大/小的数据,但是由于内存是有限的,因此不能采用快排、归并、冒泡这些基本的内部排序来对所有数据排序。可以通过维护一个具有k个元素的堆来完成。
  
  如从10亿个数据中选出最大的100个,则可以通过一个小根堆来实现
  1)首先创建一个具有100个元素的数组,从文件中读出100个数据依次填入数组中
  2)然后对这100个元素原地建小根堆,则堆顶元素为这100个元素中最小的
  3)依次从文件中读出剩余的数据,如果数据比堆顶元素小,则不处理继续读取下一个。如果数据比堆顶元素大,则将堆顶元素替换为新读出的元素,并调整堆。
  4)最终等到10亿个数据全部读完后,小根堆中剩余的元素即为最大的100个元素
  5)如果需要的话可以再对这100个元素进行排序
  

所有代码:

#define PARENT(i) ((i)-1)>>1
#define LEFT(i) ((i)<<1)+1
#define RIGHT(i) ((i)<<1)+2 

class Heap{
public:
    Heap():heapSize(0){}
    Heap(vector<int>& elems){
        for(int i=0;i<elems.size();++i){
            heap.push_back(elems[i]);
        }
        heapSize=elems.size();
    }
    void build(){
        int begin=PARENT(heapSize-1);
        for(int i=begin;i>=0;--i){
            heapify(i);
        }
    }
    void insert(int key){
        heap.push_back(key);
        ++heapSize;
        build();
    }
    void remove(){
        heap[0]=heap[heapSize-1];
        --heapSize;
        build();
    }
    void sort(){
        int size=heapSize;
        while(heapSize>0){
            swap(heap[0],heap[heapSize-1]);
            --heapSize;
            build();
        }
        heapSize=size;
    }

private: 
    void heapify(int i){
        int l=LEFT(i);
        int r=RIGHT(i);
        int largest=i;
        if(l<heapSize&&heap[l]>heap[i]){
            largest=l;
        }
        if(r<heapSize&&heap[r]>heap[largest]){
            largest=r;
        }
        if(largest!=i){
            swap(heap[i],heap[largest]);
            heapify(largest);
        }
    }

    vector<int> heap;
    int heapSize;
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值