堆排序的JAVA实现及时间复杂度分析

本文详细介绍了堆排序的JAVA实现,包括建堆和堆排序的过程,并深入分析了堆排序的时间复杂度,主要分为初始化建堆的O(n)和后续排序的O(nlogn),最终得出总的时间复杂度为O(nlogn)。

堆排序是一个比较常用的排序方式,下面是其JAVA的实现:

1. 建堆

	// 对输入的数组进行建堆的操作
    private static void buildMaxHeap(int[] array, int length) {
        // 遍历所有的内部节点,每一个都进行siftdown操作
        for (int i = (int)Math.floor(length / 2 - 1); i >= 0; i--) {
            siftdown(array, length, i);
        }
    }
	
	private static void siftdown(int[] array, int length, int index) {
        // 1. 判断是否为叶子节点
        if (isLeaf(array, length, index)) {
            return;
        }

        // 2. 计算左右两个值的大小
        int max = Integer.MIN_VALUE;
        int maxIndex = index;
        // 判断该内部节点是否有两个子树
        if (2 * (index + 1) > length - 1) {
            max = array[2 * index + 1];
            maxIndex = 2 * index + 1;
        } else {
            // 对两个子树的值进行比较
            if (array[2 * index + 1] > array[2 * (index + 1)]) {
                max = array[2 * index + 1];
                maxIndex = 2 * index + 1;
            } else {
                max = array[2 * (index + 1)];
                maxIndex = 2 * (index + 1);
            }
        }

        // 3. 如需交换,交换后继续向下递归
        if (array[index] < array[maxIndex]) {
            array[maxIndex] = array[index];
            array[index] = max;
            siftdown(array, length, maxIndex);
        }
    }

    // 判断是否为叶子节点
    private static boolean isLeaf(int[] array, int length, int index) {
        return index > (int)Math.floor(length / 2 - 1);
    }

2. 堆排序

    private static void heapSort(int[] array) {
        buildMaxHeap(array, array.length);
        // 将堆顶的最大值每次都交换到最后,对剩余的数字进行新的建堆操作
        for (int i = 0; i < array.length; i++) {
            int temp = array[array.length - i - 1];
            array[array.length - i - 1] = array[0];
            array[0] = temp;
            buildMaxHeap(array, array.length - i - 1);
        }
    }

3. 时间复杂度

堆排序的时间复杂度主要有两个部分:

  • 初始化建堆
  • 每次取堆顶元素后,剩余部分的排序

因此,我们来分别看一下这两个部分的时间复杂度

  • 对于初始化建堆:我们需要对除了叶子节点这一层的其他节点都进行遍历,每一个点假设都递归到了叶子节点,需要 k - i 次siftdown操作,而每一层有 2^(i - 1) 个节点,(i代表层数,设根节点为第一层)即,我们需要计算 2^(i - 1) * (k - i) ,当 i 取值为 1 到 n - 1时的值的和。
    即求: ∑i=1n−1(n−i)2i−1\sum_{i=1}^{n-1} (n-i)2^{i-1}i=1n1(ni)2i1
    计算过程如下:
    ∑i=1n−1(n−i)2i−1=∑i=1n−1n∗2i−1−∑i=1n−1i∗2i−1\sum_{i=1}^{n-1} (n-i)2^{i-1} = \sum_{i=1}^{n-1} n*2^{i-1} - \sum_{i=1}^{n-1} i*2^{i-1}i=1n1(ni)2i1=i=1n1n2i1i=1n1i2i1
    对于前半部分:
    ∑i=1n−1n∗2i−1=n∗∑i=1n−12i−1=n∗1∗(1−2n−1)1−2=n∗2n−1−n\sum_{i=1}^{n-1} n*2^{i-1} = n*\sum_{i=1}^{n-1} 2^{i-1} = n * \frac{1*(1-2^{n-1})}{1-2}=n*2^{n-1} - ni=1n1n2i1=ni=1n12i1=n121(12n1)=n2n1n
    对于后半部分:
    Sn=∑i=1n−1i∗2i−1=20+2∗21+3∗22+...+(n−1)∗2n−2S_n = \sum_{i=1}^{n-1} i*2^{i-1}=2^0+2*2^1+3*2^2+...+(n-1)*2^{n-2}Sn=i=1n1i2i1=20+221+322+...+(n1)2n2
    2∗Sn=2∗∑i=1n−1i∗2i−1=21+2∗22+3∗23+...+(n−1)∗2n−12* S_n = 2*\sum_{i=1}^{n-1} i*2^{i-1}=2^1+2*2^2+3*2^3+...+(n-1)*2^{n-1}2Sn=2i=1n1i2i1=21+222+323+...+(n1)2n1
    Sn−2∗Sn=−Sn=20+21+22+...+2n−2−(n−1)∗2n−1=2n−1−n∗2n−1S_n - 2* S_n =-S_n=2^0+2^1+2^2+...+2^{n-2}-(n-1)*2^{n-1}=2^n-1-n*2^{n-1}Sn2Sn=Sn=20+21+22+...+2n2(n1)2n1=2n1n2n1
    即,两部分相加,有:
    n∗2n−1−n+2n−1−n∗2n−1=2n−1−nn*2^{n-1} - n+2^n-1-n*2^{n-1}=2^n-1-nn2n1n+2n1n2n1=2n1n
    由于,n代表的是层数,它可以由 n = log N 求得,代入,有:N - 1 - log N,故时间复杂度 O(N) = N
  • 第二部分,是关于排序的,我们交换堆顶元素不断放置到堆的末尾。即堆的元素是越来越少,我们比较的次数也减少,即求:
    log(n−1)+log(n−2)…+log3+log2≈log(n!)log(n-1)+log(n-2)…+log3+log2≈log(n!)log(n1)+log(n2)+log3+log2log(n!)
    可以证明log(n!)和nlog(n)是同阶函数:
      ∵(n/2)n/2≤n!≤nn∴n/2log⁡(n/2)≤log⁡(n!)≤nlog⁡(n)∵(n/2)^{n/2}≤n!≤n^{n} ∴n/2\log(n/2) \leq \log(n!) \leq n\log(n) (n/2)n/2n!nnn/2log(n/2)log(n!)nlog(n)
     
    即可以知道第二部分的时间复杂度为O(nlogn)

将两部分结合起来,我们可以获得时间复杂度为O(n) = O(n + nlogn) = O(nlogn)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值