回首Java——堆排序

来源:互联网 发布:java自带的观察者模式 编辑:程序博客网 时间:2024/05/21 17:17

什么是堆排序?

堆实际上是一棵完全二叉树,堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。


堆排序的思想

利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。

其基本思想为(大顶堆):

1) 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;

2) 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];

3) 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。


操作过程如下:

  1. 初始化堆:将R[1..n]构造为堆;
  2. 将当前无序区的堆顶元素R[1]同该区间的最后一个记录交换,然后将新的无序区调整为新的堆。
    (因此对于堆排序,最重要的两个操作就是构造初始堆和调整堆,其实构造初始堆事实上也是调整堆的过程,只不过构造初始堆是对所有的非叶节点都进行调整。)

举例说明

给定一个整形数组a[]={16,7,3,20,17,8},对其进行堆排序。首先根据该数组元素构建一个完全二叉树,得到
image

然后需要构造初始堆,则从最后一个非叶节点开始调整,调整过程如下:

image

20和16交换后导致16不满足堆的性质,因此需重新调整

image

这样就得到了初始堆。即每次调整都是从父节点、左孩子节点、右孩子节点三者中选择最大者跟父节点进行交换(交换之后可能造成被交换的孩子节点不满足堆的性质,因此每次交换之后要重新对被交换的孩子节点进行调整)。有了初始堆之后就可以进行排序了。

image

此时3位于堆顶不满堆的性质,则需调整继续调整

image

此时3位于堆顶不满堆的性质,则需调整继续调整

image

这样整个区间便已经有序了。 从上述过程可知,堆排序其实也是一种选择排序,是一种树形选择排序。只不过直接选择排序中,为了从R[1…n]中选择最大记录,需比较n-1次,然后从R[1…n-2]中选择最大记录需比较n-2次。事实上这n-2次比较中有很多已经在前面的n-1次比较中已经做过,而树形选择排序恰好利用树形的特点保存了部分前面的比较结果,因此可以减少比较次数。对于n个关键字序列,最坏情况下每个节点需比较log2(n)次,因此其最坏情况下时间复杂度为nlogn。堆排序为不稳定排序,不适合记录较少的排序。


代码实现

public class HeapSort {    public static void main(String[] args) {        int arr[]={49,38,65,97,76,13,27,49,78,34,12,64,5,4,62,99,98,54,56,17,18,23,34,15,35,25,53,51};        heapSort(arr);    }    /**     * 堆排序     */    private static void heapSort(int[] arr) {        /**         * 由堆排序步骤可知         * 1. 先建堆         * 2. 然后交换堆顶和堆底元素         */        System.out.println("开始排序");        int arrayLength=arr.length;        //循环建堆        for(int i=0;i<arrayLength-1;i++){            //1.建堆            buildMaxHeap(arr,arrayLength-1-i);            //2.交换堆顶和最后一个元素            swap(arr,0,arrayLength-1-i);            System.out.println(Arrays.toString(arr));        }    }    /**     * buildMaxHeap 构建最大堆     * @param arr 待排序数组     * @param lastIndex 最后一个节点     */    private static void buildMaxHeap(int[] data, int lastIndex) {        //从lastIndex处节点(最后一个节点)的父节点开始        for(int i=(lastIndex-1)/2;i>=0;i--){            //k保存正在判断的节点            int k=i;            //如果当前k节点的子节点存在            while(k*2+1<=lastIndex){                //k节点的左子节点的索引                int biggerIndex=2*k+1;                //如果biggerIndex小于lastIndex,即biggerIndex+1代表的k节点的右子节点存在                if(biggerIndex<lastIndex){                    //如果右子节点的值较大                    if(data[biggerIndex]<data[biggerIndex+1]){                        //biggerIndex总是记录较大子节点的索引                        biggerIndex++;                    }                }                //如果k节点的值小于其较大的子节点的值               if(data[k]<data[biggerIndex]){                    //交换他们                    swap(data,k,biggerIndex);                    //将biggerIndex赋予k,开始while循环的下一次循环,重新保证k节点的值大于其左右子节点的值                    k=biggerIndex;                }else{                    break;                }            }        }    }    /**     * 交换堆顶和堆底元素     * @param data 堆数据     * @param first 堆顶元素     * @param lastIndex 最后一个节点     */    private static void swap(int[] data, int first, int lastIndex) {        int tmp=data[first];        data[first]=data[lastIndex];        data[lastIndex]=tmp;    }}

参考文章:

  • https://jingyan.baidu.com/album/5225f26b057d5de6fa0908f3.html?picindex=1
原创粉丝点击