Java堆排序

        堆结构就是用数组实现的一颗完全二叉树。完全二叉树指的是除了叶子节点不满,其他层都是满节点的二叉树,而且叶子节点是从左到右依次遍满的。

        给一个数组int[] nums=new int[]{1,2,3,4,5,6},指定数组从位置0出发连续一段的构成一个堆,长度6即为堆的大小,那么由该数组构成的堆结构父节点位置(i-1)/2,左节点 位置2*i+1,右节点位置2*i+2,所以构成的堆结构为:nums[0]是根节点。

                                                                        1

                                                                 2                3

                                                          4          5       6

        如何实现堆排序呢?我们先来了解下大根堆和小根堆。

* 大根堆:堆的每个节点的值都大于等于它的左右孩子节点的值,堆的根节点是最大的值。
* 小根堆:堆的每个节点的值都小于等于它的左右孩子节点的值,堆的根节点是最小的值。

        那么如何实现堆排序呢?给定一个数组,假设我们给它调整为大根堆,这也就代表着nums[0]位置的数为当前数组的最大值,我们将0位置的数和数组最后一个位置的数交换,那么数组的最大值位置就排好了。这样调整以后,大根堆就被破坏了,那么我们在数组nums.length-1这个范围内继续调整出一个大根堆,就可以把倒数第二个位置的数排好了。依次处理,最终就实现了数组排序。

        那么问题来了,如何插入一个数或者删除堆里一个数后,依然将其调整为大根堆呢?

// index位置加入一个数
// heapInsert是一个向上调整的过程 时间复杂度 (走一个二叉树的高度 )O(logN)
public void heapInsert(int[] nums,int index){
    //如果加入的节点值大于其父节点的值,就交换。如果交换到头节点了停止或者不大于其父节点值停止
    while(nums[index]>nums[(index-1)/2]){
        //数组位置交换
        swap(nums,index,(index-1)/2);
        index=(index-1)/2;
        if(index==0){
            break;
        }
    }
}

/**
* heapify是一个向下调整的过程 时间复杂度 (走一个二叉树的高度 )O(logN)
* @param heapSize 堆中元素个数
* @param nums      堆数组
* @param index    从index位置向下堆化
*/
public void heapify(int[] nums,int index,int heapsize){
    //左节点位置
    int leftIndex=(index*2)+1;
    while(leftIndex<heapsize){
        //找到左右孩子中的大值
        int childMaxIndex=left+1<heapsize&&nums[left+1]>nums[left]?left+1:left;
        if(nums[childMaxIndex]>nums[index]){
            //说明我的左或右孩子值比我大,交换
            swap(nums,childMaxIndex,index);
            index=childMaxIndex;
        }else{
            //不比我大,不需要调整
            break;
        }
        leftIndex=(index*2)+1;
    }
}

对于堆而言最重要的就是上述两个调整方法。如果我们随意修改已经数组中的一个数,我们依然可以在O(logN)的时间复杂度上将堆重新调整为大根堆。(我们将修改的值和原值比较,如果加入的值比原值大,就向上做heapInsert;加入的值比原值小,就向下做heapify。)依然用我们最开始的数组int[] nums=new int[]{1,2,3,4,5,6},我们看一下下面的过程是怎么调整数组为大根堆的。我们看数组的变化,123456,213456,321456,431256,541236,645231,调整完毕。

//初始堆大小是0,依次加入数让整个数组范围变成大根堆 
for(int i=0;i<nums.length;i++){
    heapInsert(nums,i);
}

我们看下,如果按照如下过程调整,数组是怎么变化的?变化过程是:123456,123456,123456,126453,156423,653421。所以不论向上还是向下,都可以将数组调整为大根堆。

    for(int i=nums.length-1;i>=0;i--){
        heapify(nums,i,nums.length);
    }

下面我们看下堆排序。

public void heapSort(int[] nums){
    if(nums==null||nums.length<2){
        return;
    }
    //1、我们考虑遍历数组,依次加入数,将数组调整为大根堆
    //时间复杂度是O(N*logN)
    for(int i=0;i<nums.length;i++){
        heapInsert(nums,i);
    }
    //2、除了方案一,我们知道堆本身就是用一段连续数组实现的结构,
//我们假设现在的数组就是一个堆,我们要做的是把它调整成一个大根堆,只需要从最后开始向下heapify
    /**
         * N个节点满二叉树
         * 叶子节点N/2,每个节点往下heapify,只需要看一眼 复杂度1
         * 再上一层N/4,每个节点往下heapify,最多移动2层
         * 再上一层N/8,最多移动3层
         * 所以时间复杂度为 T(N)=(N/2)+(2*N/4)+(3*N/8)+...
         * 2T(N)=(2*N/2)+(2*N/2)+(3*N/4)+(4*N/8)
         * T(N)=N+N/2+N/4+N/8+... 等比数列 求和,时间复杂度为O(N)
         */
    for(int i=nums.length-1;i>=0;i--){
        heapify(nums,i,nums.length);
    }
    int heapsize=nums.length;
    //经过上述调整,已经是大根堆了,
    while(--heapsize>0){
        //把最大位置的数放到数组最后一个位置,然后堆上释放最后一个元素
        swap(nums,0,heapsize);
        //然后堆上释放最后一个元素,因为0位置的数变了,重新调整堆,周而复始,
//直到堆大小为空,顺序就调好了
        heapify(nums,0,heapsize);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值