数据结构——优先级队列(堆)

目录

一:优先级队列

[1.1 概念](#1.1 概念)

[二 :优先级队列的模拟实现](#二 :优先级队列的模拟实现)

[2.1 堆的概念](#2.1 堆的概念)

[2.2 堆的存储⽅式](#2.2 堆的存储⽅式)

[2.3 堆的创建](#2.3 堆的创建)

[1 堆向下调整](#1 堆向下调整)

[2 堆的创建](#2 堆的创建)

2.4堆的核心操作

[1 入队列](#1 入队列)

[2 出队列](#2 出队列)

[3 取队首元素,下标为[0]的元素](#3 取队首元素,下标为[0]的元素)

三:常⽤接⼝介绍

[3.1 PriorityQueue的特性](#3.1 PriorityQueue的特性)

[3.2 插⼊/删除/获取优先级最⾼的元素](#3.2 插⼊/删除/获取优先级最⾼的元素)

四.:堆的应用

[4.1 PriorityQueue的实现](#4.1 PriorityQueue的实现)

[4.2 堆排序](#4.2 堆排序)

五:总结


一:优先级队列

是一个特殊的队列,正常的队列,是先进先出这样的顺序,

优先级队列,出队列的顺序和入队列不一样,是把队列中的每个元素,都约定了"优先级"出队列的时候把优先级最高的元素,先出队列~~

类似医院排队,紧急的肯定插队

1.1 概念

前⾯介绍过队列,队列是⼀种先进先出(FIFO)的数据结构 ,但有些情况下,操作的数据可能带有优先级,⼀般出队列时,可能需要优先级⾼的元素先出队列,该中场景下,使⽤队列显然不合适,⽐如:在⼿机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话;初中那会班主任排座位时可能会让成绩好的同学先挑座位。

在这种情况下,数据结构应该提供两个最基本的操作,⼀个是返回最⾼优先级对象,⼀个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)。

二 :优先级队列的模拟实现

JDK1.8中的PriorityQueue底层使⽤了堆这种数据结构,⽽堆实际就是在完全⼆叉树的基础上进⾏了⼀些调整。

2.1 堆的概念

如果有⼀个关键码的集合K = {k0,k1, k2,...,kn-1},把它的所有元素按完全⼆叉树的顺序存储⽅式存储在⼀个⼀维数组中,并满⾜:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2...,则称为⼩堆(或⼤堆)。将根节点最⼤的堆叫做最⼤堆或⼤根堆,根节点最⼩的堆叫做最⼩堆或⼩根堆。

堆的性质:

堆中某个节点的值总是不⼤于或不⼩于其⽗节点的值;

堆总是⼀棵完全⼆叉树。
对上面的概念的解释

1数组中

设父节点下标是parent

左子树的下标就是2parent+1

右子树的下标就是2parent+2

设子节点下标为child

父节点下标为(child-1)/2

2对于堆来说,任何一个节点的值,都小于(或大于两个节点的值)(两个子节点谁大谁小没关系)

就有小堆/小根堆 大堆/大根堆

2.2 堆的存储⽅式

从堆的概念可知,堆是⼀棵完全⼆叉树,因此可以层序的规则采⽤顺序的⽅式来⾼效存储,

针对完全二叉树,可以不使用孩子表示法

而是通过更简单的数组的方式来表示

把这个树层序遍历,遍历的结果,依次添加到数组中,中间如果遇到空节点,把空节点也添加到数组中

注意:对于⾮完全⼆叉树,则不适合使⽤顺序⽅式进⾏存储,因为为了能够还原⼆叉树,空间中必须要存储空节点,就会导致空间利⽤率⽐较低。

将元素存储到数组中后,可以根据⼆叉树章节的性质5对树进⾏还原。假设i为节点在数组中的下标,则有:

如果i为0,则i表⽰的节点为根节点,否则i节点的双亲节点为 (i - 1)/2

如果2 * i + 1 ⼩于节点个数,则节点i的左孩⼦下标为2 * i + 1,否则没有左孩⼦

如果2 * i + 2 ⼩于节点个数,则节点i的右孩⼦下标为2 * i + 2,否则没有右孩⼦

2.3 堆的创建

1 堆向下调整

对于集合{ 27,15,19,18,28,34,65,49,25,37 }中的数据,如果将其创建成堆呢?

仔细观察上图后发现:根节点的左右⼦树已经完全满⾜堆的性质,因此只需将根节点向下调整好即

可。

1:先确定要比较的子节点是那个

a)如果只有左子树,要比较的只有这个左子树

b)如果同时有左右子树,先比较左右子树更小的那个

2:拿着刚才更小的左右子树的节点和根节点比较和交换

如果发先子节点比根节点大,那就交换两个节点的值,

这个是向下调整的变换图

构建堆的基本思路:

1.从最后一个非叶子节点,从后往前遍历,针对每个节点进行向下调整

2.所谓的向下调整,找出子树中的最小值,如果根节点大,就和刚才的子树节点交换,持续往下进行直到 根节点比子树小,或者没有子树的时候

java 复制代码
package priotityqeue;  
  
import java.util.Arrays;  
  
public class Heap {  
  
    //向下调整(小堆)  
    //array 要调整的堆  
    //index 要调整的位置  
    public static void shiftDown(int[] array, int index) {  
        //要调整的父节点  
        int parent= index;  
        //计算要调整的左孩子节点  
        int child=2*parent+1;  
        //如果子节点超出范围,说明子节点不存在  
        //主循环结束,调整完成  
        while(child<array.length){  
            //如果右子树比左子树小,则定位到右子树  
            if(child+1<array.length && array[child]>array[child+1]){  
                child=child+1;  
            }  
            //然后比较child与parent的大小  
            if(array[parent]<=array[child]){  
                break;  
            }else{  
                //如果parent大于child,则交换两者的值  
                int temp=array[parent];  
                array[parent]=array[child];  
                array[child]=temp;  
                //更新parent和child的值  
                parent=child;  
                child=2*parent+1;  
            }  
            }  
            }  
  
            //创建的堆  
            public static void createHeap(int[] array){  
            //找到最后一个非叶子节点,从后从后往前向下调整  
               int lastLeaf=array.length-1;  
               int lastparent=(lastLeaf-1)/2;  
               for(int i=lastparent;i>=0;i--){  
                   shiftDown(array,i);  
               }  
            }  
    public static void main(String[] args) {  
        int[] array={4,2,7,1,3,9,8,5,6};  
        createHeap(array);  
        System.out.println(Arrays.toString(array));  
    }  
        }

2 堆的创建

java 复制代码
//创建的堆  
public static void createHeap(int[] array){  
//找到最后一个非叶子节点,从后从后往前向下调整  
   int lastLeaf=array.length-1;  
   int lastparent=(lastLeaf-1)/2;  
   for(int i=lastparent;i>=0;i--){  
       shiftDown(array,i);  
   }  
}

详细讲解:

  1. 找到最后一个叶子节点:
java 复制代码
 int lastLeaf = array.length - 1;

这里 `array.length - 1` 计算的是数组的最后一个元素的索引。因为数组的索引是从0开始的,所以最后一个元素的索引就是数组长度减1。

  1. 找到最后一个非叶子节点:
java 复制代码
 int lastparent = (lastLeaf - 1) / 2;

`lastLeaf - 1` 计算的是倒数第二个节点的索引。

  • `(lastLeaf - 1) / 2` 计算的是倒数第二个节点的父节点的索引。

  • 最后一个非叶子节点的索引就是 `(lastLeaf - 1) / 2`,因为叶子节点在完全二叉树中没有子节点。

  1. 从最后一个非叶子节点开始向前遍历,逐个向下调整:、
java 复制代码
    for (int i = lastparent; i >= 0; i--) {
        shiftDown(array, i);
    }

这个循环从 `lastparent` 开始,向前遍历到数组的第一个节点(索引为0),对每个节点调用 `shiftDown` 方法。

  • `shiftDown` 方法的作用是将一个节点与其子节点进行比较和交换,以满足小堆的性质。具体来说,它会确保当前节点的值不大于它的左右子节点的值。

详细解释

  • **完全二叉树的特性**:在完全二叉树中,除了最后一层外,每一层都被完全填满,并且最后一层的节点都尽可能地靠左。对于一个完全二叉树,如果节点的索引为 `i`,那么它的左子节点的索引是 `2 * i + 1`,右子节点的索引是 `2 * i + 2`。

  • **`shiftDown` 方法的作用**:这个方法会将一个节点与其子节点进行比较。如果当前节点的值大于其子节点的值,则进行交换,并继续向下调整,直到满足小堆的性质。

  • **`createHeap` 方法的作用**:这个方法通过从最后一个非叶子节点开始,向前遍历并调用 `shiftDown` 方法,将整个数组调整成一个小堆。

创建堆的复杂度为O(N)记住就行了,推导过程还是比较复杂度,博主也也也也困难

2.4堆的核心操作

1 入队列

数组,把新数组添加到数组末尾

但是添加的元素,会打破堆原有的规则

针对这个新来的元素,进行向上调整

java 复制代码
从最后一个元素的位置出发
找到该节点的父节点(child-1)/2
比较父节点和字子节点的大小:
如果父节点小,说明新来的元素没有破坏堆,也满足不了堆的性质,不需要额外的操作
如果父节点大,就要交换两个元素
//向上调zheng 整个数组只有最后一个元素不满足堆结构,所以从最后一个元素开始向上调整  
 public static void shiftUp(int[] array, int index){  
     int child=index;  
     int parent = (child-1)/2;  
     while(child>0){  
         //如果parent大于child,则交换两者的值  
         if(array[parent]>array[child]){  
             int temp=array[parent];  
             array[parent]=array[child];  
             array[child]=temp;  
             //更新parent和child的值  
             child=parent;  
             parent=(child-1)/2;  
         }else{  
             break;  
         }  
           
     }  
 }

2 出队列

java 复制代码
队首就是[0]的元素,需要把这个元素删除
如果直接删除根节点,那就破坏堆结构了,所以我们的解决方案为:
1:把根节点和最后一个节点的值,进行交换
2:size--,此时最后一个元素就被删除了
3:从根节点开始向下调整即可
public class MypriorityQueue {  
    private int[] arr;  
    private int size;  
  
    public MypriorityQueue() {  
        arr = new int[10];  
    }  
    public void offer(int val){  
        //先把新元素尾插到数组末尾  
        arr[size]=val;  
        size++;  
        //尾插之后,然后进行想上调用  
        Heap.shiftUp(arr,size-1);  
    }  
//出队列
    public Integer poll() {  
        if (size == 0) {  
            return null;  
        }  
        //把堆顶元素和最后一个元素交换,然后把最后一个元素删除  
        int result = arr[0];  
        arr[0]=arr[size-1];  
        size--;  
        Heap.shiftDown(arr,size,0);  
        return result;  
    }  

3 取队首元素,下标为[0]的元素

java 复制代码
直接取就可以
    public Integer peek() {  
        if (size == 0) {  
            return null;  
        }  
        return arr[0];  
    }  
  
    @Override  
    public String toString() {  
        return "MypriorityQueue{" +  
                "arr=" + Arrays.toString(arr) +  
                ", size=" + size +  
                '}';  
    }  
  
    public static void main(String[] args) {  
        MypriorityQueue queue = new MypriorityQueue();  
        queue.offer(5);  
        queue.offer(3);  
        queue.offer(7);  
        queue.offer(1);  
        queue.offer(4);  
        System.out.println(queue);  
        System.out.println(queue.poll());  
        System.out.println(queue.peek());  
        System.out.println(queue);  
  
    }  
}

三:常⽤接⼝介绍

3.1 PriorityQueue的特性

Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,

PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,本⽂主要介绍

PriorityQueue。

关于PriorityQueue的使⽤要注意:

  1. 使⽤时必须导⼊PriorityQueue所在的包,即:

import java.util.PriorityQueue;

  1. PriorityQueue中放置的元素必须要能够⽐较⼤⼩,不能插⼊⽆法⽐较⼤⼩的对象,否则会抛出

ClassCastException异常

  1. 不能插⼊null对象,否则会抛出NullPointerException

  2. 没有容量限制,可以插⼊任意多个元素,其内部可以⾃动扩容

  3. 插⼊和删除元素的时间复杂度为

  4. PriorityQueue底层使⽤了堆数据结构

  5. PriorityQueue默认情况下是⼩堆---即每次获取到的元素都是最⼩的元素

3.2 插⼊/删除/获取优先级最⾼的元素

使用比较器来进行大堆小堆

java 复制代码
还有一个代码,通过比较器,来写大堆小堆
package priotityqeue;  
  
import java.util.Comparator;  
import java.util.PriorityQueue;  
  
class IntComparator implements Comparator<Integer> {  
  
    @Override  
    public int compare(Integer o1, Integer o2) {  
        //如果判定o1小于o2,返回-1;如果判定o1大于o2,返回1;如果判定o1等于o2,返回0。  
        return o2 - o1;  
    }  
}  
  
    public class Test2 {  
        public static void main(String[] args) {  
            PriorityQueue<Integer> queue = new PriorityQueue<>(new IntComparator());  
            queue.add(10);  
            queue.add(5);  
            queue.add(15);  
            queue.add(20);  
            queue.add(1);  
  
            System.out.println(queue.poll());  
            System.out.println(queue.poll());  
            System.out.println(queue.poll());  
            System.out.println(queue.poll());  
            System.out.println(queue.poll());  
  
        }  
    }

还有一种写法,匿名内部类:
package priotityqeue;  
  
import java.util.Comparator;  
import java.util.PriorityQueue;  
import java.util.function.IntFunction;  
  
class IntComparator implements Comparator<Integer> {  
  
    @Override  
    public int compare(Integer o1, Integer o2) {  
        //如果判定o1小于o2,返回-1;如果判定o1大于o2,返回1;如果判定o1等于o2,返回0。  
        return o2 - o1;  
    }  
}  
  
    public class Test2 {  
        public static void main(String[] args) {  
           // PriorityQueue<Integer> queue = new PriorityQueue<>(new IntComparator());  
            PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {  
                @Override  
                public int compare(Integer o1, Integer o2) {  
                    return 0;  
                }  
            });  
            queue.add(10);  
            queue.add(5);  
            queue.add(15);  
            queue.add(20);  
            queue.add(1);  
  
            System.out.println(queue.poll());  
            System.out.println(queue.poll());  
            System.out.println(queue.poll());  
            System.out.println(queue.poll());  
            System.out.println(queue.poll());  
  
        }  
    }

四.:堆的应用

4.1 PriorityQueue的实现

java 复制代码
⽤堆作为底层结构封装优先级队列
package priotityqeue;  
import java.util.Comparator;  
import java.util.PriorityQueue;  
class Student{  
    public int id;  
    public String name;  
    public int score;  
  
    public Student(int id, String name, int score) {  
        this.id = id;  
        this.name = name;  
        this.score = score;  
    }  
  
    @Override  
    public String toString() {  
        return "Student{" +  
                "id=" + id +  
                ", name='" + name + '\'' +  
                ", score=" + score +  
                '}';  
    }  
}  
public class Test3 {  
    public static void main(String[] args) {  
        PriorityQueue<Student> pq = new PriorityQueue<>(new Comparator<Student>(){  
            @Override  
            public int compare(Student o1, Student o2) {  
                return o2.score - o1.score;  
            }  
        });  
        //此处没有设优先级,程序运行会出错,当我设置优先级后,程序运行正常。  
        pq.offer(new Student(1, "Alice", 80));  
        pq.offer(new Student(2, "Bob", 90));  
        pq.offer(new Student(3, "Charlie", 70));  
        pq.offer(new Student(4, "David", 60));  
        System.out.println(pq.poll());  
        System.out.println(pq.poll());  
        System.out.println(pq.poll());  
        System.out.println(pq.poll());  
  
    }  
}

4.2 堆排序

把无序数组变为有序

1堆排序即利⽤堆的思想来进⾏排序,总共分为两个步骤:

建堆

升序:建⼤堆

降序:建⼩堆

2:利⽤堆删除思想来进⾏排序

建堆和堆删除中都⽤到了向下调整,因此掌握了向下调整,就可以完成堆排序。

五:总结

优先级队列是一种特殊的队列,它根据元素的优先级决定出队顺序,优先级高的元素先出队。与普通队列(FIFO)不同,优先级队列常用于需要优先处理特定任务的场景,如医院急诊插队、游戏来电处理等。其底层通常通过堆结构实现,分为大根堆(根节点最大)和小根堆(根节点最小)。堆的存储采用数组形式,通过父子节点下标关系(如父节点i,左孩子2i+1,右孩子2i+2)维护完全二叉树结构。核心操作包括入队(尾部插入后向上调整)、出队(交换根尾后向下调整)和取队首元素。Java中的PriorityQueue默认小堆,可通过比较器自定义排序规则。堆排序利用堆的调整特性实现高效排序,时间复杂度为O(nlogn)。

相关推荐
妮妮喔妮1 天前
Go的垃圾回收
开发语言·后端·golang
bbq粉刷匠1 天前
从0开始学java--day6.5
java
向上的车轮1 天前
无需云服务的家庭相册:OpenHarmony 上的 Rust 实践
开发语言·后端·rust
Slow菜鸟1 天前
SpringBoot集成Elasticsearch | Elasticsearch 8.x专属Java Client
java·spring boot·elasticsearch
Miraitowa_cheems1 天前
LeetCode算法日记 - Day 82: 环形子数组的最大和
java·数据结构·算法·leetcode·决策树·线性回归·深度优先
Code_Shark1 天前
AtCoder Beginner Contest 426 题解
数据结构·c++·算法·数学建模·青少年编程
仰泳的熊猫1 天前
LeetCode:698. 划分为k个相等的子集
数据结构·c++·算法·leetcode
豐儀麟阁贵1 天前
4.5数组排序算法
java·开发语言·数据结构·算法·排序算法
Jane-6667771 天前
C语言——栈与队列
c语言·开发语言
“抚琴”的人1 天前
C# 取消机制(CancellationTokenSource/CancellationToken)
开发语言·c#·wpf·1024程序员节·取消机制