学习笔记-堆排序

堆排序

堆排序运用到了树的思想。它仍然使用数组排序,并不创建树,但是排序时将数组看作一个完全二叉树,通过不断地构建大顶堆,将树顶元素与树尾元素交换,以树顶构建新的大顶堆来把整个数组从小到大排序。

首先,将数字按从上到下,从左到右的顺序写成一颗完全二叉树,这样的树的特点是,对于父节点i(对应数组中的下标i),它的左子节点的数组下标为2*i+1,右子节点的数组下标为2*i+2。而最下面的非叶子节点的数组下标为arr.length/2-1
在这里插入图片描述

进行堆排序大致分为两步,第一步是构建大顶堆,第二步是树顶元素与末尾元素交换。
首先,大顶堆是指:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆。小顶堆是指:每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。对数组升序排列要构建大顶堆,而降序排列要构建小顶堆。

思路

构建大顶堆:
这个方法用来将以一个非叶子节点开始的子树构建成大顶堆,需要三个变量,数组arr,该非叶子节点的数组下标i,和要改变的最大元素数(一开始就是数组的长度,每次排好了一个元素时就不用再去改变它,所以排列时每次会减1)
首先用temp保存这个非叶子节点的值。这个值中途是不会改变的,实际上,我们就是需要把它放在合适的位置上。
然后用for循环来遍历它的子树,j用来指向子节点(j表示的数组的下标),根据上面的性质j=2*i+1,当j小于length时,就可以不断循环(一开始代表的是传进来的这个非叶子节点的左子节点,之后会不断指向子节点的子节点),这时,在两个子节点中寻找较大的,并让j指向大的那个数。j+1就代表右子节点,它同样要小于length,防止出现越界,所以当右子节点较大时,让j+1就行。完成后,j就指向了子节点中大的那个,这时判断temp和arr[j]的大小,如果temp小,就把arr[j]赋给arr[i],并让i=j,如果temp大,说明符合大顶堆的逻辑,直接break退出循环就好。
注意的是:temp的值是不变的,等于一开始的非叶子节点值,每次是用temp和arr[j]比较,而不是arr[i]和arr[j]比较,因为arr[i]的值是会变化的(一开始指向传进来的非叶子节点,如果不是大顶堆,就会指向该节点较大的子节点,然后再指向子节点的子节点,不断改变)而我们实际上是在为temp找一个合适的位置,如果是arr[i]和arr[j]比较,就会出现temp小于arr[i]的情况,使得无法正确构建大顶堆,在这里的赋值是不断将较大的子节点赋给父节点。i=j这是让i指向较大的子节点,也就是下一次循环中的父节点,也就是i永远指向父节点,j永远指向较大的子节点。至于敢于直接break,是因为我们一定是从最小的非叶子节点开始构建大顶堆,从右向左,从下到上,所以可以直接break,因为下面的构建总是正确的。
退出循环后,让arr[i]等于temp,因为我们之前只是将较大的子节点赋给父节点,而子节点的值没有改变,实际上,它们应该交换。此时i指向的就是temp合适的位置,也就是某个子节点或者就没变,这时就直接赋值,完成交换。而这一步一定要放在循环外。因为循环内的i不一定是temp的合适位置。

代码

/**
     * 构建大顶堆
     * @param arr 原数组
     * @param i 构建大顶堆的最顶的非叶子节点的下标
     * @param length 要调整的最大元素数,每次不断减少
     */
    public static void adjustHeap(int[] arr,int i,int length){
        //保存非叶子节点值
        int temp=arr[i];
        //找到子节点的最大值
        for (int j = (2*i+1); j <length ; j=(2*j+1)) {
            //左子节点小于右子节点,就让j指向右子节点
            if((j+1)<length && arr[j]<arr[j+1]){
                j++;
            }
            if(temp<arr[j]) {
                arr[i] = arr[j];
                i=j;
            }else {
                break;
            }
        }
        arr[i]=temp;
    }

排序

排序方法的思路:首先调用上面的方法将原数组构建成大顶堆。这时因为上面的逻辑,要保证一定要从最后一个非叶子节点开始,逐渐向上。所以先求出下标,然后- -。完成后,显然根节点就是数组中最大的数了,让它与数组末尾数交换,那么最大的数就放在最后了,这时新的根节点不一定是数组中最大的,所以就以它为头,重新构建大顶堆(因为实际上只改变了两个数,而移下去的最大数不参与排列新大顶堆,也就是下面的大顶堆逻辑并没有被破坏,所以不用循环,而直接排就可以),然后重复这个过程,直到排列完毕。

public static void heapSort(int[] arr){
        int temp=0;
        //构建大顶堆
        for (int i = arr.length/2-1; i >=0 ; i--) {
            adjustHeap(arr,i,arr.length);
        }
        //交换
        for (int i = arr.length-1; i >0 ; i--) {
            temp=arr[i];
            arr[i]=arr[0];
            arr[0]=temp;
            adjustHeap(arr,0,i);
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值