模拟实现优先级队列

目录

定义

特点

构造函数

常用方法

关于扩容的问题

关于建堆的问题

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

(向上调整)代码

(向下调整)代码

关于入队列和出队列问题

模拟实现优先级队列代码

关于堆排序的问题

堆排序代码

关于对象的比较

基于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));  // 按年龄比较
相关推荐
安之若素^4 分钟前
启用不安全的HTTP方法
java·开发语言
ruanjiananquan9911 分钟前
c,c++语言的栈内存、堆内存及任意读写内存
java·c语言·c++
chuanauc38 分钟前
Kubernets K8s 学习
java·学习·kubernetes
一头生产的驴1 小时前
java整合itext pdf实现自定义PDF文件格式导出
java·spring boot·pdf·itextpdf
YuTaoShao1 小时前
【LeetCode 热题 100】73. 矩阵置零——(解法二)空间复杂度 O(1)
java·算法·leetcode·矩阵
zzywxc7871 小时前
AI 正在深度重构软件开发的底层逻辑和全生命周期,从技术演进、流程重构和未来趋势三个维度进行系统性分析
java·大数据·开发语言·人工智能·spring
YuTaoShao3 小时前
【LeetCode 热题 100】56. 合并区间——排序+遍历
java·算法·leetcode·职场和发展
程序员张33 小时前
SpringBoot计时一次请求耗时
java·spring boot·后端
llwszx6 小时前
深入理解Java锁原理(一):偏向锁的设计原理与性能优化
java·spring··偏向锁
云泽野7 小时前
【Java|集合类】list遍历的6种方式
java·python·list