11.堆排序

本文深入探讨了堆排序的原理,通过实例展示了如何构建大根堆的过程,并提供了详细的Java代码实现。堆排序的时间复杂度分析表明其效率为O(N)。此外,还介绍了如何弹出大根堆的顶级元素,以及这一操作在解决实际问题中的应用。
摘要由CSDN通过智能技术生成

堆排序,形成的堆就是一颗完全二叉树 1

在这里插入图片描述

堆排序用于很多贪心算法的问题中

以数组形式表示二叉树

下标i的左孩子是2i+1,右孩子是2i+2,一个节点的父节点是(i-1)/2

大根堆

在这棵完全二叉树中任何一棵子树的最大值是头部

建立大根堆

给一个数组:2 1 3 6 0 4

我们首先人为规定这棵树只有一个节点2,依次增加一个节点并调整为大堆过程如下:演示过程使用graphviz,一个图一份源码

1
from graphviz import Digraph
dot = Digraph(comment='The Round Table')
dot.node('2', '2')
2
from graphviz import Digraph
dot = Digraph(comment='The Round Table')
dot.node('2', '2')
dot.node('1', '1')
dot.edges(['21'])
3
from graphviz import Digraph
dot = Digraph(comment='The Round Table')
dot.node('2', '2')
dot.node('1', '1')
dot.node('3', '3')
dot.edges(['21', '23'])

调整: 3和2节点交换

from graphviz import Digraph
dot = Digraph(comment='The Round Table')
dot.node('3', '3')
dot.node('1', '1')
dot.node('2', '2')
dot.edges(['32', '31'])
4
from graphviz import Digraph
dot = Digraph(comment='The Round Table')
dot.node('3', '3')
dot.node('1', '1')
dot.node('2', '2')
dot.node('6', '6')
dot.edges(['31', '32', '16'])

调整之后:

from graphviz import Digraph
dot = Digraph(comment='The Round Table')
dot.node('6', '6')
dot.node('3', '3')
dot.node('2', '2')
dot.node('1', '1')
dot.edges(['63', '62', '31'])
5
from graphviz import Digraph
dot = Digraph(comment='The Round Table')
dot.node('6', '6')
dot.node('3', '3')
dot.node('2', '2')
dot.node('1', '1')
dot.node('0', '0')
dot.edges(['63', '62', '31', '30'])
6
from graphviz import Digraph
dot = Digraph(comment='The Round Table')
dot.node('6', '6')
dot.node('3', '3')
dot.node('2', '2')
dot.node('1', '1')
dot.node('0', '0')
dot.node('4', '4')
dot.edges(['63', '62', '31', '30', '24'])

调整之后:

from graphviz import Digraph
dot = Digraph(comment='The Round Table')
dot.node('6', '6')
dot.node('3', '3')
dot.node('2', '4')
dot.node('1', '1')
dot.node('0', '0')
dot.node('4', '2')
dot.edges(['63', '62', '31', '30', '24'])

此时大根堆数组表示为:6 3 4 1 0 2

如上过程展示了建立大根堆的详细过程,代码如下:

package yzy.algorithm;
//大根堆
public class heapSort {
    public static void heapSort(int[] arr){
        if(arr == null || arr.length<2)
            return ;
        for(int i=0; i<arr.length; ++i)
            heapInsert(arr, i); // 0~i
    }
    public static void swap(int[] arr, int index1, int index2){
        arr[index1] ^= arr[index2];
        arr[index2] ^= arr[index1];
        arr[index1] ^= arr[index2];
    }
    //调整新增节点
    public static void heapInsert(int[] arr, int index) {
        while(arr[index] > arr[(index-1)/2]) { //精华语句,考虑了index=0时while终止
            swap(arr, index, (index - 1) >> 1);
            index = (index - 1) / 2;
        }
    }
    //数组中一个元素值发生变化,则调整使其继续成为大根堆
    public static void heapify(int[] arr, int index, int heapSize) {  //[0, heapSize]范围上形成的堆
        int left = index*2+1;
        while(left < heapSize){
            int largest = left + 1 < heapSize && arr[left+1] > arr[left]
                    ? left + 1
                    : left;
            largest = arr[largest] > arr[index] ? largest : index;
            if(largest == index)
                break;
            swap(arr, largest, index);
            index = largest;
            left = index*2+1;
        }
    }

    public static void showArr(int[] arr){
        int i = 0;
        while(i<arr.length)
            System.out.print(arr[i++]);
        System.out.println();
    }
    public static void main(String[] args) {
        int arr[] = {2, 1, 3, 6, 0, 4};
        showArr(arr);
        heapSort(arr);
        showArr(arr);
    }
}

建立大根堆的复杂度

每加入一个节点的时间复杂度就是,在加入这个节点之前完全二叉树的高度,在加入第i个节点时,时间复杂度就是
l o g 2 ( i − 1 ) log_2(i-1) log2(i1)
建立N个节点的大堆的过程时间复杂度就是
l o g 2 1 + l o g 2 2 + l o g 2 3 + . . . + + l o g 2 N − 1 log_2 1+log_2 2+log_2 3+...++log_2 N-1 log21+log22+log23+...++log2N1
收敛于N,最终时间复杂度就是O(N)

弹出大根

使用一个变量heapSize人为规定大根堆的大小,堆顶和最后一层最右的叶节点交换并且heapSize减一则表示弹出堆顶元素,heapify用于index位置节点改变的情况下调整二叉树依然为大根堆

package yzy.algorithm;
//大根堆
public class heapSort {
    public static void heapSort(int[] arr){
        if(arr == null || arr.length<2)
            return ;
        for(int i=0; i<arr.length; ++i)
            heapInsert(arr, i); // 0~i
    }
    public static void swap(int[] arr, int index1, int index2){
        arr[index1] ^= arr[index2];
        arr[index2] ^= arr[index1];
        arr[index1] ^= arr[index2];
    }
    //调整新增节点
    public static void heapInsert(int[] arr, int index) {
        while(arr[index] > arr[(index-1)/2]) { //精华语句,考虑了index=0时while终止
            swap(arr, index, (index - 1) >> 1);
            index = (index - 1) / 2;
        }
    }
    //数组中一个元素值发生变化,则调整使其继续成为大根堆
    public static void heapify(int[] arr, int index, int heapSize) {  //[0, heapSize]范围上形成的堆
        int left = index*2+1;
        while(left < heapSize){
            int largest = left + 1 < heapSize && arr[left+1] > arr[left]
                    ? left + 1
                    : left;
            largest = arr[largest] > arr[index] ? largest : index;
            if(largest == index)
                break;
            swap(arr, largest, index);
            index = largest;
            left = index*2+1;
        }
    }
    public static void showArr(int[] arr){
        int i = 0;
        while(i<arr.length)
            System.out.print(arr[i++]);
        System.out.println();
    }
    public static void main(String[] args) {
    //建立大根堆
        int arr[] = {2, 1, 3, 6, 0, 4};
        showArr(arr);
        heapSort(arr);
        showArr(arr);


        System.out.println("弹出大根堆顶级大根");
//        弹出大根
        int heapSize = arr.length;
        swap(arr, 0, --heapSize);  // 先将大根和最后一层最右的叶节点交换,heapSize同时减1
        heapify(arr, 0, heapSize);  // 将[0,heapSize)调整为大根堆
//      此时相比之前根弹出,有效大根堆范围时[0,heapSize)
        showArr(arr);


        System.out.println("循环依次弹出:");
//      循环依次弹出
        while(heapSize>0){
            swap(arr, 0, --heapSize);
            heapify(arr, 0, heapSize);
            showArr(arr);
        }
    }
}

观察以上弹出堆顶元素的操作,如果依次弹出堆顶元素直到人为规定的堆大小heapSize为0,就会发现数组元素从小到大依次有序,小根堆则同样可以使数组元素从大到小有序

掌握弹出堆顶元素,可以巧妙的解决很多问题,并且加速很多问题的算法时间复杂度,请继续观看后续博文


  1. 满二叉树、每一层从左往右依次补齐的一系列树都是(意思就是右孩子存在则左孩子必须先存在) ↩︎

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值