堆、索引堆与堆排序

优先队列

  • 普通队列:先进先出;后进后出

  • 优先队列:出队顺序和入队顺序无关,与优先级有关

  • 适用于动态数据维护的情况

  • N个元素中选前M个,使用普通排序算法的时间复杂度为O(NlogN),而使用堆排序的时间复杂度为O(NlogM)

  • 在这里插入图片描述

基本实现

  • 经典实现:二叉堆(Binary Heap)

  • 在这里插入图片描述

  • 二叉堆的特点:

    • 任何一个节点都小于等于其父节点——最大堆
    • 是一棵完全二叉树:除了最后一层外,其它层必须满节点。最后一层节点都在最左侧。
  • 数组实现二叉堆

    扫描二维码关注公众号,回复: 12686095 查看本文章
  • 在这里插入图片描述

  • 数组实现的特点:若将根节点序号设为1,则左节点的序号是其父节点的二倍。右节点的序号是其父节点的二倍+1;下面的实现都将根节点序号设为1.

Shift Up(向堆中插入一个元素)

  • 当数组末尾新插入一个元素后,需要将其安排在二叉堆中的合适的位置,这个过程叫做Shift Up

  • 方法:不断判断该元素与其父节点的值的大小,若大于其父节点的值,则两者交换位置,直到其不再大于其父节点为止

  • void shiftUp(int data[], int count, int k){
          
          
    	while(k > 1 && data[k/2] < data[k]){
          
          
    		swap(data[k/2], data[k]);
    		k/2;
    	}
    }
    

Shift Down(从堆中取出一个元素)

  • 当取出数组首元素之后,重新排列数组元素使其符合二叉堆的定义的过程叫做Shift Down

  • 方法:将取出首元素后,将末尾元素放到首元素的位置,然后不断与其子节点中值较大的节点进行交换位置,直到其不再小于其任意一个子节点为止

  • // swap(data[1], data[count]);
    // count--;
    void shiftDown(int data[], int count, int k){
          
          
        while(2*k <= count){
          
          
            int j=2*k;// 在此轮循环中,data[k]和data[j]交换位置
            if(j+1 <= count && data[j+1] > data[j])
                j+=1;
            
            if(data[k]>=data[j])
                break;
            
            swap(data[k], data[j]);
            k=j;
        }
    }
    
  • 不断重复该操作相当于对该数组排序,称为堆排序

Heapify

  • 将一个无序的数组重新排列,成为一个二叉堆的过程称为Heapify

  • 在这里插入图片描述

  • 方法:找到该数组的第一个非叶子节点(对于根节点索引为1的二叉堆来说,索引为count/2的节点即为第一个非叶子节点),即上图中的第5号节点22,不断对其及之前的节点(22、13、19、17、15)进行Shift Down操作

  • 将N个元素逐一插入到一个空队中的时间复杂对为O(NlogN);Heapify的时间复杂度为O(N)

堆排序

  • 步骤:使用Heapify将一个无序数组构建成堆,然后不断取出首元素,并使用ShiftDown使剩余元素仍然维持一个堆。

  • void heapSort(int arr[], int n) {
          
          
    	// 重新申请一块内存
        data = new int[n + 1];
    
        for (int i = 0; i < n; i++)
            data[i + 1] = arr[i];
        count = n;
    
        // Heapify
        for (int i = count / 2; i >= 1; i--)
            shiftDown(data,count, i);
    	
        // 不断取出首元素
        for (int i = n - 1; i >= 0; i--){
          
          
            arr[i] = data[1];
         	swap(data[1], data[count]);
            count--; 
            shiftDown(data, count, 1);
        }
    }
    

索引堆

  • 使用索引堆的原因:

    • 避免了大量的赋值操作,尤其是对于元素复杂的数据,会消耗较多时间
    • 由于重新排序后原数据元素的位置会发生改变,因此很难通过原索引找到某个元素。
  • 解决方法:

    • 存储数据的时候多存储一份每个数据的索引,即另开辟一块跟原数组同等大小的数组,存储原数据的索引。由于只是整型数组,并不占很大内存。

    • 在这里插入图片描述

    • 在构建堆的时候,元素进行比较的还是data中的值,而交换的却是对应的index。这样构建完后,原数组data并没有改变,改变的只是index。这样就避免了对原数据的复制,只需要交换index整型数组。

    • 在这里插入图片描述

    • 对于该最大索引堆,首元素为index[1]对应的元素62,原数据的第一个元素仍然为15,方便索引。

  • 反向索引

    • 除了要知道原数组第i个元素对应的值是多少外,还要知道该元素在堆中的索引,即排在堆中的位置。比如,想知道原数组第8个元素是30之外,还想要知道它在堆中的位置。

    • 在这里插入图片描述

    • 解决方法:另开辟一个数组,存放该元素在堆中的索引即可。

堆相关的其他问题

  • 多路归并排序
  • 其他堆:二项堆、斐波那契堆

猜你喜欢

转载自blog.csdn.net/qq_34731182/article/details/114312142