一文带你读懂【堆排序】

本文深入解析了堆数据结构,包括大根堆和小根堆的定义,堆的构建过程,以及如何使用堆进行排序。通过数组表示堆,并详细介绍了添加和删除元素的操作。此外,还讨论了Java中的PriorityQueue以及堆排序的实现细节,包括从无序数组到堆化的转换和借助堆进行排序的步骤。
摘要由CSDN通过智能技术生成

一、堆的定义

数据结构中的堆,不要和 jvm 中的堆空间搞混了

数据结构中的堆,分为大根堆小根堆,其表现形式是一棵完全二叉树,区别在于,对于大根堆,其每个节点的值都大于等于其所有子节点的值,小根堆反之

那什么是完全二叉树呢?,即除了叶子层外,其他部分必须是满二叉树,且所有叶子必须从左到右连续排列

那什么是满二叉树呢?,即除了叶子节点外,每个节点都有左右孩子的二叉树

在接下来的讲解中,如无特殊说明,我都是用大根堆来进行讲解的,相信聪明的你在看懂大根堆后,很快就写出小根堆的代码的

大根堆:

大根堆

小根堆:

小根堆

二、堆的构建

1、数据结构选型

构建,无非是增删两种情况

在构建之前,我们先考虑用什么数据结构来表示堆

很多初学者第一反应可能是用树来表示,毕竟上面的图展现的就是一棵树的形式

但是,我们来考虑一下堆的应用场景,经常是要取出最大的根(把树的根节点提出来),然后让剩下的部分再次形成一个大根堆

这种场景下,用树这种数据结构显然不合理,因为树的非叶子节点的删除和转移十分麻烦

那该怎么办?

此时,我们来看一看一棵树广度遍历下的性质:

广度遍历

可以看到,如果将根节点标号为1,那么每个节点 k 的左右节点的编号分别为 2k2k+1

左右孩子下标

并且因为堆是一棵完全二叉树,所以其广度遍历的顺序下,所有节点的标号是连续的,即对于有 n 个节点的堆,其节点的广度遍历顺序标号为 1-n

这样连贯的顺序,自然而然让我们想到数组

所以,我们一般都用数组表示堆

2、初始化

public class Heap {
   
    // 存放堆数据
    private int[] heap;
    // 末尾元素下标(从1开始)
    private int index;
    // 记录最大元素个数
    private int capacity;

    public Heap(int capacity) {
   
        this.capacity = capacity;
        this.index=0;
        this.heap=new int[capacity+1];
    }
}
  • int[] heap :表示堆的数组**(因为我们的根节点标号为 1 ,所以表示堆的数组,我们要多申请一个空间)**
  • int capacity:表示堆的最大容量
  • int index:表示堆的最后一个节点(堆为空的时候,index==0)

这里要多插一句,为什么我们的下标要从 1 开始,理由有 :

**1、**这样左右孩子的下标,就是 2k 和 2k+1(如果从0开始的话,左右孩子下标是 2k+1 和 2k+2)

**2、**末尾节点的下标,同时也是节点的个数,在编写的时候不容易出错

因为不是堆排序,我们不是要进行原地排序,所以数组的排列可以按照我们的意愿来,怎么方便、怎么不容易出错,就怎么来

index的意义

3、添加元素

对于添加元素,我们的做法是:

1、将新元素插入到堆的最后一个位置(要满足完全二叉树的性质)

2、将这个新的节点,与其父节点进行比较,如果大于父节点,就与父节点进行交换,直到为根或者父节点大于它为止

添加元素后,交换的过程

		// 大根堆添加元素
    public boolean add(int num) {
   
        // 堆已经满了
        if (index>=capacity) return false;
      	// 添加了一个元素,最后一个元素的下标要加1了
        index++;
        heap[index]=num;
        int curr = this.index;
				// 将新插入的元素,不断与其父节点进行比较,直到符合堆的定义为止
        while (curr/2>=1 && heap[curr]>heap[curr/2]) {
   
          	// swap 的代码,会在最后的完整代码中给出
            swap(curr,curr/2);
            curr/=2;
        }
        return true;
    }

完整代码如下:

public class Heap {
   
    // 存放堆数据
    private int[] heap;
    // 末尾元素下标(从1开始)
    private int index;
    // 记录元素个数
    private int capacity;

    public Heap(int capacity) {
   
        this.capacity = capacity;
        this.index=0;
        this.heap=new int[capacity+1];
    }

    // 大根堆添加元素
    public boolean add(int num) {
   
        // 堆已经满了
        if (index>=capacity) return false;
        index++;
        heap[index]=num;
        int curr = this.index;
        while (curr/2>=1 && heap[curr]>heap[curr/2]) {
   
            swap(curr,curr/2);
            curr/=2;
        }
        return true;
    }

    // 交换堆内元素位置
    private void swap(int x,int y) {
   
        checkInBound(x,y);
        heap[x]=heap[y]-heap[x];
        heap[y]=heap[y]-heap[x];
        heap[x]=heap[y]+heap[x];
    }

    private void checkInBound(int... indexes) {
   
        for (int i : indexes) {
   
            checkInBound(i);
        }
    }

    // 不在范围内,抛出异常
    private void checkInBound(
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FARO_Z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值