[面试手撕2][堆排序]2.LC912.排序数组:先构建大根堆,再逐渐缩小有效堆,实现升序排列

85 阅读3分钟

2.LC912.排序数组

class Solution {
    public int[] sortArray(int[] nums) {
        int n = nums.length;
        //在完成堆化逻辑之后,我们从第一个非叶子的元素开始进行堆化
        //我们发现 n/2 -1这个索引,是第一个非叶子的元素
        for(int i=n/2 -1;i>=0;i--){
            //从下往上构建树
            heapify(nums,n,i);
        }
​
        //树构建好了,现在数组的首元素就是最大元素,数组的尾部元素就是最小元素,我们只需要不断的交换首元素和左移的尾部元素指针指向的元素即可。因为我们构建好的堆会自动调整,将堆中最大元素移动到数组的头部
        for(int i=n-1;i>0;i--){
            swap(nums,0,i);
            heapify(nums,i,0);//注意:此时的n应该是不断减小的,因此是i,这里也伴随着有效堆的部分逐渐减少,当整个数组升序排列时,有效堆将归零。
        }
        return nums;
    }
    //1.排序一定涉及到元素的交换,先写swap方法
    private void swap(int[] nums,int i,int j){
        //目的是:将i和j指向的值进行交换
        int tmp =nums[i];//先把i存在临时变量中
        nums[i] = nums[j];//将j指向的元素赋值到i指向的数组空间
        nums[j]= tmp;//凭借临时变量,将i指向的值,赋值到j指向的数组空间
    }
    //2.我们需要借助一个堆来完成排序,因此需要实现heapify堆化方法
    private void heapify(int[]nums,int n,int index){
        //nums是数组,本质堆化就是将一个完全二叉树存在数组中,因此传入数组是必要的
        //n:由于我们会不断维护一个有效范围逐渐减小的堆,所以n是变化(数组长度会变化)
        //index表示当前这个堆的堆顶元素(如果是大根堆,那index就应该是最大的元素,如果是小根堆,那index就应该是最小的元素)
​
        int largest = index;//当前维护的堆的堆顶元素
        int left = index*2+1;//刚刚提到过:left和right都是将完全二叉树存入到一个数组中,节点所在的索引。而i是上级的节点,left和right都是i派生出来的左右子节点
        int right = index*2+2;
        
        //但是:目前这个堆是无序的,难道index所在的位置largest真的指向最大的元素的么?
​
        //分成left>largest和right>largest两种情况分类讨论:
        if(left<n&&nums[left]>nums[largest]){
            largest = left; //如果left指向的元素比index指向的元素大,那就让left替代largest作为调整后的堆的堆顶元素
        }
​
        if(right<n&&nums[right]>nums[largest]){//注意此处比较的是largest指向的元素,而非是index指向的元素
            largest = right;
        }
​
        //如果说堆顶元素发生了改变,我们刚刚只改变了索引,但是没有影响到具体的数组中的值
        if(index!=largest){
            swap(nums,index,largest);
            heapify(nums,n,largest);//把新的堆顶传入递归,判断是否可以保证当前堆顶是最大的。
        }
​
    }
    //3.需要在排序中,将数组构建成一个大根堆(调用heapify)
    //4.需要在排序中,通过交换大根堆的首元素和一个不断右移的最小元素,来实现大小元素位置交换,从而让大的元素都排在右边,让小的元素都排在左边。同时原本的堆的有效部分逐渐减小,我们需要维护减少后的堆
}