欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

堆排序实现原理解释及思考

程序员文章站 2022-03-31 19:16:51
...
/**
 * @author https://www.cnblogs.com/chengxiao/p/6129630.html
 */
public class Heap {
    public static void main(String []args){
        int []arr = {7,8,9,6,3,4,5,2,1,10};
        sort(arr);
        System.out.println(Arrays.toString(arr));
    }
    public static void sort(int []arr){
        //1.构建大顶堆
        for(int i=arr.length/2-1;i>=0;i--){
            //从第一个非叶子结点从下至上,从右至左调整结构
            adjustHeap(arr,i,arr.length);
        }
        //2.调整堆结构+交换堆顶元素与末尾元素
        for(int j=arr.length-1;j>0;j--){
            //将堆顶元素与末尾元素进行交换
            swap(arr,0,j);
            //重新对堆进行调整
            adjustHeap(arr,0,j);
        }

    }

    /**
     * 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
     * @param arr
     * @param i
     * @param length
     */
    public static void adjustHeap(int []arr,int i,int length){
        //先取出当前元素i
        int temp = arr[i];
        //从i结点的左子结点开始,也就是2i+1处开始
        for(int k=i*2+1;k<length;k=k*2+1){
            //如果左子结点小于右子结点,k指向右子结点
            if(k+1<length && arr[k]<arr[k+1]){
                k++;
            }
            //如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
            if(arr[k] >temp){
                arr[i] = arr[k];
                i = k;
            }else{
                break;
            }
        }
        //将temp值放到最终的位置
        arr[i] = temp;
    }

    /**
     * 交换元素
     * @param arr
     * @param a
     * @param b
     */
    public static void swap(int []arr,int a ,int b){
        int temp=arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
}
以上是摘自https://www.cnblogs.com/chengxiao/p/6129630.html可运行的代码


堆排序处理顺序的特点是升序使用大顶堆,降序使用小顶堆。
大顶堆(又叫大根堆,最大堆):
子节点的值一定小于父节点。

即下图中,(1)的值大于(2)、(3)的值

堆排序实现原理解释及思考

有的人觉得升序应该使用小顶堆,这样从上自下依次读取(1)(2)(3),就是一个升序的数组;
这实际上是误解了堆排序的实现方式,因为堆排序没有进行横向排序的。即(2)和(3)
没有固定的大小关系。


堆排序的实现原理

将数组看作一个二叉树。数组从左至右依次对应二叉树从上至下从左至右的元素,如下图是一个

长度为12的数组对应的二叉树示意图,数字为对应数组的下标。

堆排序实现原理解释及思考

1.  首先从末尾父节点(5)开始,比较两个子节点和父节点的大小,将最大值和父节点交换。然后按(4),(3),(2),(1),(0)

的顺序一直遍历到根节点。此时得到一个大顶堆。


2.  在大顶堆中,根节点(0)的值一定是最大值。所以此时将根节点的值置于末尾。【即交换(0)和(11)的值】


3. 再次基于剩下的元素构建大顶堆,并将根节点值置于当前末尾。。。。



这样做在第二次开始每一次构建大顶堆时,除开顶层元素a,第二层开始都是构建好的大顶堆,

不需比较所有元素,只需要不停交换a和剩下旁支中较大的元素即可。

如下图是第二次比较及交换的其中一种情况。

堆排序实现原理解释及思考




数学思考:


为什么最末父节点的坐标为array.length/2-1?

 由于数组的起始坐标为0,我们可以简单地理解一个元素的坐标为它之前元素的总个数。

 二叉树可按每层的元素个数转换为一个基数为1,比值为2的等比数列(除开叶子层);
可得第n层的元素个数为2^(n-1)
1~n层的元素个数和为2^(n-1)-1


堆排序实现原理解释及思考

以上图为例:

 此时,最末父节点所在层n为第三层,最末父节点的子节点数count =1;

由等比数列性质可知:

该节点之前的元素个数为: 2^(n-1)-1+(x-count)/2;

该节点之后的元素个数为: 2^(n-1)-1-(x-count)/2-1+x;

设m = 2^(n-1) -1 +x/2

则该节点之前的元素个数为: m-count/2

该节点之后的元素个数为:m+count/2

array.length = 节点之前元素+节点+节点之后元素 = 2m+1

m = (array.length-1)/2

可得该节点下标为其之前的元素个数:

(array.length -1)/2 -count/2..................................................................(1)

由于java浮点数转int总是截断小数点后的数(即1/2取值为0):

若count =1:

 则array.length为偶数;array.length-1为奇数;

 (array.length-1)/2 = (array.length)/2 -1;

 同时count/2舍去;

 (1)式可转换为 array.length/2 -1;

若count = 2 :

 则array.length为奇数;array.length-1为偶数;

 (array.length-1)/2 = (array.length)/2;

count/2 = 1;

(2)式也可转换为array.length/2 - 1;


为什么某节点的左子节点的坐标 = 该节点坐标i*2+1?

 将上个问题的最末父节点看作二叉树中的某一节点,则上个问题中count=1情况下的array.length即为子节点的坐标。

可得关系:

 (子节点坐标-1)/2 = 父节点坐标。

 即 父节点坐标的左子节点坐标 = 父节点坐标 * 2 + 1