理解堆排序的原理

Posted 我是攻城师

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了理解堆排序的原理相关的知识,希望对你有一定的参考价值。


前面的文章提到过,堆的数据结构其实是一颗二叉树,准确的说是一颗完全二叉树,因此符合完全二叉树的性质:

如果对具有n个节点二叉树的根节点从0开始编号,则序号为i的节点的双亲结点为(i-1)/2, 左孩子的编号为2i+1, 右孩子为2i+2。

如果从1开始编号,则双亲结点编号为i/2,左孩子结点序号为2i,右孩子结点序号为2i+1.

正是由于上面的这种性质,所以决定了堆结构可以采用数组来实现,此外,堆又分最大堆和最小堆:

最大堆:每一个父节点的值,都大于或等于左右两个子节点的值。

最小堆:每一个父节点的值,都小于或等于左右两个子节点的值。

那么堆这种结构是如何用来实现排序的呢? 这里以最大堆为例,首先给定一个无序的数组,这里我假设元素是[3,-1,4,6],要想使用堆排序,必须先把这个无序数组给构建成最大堆,在构建完毕后,root节点的值一定是最大的,然后取出最大值,放在原数组的尾部,接着对剩下的数组,继续调整堆,得到最大值,放在数组的倒数第二的位置,依次类推,最终得到一个有序的数组。

图示如下:

代码已经准备好了,下面我们看看如何在Java中实现:

 
   
   
 
  1.    public static void sort(int arr[]){

  2.        //初始化构建一个大顶堆

  3.        for (int i = arr.length/2;i>=0; i--) {

  4.            System.out.println();

  5.            heapAdjust(arr,i,arr.length);

  6.        }

  7.        //原地排序,无序额外的空间辅助

  8.        for (int i =  arr.length-1;i>0; i--) {

  9.            swap(arr,0,i);//将最大的值,放在数组的尾部

  10.            heapAdjust(arr,0,i);//重新调整堆的结构,选举出来新的最大值。

  11.        }

  12.    }

  13.    private static void swap(int[] arr, int index1, int index2) {

  14.        int tmp=arr[index1];//最大值,下标为0的永远是最大值

  15.        arr[index1]=arr[index2];//将最后一位数字与第一位替换

  16.        arr[index2]=tmp;//现在最后一位是最大的

  17.    }

  18.    private static void heapAdjust(int[] arr, int i, int length) {

  19.        int child;

  20.        int father;

  21.        for (father=arr[i];left(i)<length;i=child){

  22.            child=left(i);

  23.            //第一个条件代表必定有右子树,,第二个条件代表左子树小于右子树,则比较右子树和父亲

  24.            if(child!=length-1 && arr[child]<arr[child+1]){

  25.                child++;//加1,代表是右子树

  26.            }

  27.            //如果父节点小于孩子,则进行交换

  28.            if(father<arr[child]){

  29.                arr[i]=arr[child];

  30.            }else{

  31.                //符合大顶堆的结构

  32.                break;

  33.            }

  34.        }

  35.        //此时i的下标代表的是孩子的值,把父亲的数据赋值给孩子。

  36.        arr[i]=father;

  37.    }

  38.    public static int left(int i){

  39.        return 2*i+1;

  40.    }

  41.    public static void main(String[] args) {

  42.        int array[]={3,-1,4,6};

  43.        System.out.println("before: "+ Arrays.toString(array));

  44.        sort(array);

  45.        System.out.println("after: "+Arrays.toString(array));

  46.    }

堆排序的最优,最坏及平均时间复杂度均为O(nlogn),空间复杂度为O(1),由于在比较和交换时不记录状态,所以和快排一样属于不稳定的排序算法。

总结:

本文主要介绍了堆排序的思想,原理和实现,由于堆特殊的数据结构所以在处理一些优先级的任务排序或者求海量数据topN的问题时,具有着明显的优势。


以上是关于理解堆排序的原理的主要内容,如果未能解决你的问题,请参考以下文章

106,排序-堆排序

堆排序与海量TopK问题

面试必知必会|理解堆和堆排序

面试必知必会|理解堆和堆排序

Java排序算法 - 堆排序的代码

常见排序算法的实现(归并排序快速排序堆排序选择排序插入排序希尔排序)