Part5 优先级队列(2025.6.7)

1.基本概念

  • 优先级队列是一种特殊的队列,其中每个元素都有一个优先级。出队时,优先级高的元素会先被处理。优先级队列可以用来实现任务调度、事件处理等。

优先级队列的简单实现:

  • 方式一:入队时,按照优先级数值在队列(线性表)中寻找合适的位置,将新入队的元素插入在此位置。出队操作的实现保持不变。
  • 方式二:入队时将新入队的元素直接放在队尾。但出队时,在整个队列中查找优先级最高的元素,让它出队。
  • 时间复杂度分析:
    • 方式一:入队操作的时间复杂度为O(n),出队操作的时间复杂度为O(1)。
    • 方式二:入队操作的时间复杂度为O(1),出队操作的时间复杂度为O(n)。

2.二叉堆

  • 二叉堆是一种特殊的完全二叉树,分为最大化堆(大顶堆)和最小化堆(小顶堆)满足堆的性质:对于每个节点,其值都大于或等于(或小于或等于)其子节点的值。
例如:序列 { 2,3,4,5,7,10,23,29,60 } 是最小化堆  
序列 { 12,7,8,4,6,5,3,1} 是最大化堆  
教材默认最小化堆  

优先级队列的堆实现

使用二叉堆来实现优先级队列,入队和出队操作的时间复杂度均为O(log n)。

类定义

template <class Type>
class priorityQueue:public queue<Type>
{
private:
    int currentSize;
    Type *array;
    int maxSize; //同顺序表定义
    void doubleSpace();
    void buildHeap( );//建堆
    void percolateDown( int hole ); //堆调整
public:
    priorityQueue( int capacity = 100 ) {
        array = new Type[capacity];
        maxSize = capacity;
        currentSize = 0;
    }//顺序表初始化
    priorityQueue( const Type data[], int size );//建堆过程
    ~priorityQueue() { delete [] array; }
    bool isEmpty( ) const { return currentSize == 0; }
    void enQueue( const Type & x );//入队
    Type deQueue();
    Type getHead() { return array[1]; }//array[0]做了哨兵
};

小顶堆进队(向上过滤)

最坏情况(插入结点调整到顶)下一次完整调整过程的时间复杂度为O(log n),因此入队操作的时间复杂度为O(log n)。

template <class Type>
void priorityQueue<Type>::enQueue( const Type & x )
{
    if( currentSize == maxSize - 1 ) doubleSpace();
    // 向上过滤(调整)
    int hole = ++currentSize;
    for( ; hole > 1 && x < array[ hole / 2 ]; hole /= 2 ) { //注意边界及下标的含义
        array[ hole ] = array[ hole / 2 ];
    }
    array[ hole ] = x;
}

小顶堆出队(向下过滤)

出队操作的时间复杂度为O(log n)。

template <class Type>
Type priorityQueue<Type>::deQueue()
{
    Type minItem;
    minItem = array[ 1 ];//堆顶下标为1或者0,可自行定义
    array[ 1 ] = array[ currentSize-- ];
    percolateDown( 1 );
    return minItem;
}
 
template <class Type>
void priorityQueue<Type>::percolateDown(int hole) {
    int child;                // 子节点索引
    Type tmp = array[hole];   // 保存当前节点的值
 
    // 循环直到当前节点没有子节点
    for (; hole * 2 <= currentSize; hole = child) {
        child = hole * 2;     // 左子节点
 
        // 如果右子节点存在且更小,则选择右子节点
        if (child != currentSize && array[child + 1] < array[child]) {
            child++;
        }
 
        // 如果子节点比当前节点小,则交换
        if (array[child] < tmp) {
            array[hole] = array[child];
        } else {
            break;  // 否则终止循环
        }
    }
 
    // 将初始节点值放到最终位置
    array[hole] = tmp;
}

建堆

  • 可以采取N次连续插入的方式,但是,其时间复杂度O(NlogN),故不采纳。
  • 实际上建堆的时间复杂度为O(N),可以通过从最后一个非叶子节点开始向下过滤的方式实现。
// percolateDown(int hole)部分
Type tmp = array[ hole ];
for( ; hole * 2 <= currentSize; hole = child ) {
    child = hole * 2;
    if( child != currentSize && array[ child + 1 ] < array[ child ] ) child++;
    if( array[ child ] < tmp ) array[ hole ] = array[ child ];
    else break;
}
array[ hole ] = tmp;
  • 复杂度推导即每一层结点的数量乘2的深度次方倍的和,使用可以错位相消推导。