模拟实现优先级队列

目录

定义

特点

构造函数

常用方法

关于扩容的问题

关于建堆的问题

向上调整和向下调整的比较

(向上调整)代码

(向下调整)代码

关于入队列和出队列问题

模拟实现优先级队列代码

关于堆排序的问题

堆排序代码

关于对象的比较

基于Comparable的比较

基于Comparator的比较


定义

在Java中,PriorityQueue 是一个基于优先级堆的无界优先级队列。优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时所提供的 Comparator 进行排序,具体取决于所使用的构造器。PriorityQueue 不允许 null 元素。
PriorityQueue默认是小根堆。

特点

1.无界:PriorityQueue 可以根据需要动态增长以容纳任意数量的元素。

2.基于优先级:队列中的元素按照优先级进行排序。元素的优先级可以是其自然顺序(对于实现了 Comparable 接口的元素),或者根据构造时提供的 Comparator 来确定。

3.不允许 null 元素:尝试添加 null 元素到 PriorityQueue 会抛出 NullPointerException。

4.非线程安全:如果多个线程同时访问一个 PriorityQueue 实例,并且至少有一个线程从结构上修改了队列,那么它必须保持外部同步。

为什么PriorityQueue不能够插入null对象?下面我们看一下源码:

构造函数

1.PriorityQueue():创建一个默认初始容量的 PriorityQueue,其元素将按照其自然顺序进行排序。

2.PriorityQueue(int initialCapacity):创建一个具有指定初始容量的 PriorityQueue,其元素将按照其自然顺序进行排序。

3.PriorityQueue(Collection<? extends E> c):创建一个包含指定集合元素的 PriorityQueue,其元素将按照其自然顺序进行排序。

4.PriorityQueue(int initialCapacity, Comparator<? super E> comparator):创建一个具有指定初始容量的 PriorityQueue,并根据指定的比较器对其元素进行排序。

5.PriorityQueue(Collection<? extends E> c, Comparator<? super E> comparator):创建一个包含指定集合元素的 PriorityQueue,并根据指定的比较器对其元素进行排序。

常用方法

1.boolean add(E e):将指定元素插入此优先级队列。

2.E poll():检索并移除此队列的头,即在此队列中优先级最低/最高的元素。如果此队列为空,则返回 null。

3.E peek():检索但不移除此队列的头;如果此队列为空,则返回 null。

4.int size():返回队列中的元素数量。

5.boolean isEmpty():如果此队列不包含任何元素,则返回 true。

6.void clear():移除此队列中的所有元素。

关于扩容的问题

通过源码我们了解到,当容量小于64的时候,每次扩容只增加两个空间;当容量大于64的时候,每次扩容是1.5倍扩容。

并且,定义了一个常量值MAX_ARRAY_SIZE=MAX_VALUE-8,当计算后得到的新容量溢出时会抛异常OutOfMemoryError;当计算后得到的新容量大于MAX_ARRAY_SIZE时,新容量设置为MAX_VALUE。

关于建堆的问题

因为优先级队列是基于堆实现的,因此要想实现优先级队列,必须先实现建堆。

那么建立大根堆还是小根堆呢?需要根据实际需要来决定建立大根堆还是小根堆。

向上调整和向下调整的比较

那么建堆的时候使用"向上调整算法"还是"向下调整算法"呢?

下面我们分析一下二者的时间复杂度:

向下调整算法:时间复杂度为N

向上调整算法:时间复杂度为N*logN

显然,使用"向下调整"比"向上调整"更好。

(向上调整)代码
java 复制代码
    private void shiftUp(int child) {
        int parent = (child-1)/2;
        while (child > 0) {
            if (elem[child] > elem[parent]) {
                swap(child,parent);
                child = parent;
                parent = (child-1)/2;
            } else {
                break;
            }
        }
    }

    private void swap(int i,int j) {
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }
(向下调整)代码
java 复制代码
    private void shiftDown(int parent, int usedSize) {
        int child = (2*parent)+1;//左孩子下标
        while (child < usedSize) {
            if(child+1 < usedSize && elem[child] < elem[child+1]) {
                child++;
            }
            //child一定是 左右孩子最大值的下标
            if(elem[child] > elem[parent]) {
                swap(child, parent);
                parent = child;
                child = 2*parent+1;
            }else {
                //已经是大根堆了
                break;
            }
        }
    }
    private void swap(int i,int j) {
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }
关于入队列和出队列问题

策略:

针对入队列:将要插入的元素放到堆尾,然后对该位置进行"向上调整"即可;

针对出队列:将要删除的元素(即堆顶元素)和堆尾元素进行交换,然后把队列的大小-1,然后从堆顶进行"向下调整"即可。

模拟实现优先级队列代码
java 复制代码
public class TestHeap {
    private int[] elem;
    private int usedSize;

    public TestHeap() {
        this.elem = new int[10];
    }

    public void initHeap(int[] array) {
        for (int i = 0; i < array.length; i++) {
            elem[i] = array[i];
            usedSize++;
        }
    }

    public void createHeap() {
        for (int parent = (usedSize-1-1)/2; parent >= 0 ; parent--) {
            shiftDown(parent,usedSize);
        }
    }

    private void shiftDown(int parent, int usedSize) {
        int child = (2*parent)+1;//左孩子下标
        while (child < usedSize) {
            if(child+1 < usedSize && elem[child] < elem[child+1]) {
                child++;
            }
            //child一定是 左右孩子最大值的下标
            if(elem[child] > elem[parent]) {
                swap(child, parent);
                parent = child;
                child = 2*parent+1;
            }else {
                //已经是大根堆了
                break;
            }
        }
    }
    private void swap(int i,int j) {
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }


    public void offer(int val) {
        if(isFull()) {
            this.elem = Arrays.copyOf(elem,2*elem.length);
        }
        this.elem[usedSize] = val;//usedSize=10
        //向上调整
        shiftUp(usedSize);
        usedSize++;
    }

    private void shiftUp(int child) {
        int parent = (child-1)/2;
        while (child > 0) {
            if (elem[child] > elem[parent]) {
                swap(child,parent);
                child = parent;
                parent = (child-1)/2;
            } else {
                break;
            }
        }
    }

    public boolean isFull() {
        return usedSize == elem.length;
    }

    public int poll() {
        int tmp = elem[0];
        swap(0,usedSize-1);
        usedSize--;
        shiftDown(0,usedSize);
        return tmp;
    }

    public void heapSort() {
        int end = usedSize-1;
        while (end > 0) {
            swap(0,end);
            shiftDown(0,end);
            end--;
        }
    }
}
关于堆排序的问题

上面我们分析了"向上调整"和"向下调整"的时间复杂度,我们知道"向下调整"优于"向上调整",下面将使用"向下调整"来实现堆排序。

实现堆排序的步骤:

1.首先建好堆

2.其次定义一个变量end标记最后一个元素的位置

3.然后交换堆顶和堆尾的元素

4.然后对堆顶位置进行向下调整,end--;

5.重复执行步骤3,4,5,直到end=0

堆排序代码
java 复制代码
    public void heapSort() {
        int end = usedSize-1;
        while (end > 0) {
            swap(0,end);
            shiftDown(0,end);
            end--;
        }
    }

    private void shiftDown(int parent, int usedSize) {
        int child = (2*parent)+1;//左孩子下标
        while (child < usedSize) {
            if(child+1 < usedSize && elem[child] < elem[child+1]) {
                child++;
            }
            //child一定是 左右孩子最大值的下标
            if(elem[child] > elem[parent]) {
                swap(child, parent);
                parent = child;
                child = 2*parent+1;
            }else {
                //已经是大根堆了
                break;
            }
        }
    }

    private void swap(int i,int j) {
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }
关于对象的比较
基于Comparable的比较

对于自定义类型,如果要对其进行比较,需要实现Comparable的compareTo方法。

代码示例

java 复制代码
public class Person implements Comparable<Person> {  
    private String name;  
    private int age;  
  
    // 构造器、getter和setter省略  
  
    @Override  
    public int compareTo(Person o) {  
        return this.age - o.age; // 以年龄为基准进行比较  
    }  
}  
  
// 使用示例  
Person p1 = new Person("Alice", 30);  
Person p2 = new Person("Bob", 25);  
System.out.println(p1.compareTo(p2)); // 输出正数,因为p1的年龄大于p2
基于Comparator的比较

对于自定义类型,如果要对其进行比较,需要实现Comparator的compare方法。

代码示例

java 复制代码
import java.util.Comparator;  
  
public class Person {  
    private String name;  
    private int age;  
  
    // 构造器、getter和setter省略  
  
    // 使用Comparator进行比较  
    public static Comparator<Person> byName = new Comparator<Person>() {  
        @Override  
        public int compare(Person p1, Person p2) {  
            return p1.getName().compareTo(p2.getName());  
        }  
    };  
  
    // 或者使用Lambda表达式(Java 8+)  
    public static Comparator<Person> byAge = (Person p1, Person p2) -> Integer.compare(p1.getAge(), p2.getAge());  
}  
  
// 使用示例  
Person p1 = new Person("Alice", 30);  
Person p2 = new Person("Bob", 25);  
System.out.println(Person.byName.compare(p1, p2)); // 按名字比较  
System.out.println(Person.byAge.compare(p1, p2));  // 按年龄比较
相关推荐
南宫生9 分钟前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石17 分钟前
12/21java基础
java
李小白6625 分钟前
Spring MVC(上)
java·spring·mvc
GoodStudyAndDayDayUp38 分钟前
IDEA能够从mapper跳转到xml的插件
xml·java·intellij-idea
装不满的克莱因瓶1 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb
n北斗1 小时前
常用类晨考day15
java
骇客野人1 小时前
【JAVA】JAVA接口公共返回体ResponseData封装
java·开发语言
yuanbenshidiaos2 小时前
c++---------数据类型
java·jvm·c++
向宇it2 小时前
【从零开始入门unity游戏开发之——C#篇25】C#面向对象动态多态——virtual、override 和 base 关键字、抽象类和抽象方法
java·开发语言·unity·c#·游戏引擎
Lojarro3 小时前
【Spring】Spring框架之-AOP
java·mysql·spring