问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

优先队列、最小堆、最大堆详解

创作时间:
作者:
@小白创作中心

优先队列、最小堆、最大堆详解

引用
CSDN
1.
https://blog.csdn.net/m0_64132144/article/details/145528617

优先队列是一种特殊的队列,支持以下两种操作:

  • 插入元素:将一个元素插入到队列中。
  • 取出最高优先级元素:从队列中取出具有最高优先级的元素。

堆是一种特殊的树形数据结构,它满足以下两个条件:

  • 堆是一棵完全二叉树。
  • 堆中的每个节点都满足堆的性质。

堆的性质分为两种:

  • 最小堆:每个节点的值都小于或等于其子节点的值。
  • 最大堆:每个节点的值都大于或等于其子节点的值。

优先级队列允许在任何时候访问其中具有最高优先级的元素。最小堆允许在任何时候访问其中具有最小值的元素。

堆和优先队列

在实现优先级队列时,最常用的方法是使用最小堆,可以说最小堆是优先级队列的一种实现方式。优先级队列中的元素被存储在最小堆中,其中每个元素的优先级由其值确定。这样,队列中具有最高优先级的元素就是最小堆中的根节点。每当需要访问具有最高优先级的元素时,只需访问最小堆的根节点即可。

堆的根节点就是具有最高优先级的元素——每个节点的值都小于或等于其子节点的值。

创建优先队列

Java 中的优先队列可以使用 PriorityQueue 类来创建。默认情况下,优先队列中的元素按照自然顺序进行排序,也可以通过自定义比较器来进行排序。

PriorityQueue<Integer> pq = new PriorityQueue<>();

这样创建的优先队列中,元素的类型是 Integer,元素的顺序是从小到大的。

如果要创建一个元素顺序为从大到小的优先队列,可以使用以下语句:

PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> b.compareTo(a));

元素的类型是 Integer,元素的顺序是从大到小的,比较器使用了一个 lambda 表达式(比较器),将元素从大到小排列。

Lambda表达式的语法是

(参数列表) -> {函数体}

PriorityQueue pq = new PriorityQueue<>(lists.length, (a, b)->(a.val - b.val));

在这里参数列表是

(a, b)

,表示比较的两个元素,函数体是

(a.val - b.val)

,表示比较的规则,即按照节点的值从小到大排序

完全二叉树与优先级队列

完全二叉树(Complete Binary Tree)是一种二叉树,其中除了最后一层,其他层的节点数都是满的,最后一层的节点都靠左对齐

        1
      /   \
     2     3
    / \   /
   4   5 6

堆与完全二叉树

堆是一种特殊的树形数据结构,它满足以下两个条件:

  • 堆是一棵完全二叉树。
  • 堆中的每个节点都满足堆的性质。

堆的性质分为两种:

  • 最小堆:每个节点的值都小于或等于其子节点的值。
  • 最大堆:每个节点的值都大于或等于其子节点的值。

下面这是一颗小顶堆:

最小堆的根节点是整个堆中的最小值。

      1
     / \
    2   3
   / \ / \
  4  5 6  7

下面这是一颗大顶堆。

最大堆的根节点是整个堆中的最大值。

            8
          /   \
         7     5
        / \   / \
       6   4 2   1

数组转换为堆

在存储树结构时,常见的是使用数组来存放,而不需要使用指针等额外的空间。在数组中,堆的根节点存储在下标为1的位置,而其余节点按照从上到下、从左到右的顺序存储。这样,对于下标为i的节点,其左子节点的下标为2i,右子节点的下标为2i+1,而其父节点的下标为i/2。

      1
     / \
    2   3
   / \ / \
  4  5 6  7

其实排列顺序就是从上到下,从左到右进行排列,例如{1,2,3,4,5,6,7},看第一张图就是按照顺序进行排列,当然对照下面公式也可以来做,例如 1 的索引为0,要确定它的左子节点就是 0* 2 +1 = 1,也就是索引1,对应元素值2,同理右子节点就是 0*2+2=2,索引2也就是元素值3。

  • 对于下标为i的节点:
  • 左子节点的下标为2i+1
  • 右子节点的下标为2i+2
leftNo = parentNo*2+1
rightNo = parentNo*2+2
parentNo = (nodeNo-1)/2

当然上面按照顺序排列是第一步,实际上在转换为小顶堆或大顶堆,还需要进行调整。

  • 向下调整(堆化):从根结点开始,将其与两个子结点中较小者交换,直到堆的性质恢复为止。

例如假设有一个数组arr=[20, 10, 15, 30, 40],现在要将其转化为一个小顶堆

首先,我们将数组按照完全二叉树的形式排列,在数组中堆的根节点存储在下标为1的位置,其余节点按照从上到下、从左到右的顺序存储。如下图所示:

      20
     /  \
   10    15
  /  \
30   40

从上往下、从左往右依次给每个节点编号,如下所示:

      1
     / \
    2   3
   / \
  4   5

下面需要判断是否是小顶堆,可知根节点20并不是最小,那么需要与其子节点最小值进行交换

      10
     /  \
   20    15
  /  \
30   40

对应的数组为[10, 20, 15, 30, 40],符合小顶堆的定义,即每个节点的值都小于或等于其子节点的值。

当然常见的笔试题中,还有出下面这样的:

最小堆删除堆顶(即根结点)元素的操作步骤:

  1. 删除堆顶元素:将根结点(数组第一个元素)删除;
  2. 用最后一个元素填补空位:把堆中最后一个元素放到根结点的位置;
  3. 向下调整(堆化):从根结点开始,将其与两个子结点中较小者交换,直到堆的性质恢复为止。
      0
     /  \
    2    1
   /  \   /  \
  4    3  9   5
 /  \  /
8   6  7

按照上面流程,由末尾元素7代替0的位置,此时判断是否是小顶堆

      7
     /  \
    2    1
   /  \   /  \
  4    3  9   5
 /  \
8   6

依次堆化,最终结构如下:

      1
     /  \
    2    5
   /  \   /  \
  4    3  9   7
 /  \
8   6

对应就是{1,2,5,4,3,9,7,8,6}

最小堆实现优先队列

好,我们画幅图再来理解一下。

下面我们看新增一个元素,最小堆实现的优先队列将作如何变化:

因为要维持小根堆的根节点最小,4<9 需要进行交换,4<5 需要进行交换,4>3 不需要进行交换

在优先级队列新增元素,实际上是有两个Api方法:add()和 offer()

它们都是向优先队列中插入元素,只是Queue接口规定二者对插入失败时的处理不同:

  • add()在插入失败时抛出异常
  • offer()会返回false

最小堆允许在任何时候访问其中具有最小值的元素。

优先级队列允许在任何时候访问其中具有最高优先级的元素。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号