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

/**
 * @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




 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值