堆及堆排序

堆及堆排序

堆是一种特殊的完全二叉树,这种二叉树的每个子树的根节点是该子树的最大值或者最小值,以此分为大根堆和小根堆。
存储方式:我们通常用数组的方式存储堆。

构造堆

这里默认构造大根堆
构造堆最重要的操作就是对每个子树的父亲节点和孩子节点的调整。这种调整要考虑以下几个方面:

  • 父亲节点和左右孩子的下标的关系
  • 父亲节点和左右孩子要做什么比较
  • 这种操作何时进行?需要进行多少次?
下标关系
  • 下标从0开始: 父:fa; 左:fa * 2 + 1; 右:fa * 2 + 2
  • 下标从1开始: 父:fa; 左:fa * 2; 右:fa * 2 + 1
比较
  • 大根锥:比较左右孩子和父亲节点,并将大者置为父节点
  • 小根锥:比较左右孩子和父亲节点,并将小者置为父节点
自下而上还是自上而下

这样的比较通常有两个方向,即自上而下和自下而上(这里的初始下标为0)

  • 自上而下:这是父节点不断向下走的过程

      void adjustDown(int left, int right){ //待调整区间
          int i = left, j = 2 * i + 1; // i为父节点,j为左孩子
          while(j <= right){ //孩子节点没有超出待调整范围
              if(j + 1 <= right && heap[j] < heap[j + 1]){//右孩子存在并且比左孩子大
                  j = j + 1;
              } 
              if(heap[j] > heap[i]){
                  swap(heap[j], heap[i]);
                  i = j;
                  j = 2 * i + 1; //更新节点  父节点和左孩子
              }
              else{
                  break;
              }
          }
      }
    
  • 自下而上:这是孩子节点不断向上走的过程

          void adjustUp(int left, int right){ //待调整区间
          int i = right, j = (i - 1) / 2; //孩子节点和父节点
          while(j >= left){
              if(heap[i] > heap[j]){
                  swap(heap[i], heap[j]);
                  i = j;
                  j = (i - 1) / 2;
              }
              else{
                  break;
              }
          } 
      }
    
何时需要调整节点之间的关系?
  • 建堆:在初始化一个堆的时候,需要将所有节点进行调整
    • 采用自上而下的方式调整,把更大的数拉上来
    • 每个节点都要向下扫描,尝试把它下面的节点拉上来
    • 这样的调整应该由叶子结点到根节点,也就是自下而上(注意这里指的是遍历节点的顺序,而不是调整节点的方式)
  • 插入元素:插入在数组的最后,然后自下而上,尝试把自个新元素往上拉
  • 删除堆顶元素:数组的最后一个元素覆盖堆顶元素,再将新的堆顶元素向下调整

总结:不难发现,只有建堆的过程需要对每个节点都进行一次向下调整,而删除和插入只对某个特定的节点进行向上或者向下调整,这是因为在删除和插入节点的时候,堆在进行该操作之前所有节点之间的关系是符合条件的。

堆类实现

弄清楚以上的内容,堆的实现方式就很清晰了,代码如下:

class Heap{
private:
    const int MAXN = 10000000; 
    int* heap;
    int n;
    int end;
public:
    Heap(int n){
        this -> n = n;
        this -> end = n - 1;
        heap = new int[MAXN];
        cout << "输入n个节点的权值" << endl;
        for(int i = n - 1; i >= 0; i--){
            cin >> heap[i];
            adjustDown(i, n - 1);
        }
    }

    void adjustDown(int left, int right){ //用于删除节点,新建堆
        int i = left, j = 2 * i + 1; // j 为左孩子
        while(j <= right){
            if(j + 1 <= right && heap[j] < heap[j + 1]){
                j = j + 1;
            }
            if(heap[j] > heap[i]){
                swap(heap[j], heap[i]);
                i = j;
                j = 2 * i + 1; //更新节点  父节点和左孩子
            }
            else{
                break;
            }
        }
    }

    void adjustUp(int left, int right){ //用于新增节点,将新节点和父亲比较
        int i = right, j = (i - 1) / 2;
        while(j >= left){
            if(heap[i] > heap[j]){
                swap(heap[i], heap[j]);
                i = j;
                j = (i - 1) / 2;
            }
            else{
                break;
            }
        } 
    }

    int pop(){
        if(end < 0) return INT_MIN;
        int tmp = heap[0];
        heap[0] = heap[end--];
        if(n > 0) n--;
        adjustDown(0, end);
        return tmp;
    }


    void push(int val){
        if(end == MAXN - 1){
            cout << "堆已满" << endl;
            return;
        }
        heap[++end] = val;
        n++;
        adjustUp(0, end);
    }

    int size(){
        return n;
    }


    void print(){
        for(int i = 0; i < n; i++){
            cout << heap[i] << " ";
        }
    }


};  


堆排序

利用堆的特性,通过n次出堆操作,能够实现对无序数组的排序。每次推出堆顶元素,堆的维护需要 O ( l o g n ) O(log^n) O(logn)的时间复杂度,即堆排序的时间复杂度为 O ( n l o g n ) O(nlog^n) O(nlogn)

构造出了堆之后,所谓的堆排序不过是无脑的推出堆顶元素并放到数组最后而已

    void heap_Sort(){
        int flag = end;
        while(flag > 0){
            swap(heap[0], heap[flag--]);
            adjustDown(0, flag);
        }
    }
  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值