排序(三):谁主沉浮——堆与堆排序

本文深入探讨了堆这一数据结构,包括堆的定义、性质、建立方法以及堆排序的过程。通过筛选法和插入法两种方式展示了如何构建和调整堆,并提供了相应的代码示例。堆排序的时间复杂度为O(nlogn),具有较高的稳定性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

leetcode:https://leetcode-cn.com/problems/sort-an-array/

定义&&性质

堆(heap)是计算机科学中一类特殊的数据结构的统称,通常是一个可以被看作是一棵树的数组对象。

定义:n 个元素的序列 {k1,k2,ki,…,kn} 当且仅当满足下关系时,称之为堆。 k i ≤ k 2 i k_i \le k_{2i} kik2i k i ≤ k 2 i + 1 k_i \le k_{2i+1} kik2i+1 或者 k i ≥ k 2 i k_i \ge k_{2i} kik2i k i ≥ k 2 i + 1 k_i \ge k_{2i+1} kik2i+1。若以一维数组存储此序列,并将数组视为完全二叉树,可知非叶子节点的值均不大于(或不小于)其左右子节点的值,堆顶元素(即二叉树的根)必为序列中 n 个元素的最小值。

性质

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

堆的建立方法

筛选法:下调

设堆共有 n 个节点,深度为 l o g 2 ( n ) log_2(n) log2(n),设为 k。按照从下到上,从右到左的方法找到第一个非叶子节点 i(索引为 n/2)开始调整,使以该节点为根节点的子树为大顶堆。

调整策略

  • 取左右子节点中较大的一个与节点 i 比较;
  • 若大于节点 i,父子交换值。若子节点仍有子节点(即节点 i 有孙子节点),则继续向下调整,确保以子节点为根节点的子树为大顶堆;
  • 若小于节点 i,说明当前子树为大顶堆,结束。

根据树的相关性质,又有:

  • 第 i 层最多有节点 2 i − 1 2^{i -1} 2i1 个;
  • 非叶子节点的深度为 [0, k - 1] 即 [0, l o g 2 n − 1 log_2 n-1 log2n1]
  • 利用筛选法建堆,每层每个节点最多比较两次——父节点与两个子节点比较
  • 第 k - 1 层的节点最多下调一层,第 k - i 层的节点最多下调 i 层…以此类推。

所以, S ( k ) = ∑ i = 1 k − 1 2 i − 1 ∗ ( k − i ) ∗ 2 S(k) = \sum_{i = 1}^{k-1}2^{i-1}*(k-i)*2 S(k)=i=1k12i1(ki)2,时间复杂度 A ( n ) = O ( n ) A(n) = O(n) A(n)=O(n)
请添加图片描述

插入法:上浮

调整策略

  • 插入新元素时,只与它的父节点比较,无需和兄弟节点比较,上调一层,比较一次。二叉树第 i 层最多有 2 i − 1 2^{i-1} 2i1 个节点,每个节点最多上调 i - 1。

所以, S ( k ) = ∑ i = 2 k 2 i − 1 ∗ ( i − 1 ) S(k) = \sum_{i = 2}^{k}2^{i-1}*(i-1) S(k)=i=2k2i1(i1),时间复杂度 A ( n ) = O ( n l o g n ) A(n) = O(nlogn) A(n)=O(nlogn)
请添加图片描述

堆排序

过程

堆排序的基本思想是:

  • 将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。
  • 将其与末尾元素进行交换,此时末尾元素为最大值。
  • 然后将剩余 n-1 个元素重新构造成大顶堆,得到 n 个元素的次大值。如此反复执行,最终得到一个有序序列。

代码

筛选法建堆后堆排序:

class Solution {
    public int[] sortArray(int[] nums) {
        int len = nums.length;
        for(int i = len/2 - 1; i >= 0; i--){
            AdjustHeap(nums, i, len);
        }
        for(int j = len - 1; j > 0; j--){
            int temp = nums[j];
            nums[j] = nums[0];
            nums[0] = temp;
            AdjustHeap(nums, 0, j);
        }
        return nums;
    }

    public void AdjustHeap(int[] nums, int i, int size){
        int temp = nums[i];
        for(int child = 2*i+1; child < size; child = 2*child+1){
            // 选择较大的子节点进行比较
            if(child+1 < size && nums[child+1] > nums[child])
                child++;
            // 父节点小于子节点,向下继续调整
            if(temp < nums[child]){
                nums[i] = nums[child];
                i = child;
            }
            // 父节点大于子节点,符合大顶堆,跳出循环
            else
                break;
        }
        nums[i] = temp;
    }
}

插入法建堆后堆排序:

class Solution {
    int copy[] = new int[50000];  

    public int[] sortArray(int[] nums) {
        int len = nums.length;
        for(int i = 0; i < len; i++)
            BuildHeap(nums, i);
        for(int j = len - 1; j > 0; j--){
            swap(copy, j, 0);
            AdjustHeap(copy, 0, j);
        }
        for(int i = 0; i < len; i++)
            nums[i] = copy[i];
        return nums;
    }

    public void BuildHeap(int[] nums, int i){
        copy[i] = nums[i];
        // 子节点与父节点比较,如果大于父节点就上调
        // 父节点索引(i-1)/2
        while(i > 0 && copy[(i-1)/2] < copy[i]){
            swap(copy, (i-1)/2, i);
            i = (i-1)/2;
        }
    }

    public void AdjustHeap(int[] nums, int i, int size){
        /*
        代码见筛选法建堆
        */
    }
}

时间复杂度

堆排序的最优、最差和平均时间复杂度都为 O ( n l o g n ) O(nlogn) O(nlogn) ,是常用的排序算法中最稳定的排序方法,实验表明堆排序的稳定性优于归并排序。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值