排序之——堆排序

253 阅读2分钟

堆排序

1、定义

众所周知,有一种二叉树叫排序二叉树,他要求每一颗子树的左子树的节点值要小于(或大于)根节点,右子树节点值要大于(小于)根节点值,并且每一颗子树都是排序二叉树。 堆和排序二叉树有相似的地方,他要求每一颗子树的根节点都大于或者小于它的左右子树的节点值,其中每一颗子树的根节点值大于左右子树称之为大根堆,反之就是小根堆 因此堆排序就是将数组的值构建为大(小)根堆,然后取走堆顶元素,将堆底元素放置堆顶后重新调整堆结构让它成为大(小)根堆,继续重复操作直至完成排序的过程。

2、代码设计

用数组[4,5,7,3,2,1,6,8]构建大根堆来举例

2.1、构建堆结构

构建堆结构有两种,上浮下沉

2.1.1、上浮

上浮是从堆顶开始,不断的循环判断自己和父节点的值,如果大于了父节点,那么父节点就不配做父节点,自己就得上浮替代父节点。

<<< 左右滑动见更多 >>>

public void buildMaxHeapSort(int[] nums){
  for (int i = 0; i < nums.length; i++) {
    ascend(i,nums);
  }
}
private void ascend(int i, int[] nums) {
    //从第二个节点开始
    //索引从1开始
    while (i>0 && nums[i] > nums[(i+1)/2-1]){
            swap(nums,i,(i+1)/2-1);
            //将自己的索引同样上浮
            i = i / 2;
        }
}

2.1.2、下沉

下沉是从堆底的父节点开始,不断判断自己和左右子树的节点值,如果小于就得下沉父节点作子节点。

<<< 左右滑动见更多 >>>

public void buildMaxHeap(int[] nums){
  //
  for (int i = (int)Math.floor(nums.length)-1 ; i >=0; i--) {
    sink(nums,i,nums.length-1);
  }
}
private int left(int x) {return x*2;}
private int right(int x) {return x*2+1;}
private void sink(int[] nums, int start, int end) {
    //要交换的索引位置  
    //注意索引是从1开始
    int maxIndex;
    while (left(start+1)-1 <= end){
        maxIndex = left(start+1-1;
        //(right(start+1) -1)<= end 右子树是否存在
        //nums[right(start+1)-1] > nums[maxIndex]判断左右子树谁的值更大,大的那个当作maxIndex
        if(right(start+1-1 <= end && nums[right(start+1)-1] > nums[maxIndex])  maxIndex = right(start+1)-1;
        //再去和根节点比较,满足条件进行交换
        if(nums[start] < nums[maxIndex]){
            swap(nums,start,maxIndex);
            //同时将节点索引不断下沉比较
            start = maxIndex;
        }else {
            break;
        }
    }
}

下沉和上浮区别在于一个是从堆底的父节点开始向前遍历,一个是从堆顶向后遍历,时间复杂度都是O(n)

2.2、堆排序

构建大根堆之后,堆顶是最大的元素,这时候我们只需要把堆顶和堆底元素进行交换,然后在n-1的范围内重新构建大根堆,再交换,重复多次直到达到排序。

<<< 左右滑动见更多 >>>

由图,往后不停交换即可得到排序效果(后图大家自行脑补,懒得画了~~)

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

这里除了维护堆结构之外,范围也需要维护,拿掉一个元素时候范围缩小一个单位。

2.1.3、完整代码

public void headSort(int[] nums){
        
        //构建大顶堆
        buildMaxHeap(nums);
        //交换排序
        for (int i = nums.length -1 ; i > 0; i--) {
            swap(nums,0,i);
            sink(nums,0,i - 1);
        }

    }

    private void buildMaxHeap(int[] nums) {
        for (int i = (int)Math.floor(nums.length/2); i >= 0i--) {
            sink(nums, i,  nums.length-1);
        }
    }

    private int left(int x) {return x*2;}
    private int right(int x) {return x*2+1;}
    private void sink(int[] nums, int start, int end) {
        int maxIndex;

        while (left(start+1)-1 <= end){
            maxIndex = left(start+1) -1;
            if(right(start+1) -1 <= end && nums[right(start+1)-1] > nums[maxIndex])  maxIndex = right(start+1)-1;
            if(nums[start] < nums[maxIndex]){
                swap(nums,start,maxIndex);
                start = maxIndex;
            }else {
                break;
            }
        }
    }

觉得写的好可以关注下公众号,可以免费领大数据,java,资料~~