优先级队列详解

一、优先级队列概述

1.1 基本概念

优先级队列是一种特殊的队列数据结构,其中每个元素都关联一个"优先级"。与普通队列的先进先出(FIFO)不同,优先级队列按照元素的优先级进行出队操作,优先级高的元素先出队。

1.2 Java中的PriorityQueue

Java提供了PriorityQueue类,它实现了Queue接口,底层基于堆数据结构实现。

java

java 复制代码
import java.util.PriorityQueue;
import java.util.Queue;

public class PriorityQueueDemo {
    public static void main(String[] args) {
        // 默认创建小根堆(最小堆)
        Queue<Integer> minHeap = new PriorityQueue<>();
        
        minHeap.offer(28);
        minHeap.offer(23);
        minHeap.offer(19);
        minHeap.offer(21);
        minHeap.offer(8);
        minHeap.offer(18);
        minHeap.offer(5);
        
        System.out.println("小根堆内容:");
        while (!minHeap.isEmpty()) {
            System.out.print(minHeap.poll() + " ");
        }
        // 输出: 5 8 18 19 21 23 28
    }
}

二、堆数据结构详解

2.1 堆的基本概念

堆是一种特殊的完全二叉树,满足以下性质:

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

2.2 堆的数组表示

由于堆是完全二叉树,可以用数组高效存储:

  • 父节点下标:(i-1)/2
  • 左子节点:2*i + 1
  • 右子节点:2*i + 2

2.3 创建自定义堆类型

java

java 复制代码
import java.util.Comparator;

// 自定义比较器创建大根堆
class MaxHeapComparator implements Comparator<Integer> {
    @Override
    public int compare(Integer a, Integer b) {
        return b - a; // 降序排列,创建大根堆
    }
}

public class CustomHeapDemo {
    public static void main(String[] args) {
        // 使用自定义比较器创建大根堆
        Queue<Integer> maxHeap = new PriorityQueue<>(new MaxHeapComparator());
        
        maxHeap.offer(28);
        maxHeap.offer(23);
        maxHeap.offer(19);
        maxHeap.offer(21);
        maxHeap.offer(8);
        maxHeap.offer(18);
        maxHeap.offer(5);
        
        System.out.println("大根堆内容:");
        while (!maxHeap.isEmpty()) {
            System.out.print(maxHeap.poll() + " ");
        }
        // 输出: 28 23 21 19 18 8 5
    }
}

三、堆的核心操作实现

3.1 向下调整算法

大根堆向下调整

java

ini 复制代码
/**
 * 大根堆向下调整
 * @param root 需要调整的根节点下标
 * @param len 堆的有效长度
 */
private void shiftDownBig(int root, int len) {
    int parent = root;
    int child = 2 * parent + 1; // 左孩子
    
    while (child < len) {
        // 选择左右孩子中较大的那个
        if (child + 1 < len && elem[child] < elem[child + 1]) {
            child++; // 右孩子更大
        }
        
        // 如果孩子大于父亲,需要交换
        if (elem[child] > elem[parent]) {
            swap(parent, child);
            parent = child;
            child = 2 * parent + 1;
        } else {
            break; // 调整完成
        }
    }
}

/**
 * 小根堆向下调整
 */
private void shiftDownSmall(int root, int len) {
    int parent = root;
    int child = 2 * parent + 1;
    
    while (child < len) {
        // 选择左右孩子中较小的那个
        if (child + 1 < len && elem[child] > elem[child + 1]) {
            child++;
        }
        
        // 如果孩子小于父亲,需要交换
        if (elem[child] < elem[parent]) {
            swap(parent, child);
            parent = child;
            child = 2 * parent + 1;
        } else {
            break;
        }
    }
}

private void swap(int i, int j) {
    int temp = elem[i];
    elem[i] = elem[j];
    elem[j] = temp;
}

向下调整过程图解

text

makefile 复制代码
初始大根堆(需要调整节点24):
       67
      /  \
     45   12
    /  \  / \
   24  29 8  10
  / \
 19  21

步骤1: 24与较大的孩子45比较,24 < 45,交换
       67
      /  \
     45   12
    /  \  / \
   45  29 8  10  ← 24移动到45的位置
  / \
 19  21

步骤2: 24与较大的孩子29比较,24 < 29,交换
       67
      /  \
     45   12
    /  \  / \
   29  29 8  10  ← 24移动到29的位置
  / \
 19  21

最终: 24找到合适位置
       67
      /  \
     45   12
    /  \  / \
   29  24 8  10
  / \
 19  21

3.2 向上调整算法

大根堆向上调整

java

ini 复制代码
/**
 * 大根堆向上调整
 * @param child 需要调整的孩子节点下标
 */
private void shiftUpBig(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 shiftUpSmall(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;
        }
    }
}

向上调整过程图解

text

makefile 复制代码
初始(需要调整节点24):
       67
      /  \
     45   12
    /  \  / \
   29  19 8  10
  / \
 21  24

步骤1: 24与直接父亲21比较,24 > 21,交换
       67
      /  \
     45   12
    /  \  / \
   29  19 8  10
  / \
 24  21

步骤2: 24与父亲29比较,24 < 29,停止
       67
      /  \
     45   12
    /  \  / \
   29  19 8  10
  / \
 24  21

四、堆的构建方法

4.1 向下调整建堆

java

ini 复制代码
public class HeapBuilder {
    private int[] elem;
    
    /**
     * 向下调整构建大根堆
     * 时间复杂度: O(n)
     */
    public void buildMaxHeapDown(int[] array) {
        this.elem = array.clone();
        int n = elem.length;
        
        // 从最后一个非叶子节点开始向下调整
        for (int i = (n - 2) / 2; i >= 0; i--) {
            shiftDownBig(i, n);
        }
    }
    
    /**
     * 向下调整构建小根堆
     */
    public void buildMinHeapDown(int[] array) {
        this.elem = array.clone();
        int n = elem.length;
        
        for (int i = (n - 2) / 2; i >= 0; i--) {
            shiftDownSmall(i, n);
        }
    }
}

4.2 向上调整建堆

java

ini 复制代码
public class HeapBuilder {
    /**
     * 向上调整构建大根堆
     * 时间复杂度: O(n log n)
     */
    public void buildMaxHeapUp(int[] array) {
        this.elem = array.clone();
        int n = elem.length;
        
        // 从第二个元素开始向上调整
        for (int i = 1; i < n; i++) {
            shiftUpBig(i);
        }
    }
    
    /**
     * 向上调整构建小根堆
     */
    public void buildMinHeapUp(int[] array) {
        this.elem = array.clone();
        int n = elem.length;
        
        for (int i = 1; i < n; i++) {
            shiftUpSmall(i);
        }
    }
}

五、完整堆实现

5.1 自定义堆类

java

ini 复制代码
import java.util.Arrays;
import java.util.NoSuchElementException;

public class MyPriorityQueue {
    private int[] elem;      // 存储堆元素的数组
    private int size;        // 堆中元素个数
    private boolean isMaxHeap; // 标记是否为大根堆
    
    private static final int DEFAULT_CAPACITY = 10;
    
    // 构造方法
    public MyPriorityQueue() {
        this(false); // 默认小根堆
    }
    
    public MyPriorityQueue(boolean isMaxHeap) {
        this.elem = new int[DEFAULT_CAPACITY];
        this.size = 0;
        this.isMaxHeap = isMaxHeap;
    }
    
    public MyPriorityQueue(int[] array, boolean isMaxHeap) {
        this.isMaxHeap = isMaxHeap;
        buildHeap(array);
    }
    
    // 构建堆
    private void buildHeap(int[] array) {
        this.elem = array.clone();
        this.size = array.length;
        
        // 使用向下调整建堆(效率更高)
        for (int i = (size - 2) / 2; i >= 0; i--) {
            shiftDown(i);
        }
    }
    
    // 统一的向下调整方法
    private void shiftDown(int root) {
        if (isMaxHeap) {
            shiftDownBig(root, size);
        } else {
            shiftDownSmall(root, size);
        }
    }
    
    // 统一的向上调整方法
    private void shiftUp(int child) {
        if (isMaxHeap) {
            shiftUpBig(child);
        } else {
            shiftUpSmall(child);
        }
    }
    
    // 插入元素
    public void offer(int val) {
        // 扩容检查
        if (size >= elem.length) {
            elem = Arrays.copyOf(elem, elem.length * 2);
        }
        
        elem[size] = val;
        shiftUp(size);
        size++;
    }
    
    // 删除堆顶元素
    public int poll() {
        if (isEmpty()) {
            throw new NoSuchElementException("Heap is empty");
        }
        
        int result = elem[0];
        // 将最后一个元素移到堆顶
        elem[0] = elem[size - 1];
        size--;
        
        // 从堆顶开始向下调整
        if (size > 0) {
            shiftDown(0);
        }
        
        return result;
    }
    
    // 获取堆顶元素(不删除)
    public int peek() {
        if (isEmpty()) {
            throw new NoSuchElementException("Heap is empty");
        }
        return elem[0];
    }
    
    // 堆大小
    public int size() {
        return size;
    }
    
    // 判断堆是否为空
    public boolean isEmpty() {
        return size == 0;
    }
    
    // 清空堆
    public void clear() {
        size = 0;
        // 可选:释放数组引用
        // elem = new int[DEFAULT_CAPACITY];
    }
    
    // 打印堆内容(调试用)
    public void printHeap() {
        System.out.print("Heap: ");
        for (int i = 0; i < size; i++) {
            System.out.print(elem[i] + " ");
        }
        System.out.println();
    }
    
    // 原有的调整方法(稍作修改)
    private void shiftDownBig(int root, int len) {
        int parent = root;
        int child = 2 * parent + 1;
        
        while (child < len) {
            if (child + 1 < len && elem[child] < elem[child + 1]) {
                child++;
            }
            if (elem[child] > elem[parent]) {
                swap(parent, child);
                parent = child;
                child = 2 * parent + 1;
            } else {
                break;
            }
        }
    }
    
    private void shiftDownSmall(int root, int len) {
        int parent = root;
        int child = 2 * parent + 1;
        
        while (child < len) {
            if (child + 1 < len && elem[child] > elem[child + 1]) {
                child++;
            }
            if (elem[child] < elem[parent]) {
                swap(parent, child);
                parent = child;
                child = 2 * parent + 1;
            } else {
                break;
            }
        }
    }
    
    private void shiftUpBig(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 shiftUpSmall(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 temp = elem[i];
        elem[i] = elem[j];
        elem[j] = temp;
    }
}

5.2 测试示例

java

csharp 复制代码
public class HeapTest {
    public static void main(String[] args) {
        // 测试小根堆
        System.out.println("=== 小根堆测试 ===");
        MyPriorityQueue minHeap = new MyPriorityQueue(false);
        
        int[] testData = {28, 23, 19, 21, 8, 18, 5};
        for (int num : testData) {
            minHeap.offer(num);
        }
        
        minHeap.printHeap(); // 堆结构
        System.out.println("按优先级出队:");
        while (!minHeap.isEmpty()) {
            System.out.print(minHeap.poll() + " ");
        }
        System.out.println();
        
        // 测试大根堆
        System.out.println("\n=== 大根堆测试 ===");
        MyPriorityQueue maxHeap = new MyPriorityQueue(true);
        
        for (int num : testData) {
            maxHeap.offer(num);
        }
        
        maxHeap.printHeap(); // 堆结构
        System.out.println("按优先级出队:");
        while (!maxHeap.isEmpty()) {
            System.out.print(maxHeap.poll() + " ");
        }
        System.out.println();
        
        // 测试从数组构建堆
        System.out.println("\n=== 数组构建堆测试 ===");
        int[] array = {3, 1, 4, 1, 5, 9, 2, 6};
        MyPriorityQueue heapFromArray = new MyPriorityQueue(array, true);
        
        heapFromArray.printHeap();
        System.out.println("堆顶元素: " + heapFromArray.peek());
    }
}

六、堆的应用场景

6.1 实际应用

  1. 任务调度:操作系统进程调度
  2. 数据流的中位数:维护两个堆(大根堆+小根堆)
  3. Dijkstra算法:图的最短路径
  4. Huffman编码:数据压缩
  5. 堆排序:高效的排序算法

6.2 堆排序实现

java

ini 复制代码
public class HeapSort {
    /**
     * 堆排序(升序 - 使用大根堆)
     */
    public static void heapSort(int[] arr) {
        int n = arr.length;
        
        // 构建大根堆
        for (int i = (n - 2) / 2; i >= 0; i--) {
            shiftDownBig(arr, i, n);
        }
        
        // 逐个提取最大元素
        for (int i = n - 1; i > 0; i--) {
            // 将堆顶元素(最大)与当前末尾元素交换
            swap(arr, 0, i);
            // 调整剩余元素为堆
            shiftDownBig(arr, 0, i);
        }
    }
    
    private static void shiftDownBig(int[] arr, int root, int len) {
        int parent = root;
        int child = 2 * parent + 1;
        
        while (child < len) {
            if (child + 1 < len && arr[child] < arr[child + 1]) {
                child++;
            }
            if (arr[child] > arr[parent]) {
                swap(arr, parent, child);
                parent = child;
                child = 2 * parent + 1;
            } else {
                break;
            }
        }
    }
    
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

七、性能分析与总结

7.1 时间复杂度

操作 时间复杂度 说明
插入元素 O(log n) 向上调整
删除堆顶 O(log n) 向下调整
获取堆顶 O(1) 直接访问
建堆 O(n) 向下调整建堆

7.2 核心要点总结

  1. 堆的性质:完全二叉树 + 堆序性质
  2. 数组存储:利用完全二叉树的特性高效存储
  3. 调整操作:向上调整用于插入,向下调整用于删除
  4. 建堆选择:向下调整建堆效率更高(O(n) vs O(n log n))
  5. 应用广泛:从操作系统到算法竞赛都有重要应用

通过深入理解堆的原理和实现,可以更好地应用这种高效的数据结构解决实际问题。

相关推荐
雨中飘荡的记忆37 分钟前
ByteBuddy 实战指南
后端
Apifox44 分钟前
Apifox 11 月更新|AI 生成测试用例能力持续升级、JSON Body 自动补全、支持为响应组件添加描述和 Header
前端·后端·测试
有风631 小时前
双向循环带头链表详解
后端
找不到对象就NEW一个1 小时前
用wechatapi进行微信二次开发,微信api
后端
charlie1145141911 小时前
勇闯前后端Week2:后端基础——Flask API速览
笔记·后端·python·学习·flask·教程
有风631 小时前
基于顺序表完成通讯录项目
后端
yuuki2332331 小时前
【C++】初识C++基础
c语言·c++·后端
q***87601 小时前
springboot下使用druid-spring-boot-starter
java·spring boot·后端
程序员西西1 小时前
SpringBoot无感刷新Token实战指南
java·开发语言·前端·后端·计算机·程序员