优先队列的出队操作

本文深入解析了优先队列的出队操作,重点介绍了shiftDown算法,该算法通过比较父节点与子节点来维护大根堆结构。在出队过程中,将堆顶元素与最后一个元素交换,并通过shiftDown调整堆结构,确保大根堆性质不变。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

要点:

  • 了解 shiftDown
  • 了解优先队列 出队的机制
    上一篇文章中我写了优先队列的入队操作,入队shiftUp,出队shiftDown
    看代码效果:

在这里插入图片描述
首先,在优先队列插入50个元素,执行poll操作,也就是出队
把队顶的一个元素拿出
在这里插入图片描述
如图,2个97只剩下一个

对于一个50个元素的大根堆,只要出队,就变成49个元素,这里我使用 count–,因此数组长度并不是真的变成了49个,而是模拟 了一个出队,第50个元素还是存在的,这个大根堆的容量只有100个
当大根堆满了之后,要重新创建一个数组,维护一个新的大根堆,这个功能我就暂时先不实现了

出队主要是把队首和队尾的元素交换,然后执行shiftDown 操作,也就是和他的儿子,比较,如果父亲比儿子小,儿子上来,父亲下去,重复这个操作,你就又维持了一个大根堆,具体可以看我的上一篇文章, 优先队列的入队操作

package day1;


import util.AlgoVisHelper;
import util.AlgoVisualizer;

import java.util.*;

/**
 * Created by ASUS on 2019/8/22.
 */
public class Main {


    public static void main(String[] args) {


        Random r = new Random();
        MaxHeap  p = new MaxHeap(100);
        int n = 50;
        int m =100;
        for(int i=0;i<n;++i) {
            int t = r.nextInt(100);
            p.insert(t);

        }


        p.treePrint();
        System.err.println("----------------------------------------------------------------");
        int big = p.poll();
        System.err.println("------  big  " +big   + " -----------------------");
        p.treePrint();


    }





}





class MaxHeap {
    int[] data;
    int count;
    int capacity;

    public MaxHeap(int capacity) {
        this.capacity = capacity;
        data = new int[capacity];
        this.count=0;

    }
    int size() {
        return count;
    }

    public boolean isEmpty() {
        return count==0;
    }
    void insert(int e) {
        data[count+1]=e;
        count++;
        shiftUp(count);
    }

    void swap(int i,int j) {
        if(data[i]==data[j])return;
        data[i]^=data[j];
        data[j]^=data[i];
        data[i]^=data[j];
    }

    void shiftUp(int k) {
        while (k>1&&data[k]>data[k/2])
        {
            swap(k,k/2);
            k/=2;
        }
    }
	 int poll() {
	        if(count<0)return 0;
	        int ret = data[1];
	        swap(1,count);
	        count--;
	        shiftDown(1);
	        return ret;
	    }

    void shiftDown(int k) {
        while (2*k<=count) {
            int j = 2*k;
            if(j+1<=count&&data[j+1]>data[j]) {//尽量和大的交换
                j+=1;
            }
            if(data[k]>=data[j]) {
                break;
            }
            swap(k,j);
            k=j;
        }
    }




    public void treePrint() {

        // 最多打印100个元素
        if (size() >= 100) {
            System.out.println("This print function can only work for less than 100 integer");
            return;
        }

        System.out.println("The max heap size is: " + size());
        System.out.println("Data in the max heap: ");
        for (int i = 0; i < size(); i++) {
            // 我们的print函数要求堆中的所有整数在[0, 100]的范围内
            assert (Integer) data[i] >= 0 && (Integer) data[i] < 100;
            System.out.println(data[i] + " ");
        }

        System.out.println();
        System.out.println();

        int n = size();
        int maxLevel = 0;
        int numberPerLevel = 1;
        while (n > 0) {
            maxLevel += 1;
            n -= numberPerLevel;
            numberPerLevel *= 2;
        }

        int maxLevelNumber = (int) Math.pow(2, maxLevel - 1);
        int curTreeMaxLevelNumber = maxLevelNumber;
        int index = 1;
        for (int level = 0; level < maxLevel; level++) {

            String line1 = new String(new char[maxLevelNumber * 3 - 1]).replace('\0', ' ');

            int curLevelNumber = Math.min(count - (int) Math.pow(2, level) + 1, (int) Math.pow(2, level));
            boolean isLeft = true;
            for (int indexCurLevel = 0; indexCurLevel < curLevelNumber; index++, indexCurLevel++) {
                line1 = putNumberInLine((Integer) data[index], line1, indexCurLevel, curTreeMaxLevelNumber * 3 - 1, isLeft);
                isLeft = !isLeft;
            }
            System.out.println(line1);

            if (level == maxLevel - 1) {
                break;
            }

            String line2 = new String(new char[maxLevelNumber * 3 - 1]).replace('\0', ' ');
            for (int indexCurLevel = 0; indexCurLevel < curLevelNumber; indexCurLevel++) {
                line2 = putBranchInLine(line2, indexCurLevel, curTreeMaxLevelNumber * 3 - 1);
            }
            System.out.println(line2);

            curTreeMaxLevelNumber /= 2;
        }
    }


    private String putNumberInLine(Integer num, String line, int indexCurLevel, int curTreeWidth, boolean isLeft) {

        int subTreeWidth = (curTreeWidth - 1) / 2;
        int offset = indexCurLevel * (curTreeWidth + 1) + subTreeWidth;
        assert offset + 1 < line.length();
        if (num >= 10) {
            line = line.substring(0, offset + 0) + num.toString()
                    + line.substring(offset + 2);
        } else {
            if (isLeft) {
                line = line.substring(0, offset + 0) + num.toString()
                        + line.substring(offset + 1);
            } else {
                line = line.substring(0, offset + 1) + num.toString()
                        + line.substring(offset + 2);
            }
        }
        return line;
    }

    private String putBranchInLine(String line, int indexCurLevel, int curTreeWidth) {

        int subTreeWidth = (curTreeWidth - 1) / 2;
        int subSubTreeWidth = (subTreeWidth - 1) / 2;
        int offsetLeft = indexCurLevel * (curTreeWidth + 1) + subSubTreeWidth;
        assert offsetLeft + 1 < line.length();
        int offsetRight = indexCurLevel * (curTreeWidth + 1) + subTreeWidth + 1 + subSubTreeWidth;
        assert offsetRight < line.length();

        line = line.substring(0, offsetLeft + 1) + "/" + line.substring(offsetLeft + 2);
        line = line.substring(0, offsetRight) + "\\" + line.substring(offsetRight + 1);

        return line;
    }
}


















<think>我们正在讨论优先队列优先队列是一种特殊的队列,其中每个元素都有一个优先级。出队操作总是移除优先级最高(或最低)的元素,而不是像普通队列那样先进先出。根据引用,优先队列通常通过堆(尤其是二叉堆)来实现。堆是一种完全二叉树,满足堆属性:在最大堆中,每个节点的值都大于或等于其子节点的值;在最小堆中,每个节点的值都小于或等于其子节点的值。入队操作(push):1.将新元素添加到堆的末尾(即完全二叉树的最后一个位置)。2.然后,为了维护堆的性质,需要对新元素进行“上浮”(ShiftUp)操作:比较新元素与其父节点的优先级,如果新元素的优先级更高(在最大堆中,即值更大;在最小堆中,值更小),则交换它们的位置。3.重复上浮过程,直到新元素到达合适的位置(即不再比父节点优先级高,或者到达根节点)。出队操作(pop):1.移除堆顶元素(即优先级最高的元素,在最大堆中是根节点,最小堆同理)。2.将堆的最后一个元素移动到堆顶。3.然后,进行“下沉”(ShiftDown操作:将该元素与其子节点比较,如果它的优先级比子节点低(在最大堆中,即值小于子节点;最小堆则相反),则与优先级最高的子节点交换位置。4.重复下沉过程,直到该元素到达合适的位置(即比其子节点的优先级都高,或者成为叶子节点)。时间复杂度:入队和出队操作的时间复杂度都是$O(\log n)$,因为堆的高度为$\log n$,上浮或下沉的过程最多需要比较和交换$\log n$次。引用[2]和[3]中提到了最大堆的实现,引用[4]展示了C++中优先队列的声明(包括最大堆和最小堆),引用[5]则展示了一个用JavaScript实现的优先队列(注意:引用[5]的实现并不是堆,而是用数组遍历插入,复杂度较高,实际中通常用堆实现)。因此,优先队列出队顺序总是按照优先级从高到低(或从低到高,取决于是最小堆还是最大堆),而入队操作则是在维护堆结构的同时将新元素插入到合适的位置。注意:在优先队列中,相同优先级的元素出队顺序可能不是先进先出,这取决于具体的实现。例如,在堆实现中,相同优先级的元素出队顺序通常是不确定的(除非在比较时加入额外的条件,如时间戳)。总结: -入队:将元素插入堆的末尾,然后执行上浮操作。-出队:移除堆顶元素,将最后一个元素移到堆顶,然后执行下沉操作。下面我们用伪代码描述最大堆的入队和出队过程:入队(以最大堆为例):procedure enqueue(value):heap.append(value)//将新元素添加到末尾index= heap.size -1while index>0:parent_index= (index -1) //2if heap[index]> heap[parent_index]:swap(heap[index],heap[parent_index])index =parent_indexelse:break出队(以最大堆为例):proceduredequeue():if heapis empty:return nullmax_value= heap[0]//堆顶元素last= heap.pop()//移除最后一个元素if heapis notempty:heap[0] =last//将最后一个元素放到堆顶index =0while index< heap.size:left=2*index+1right =2*index+2largest =indexifleft <heap.sizeand heap[left]> heap[largest]:largest= leftif right< heap.size andheap[right] >heap[largest]:largest =rightiflargest !=index:swap(heap[index],heap[largest])index =largestelse:breakreturnmax_value注意:最小堆的实现类似,只是比较的方向相反。在C++的STL中,优先队列默认是最大堆(使用`priority_queue<int>`),而最小堆需要显式指定比较函数(`priority_queue<int,vector<int>, greater<int>>`)。引用[5]的JS实现虽然可以工作,但入队操作使用了线性扫描插入,时间复杂度为$O(n)$,而堆实现是$O(\logn)$,因此在大数据量时堆实现更优。希望这个解释能帮助你理解优先队列出队和入队操作顺序及其原理。</think>### 优先队列出队与入队操作顺序及原理 #### 一、核心特性 优先队列是一种特殊队列,其**出队顺序由元素优先级决定**(而非普通队列的先进先出原则)。入队操作仅添加元素,出队操作则移除当前**优先级最高/最低**的元素(取决于队列类型)[^2][^4]。 #### 二、操作原理(基于二叉堆实现) ##### 1. 入队操作($O(\log n)$) 1. **添加元素**:将新元素插入堆的**末尾位置**(完全二叉树的最后一个节点) 2. **上浮调整(ShiftUp)**: - 比较新元素与其父节点的优先级 - 若违反堆序(如最大堆中父节点 < 新元素),则交换两者位置 - 重复上浮直至满足堆性质 ```python # 最大堆入队伪代码 def enqueue(value): heap.append(value) # 添加到末尾 index = len(heap) - 1 while index > 0: parent = (index-1)//2 # 父节点位置 if heap[parent] < heap[index]: swap(heap[parent], heap[index]) index = parent else: break ``` ##### 2. 出队操作($O(\log n)$) 1. **移除堆顶**:取出堆顶元素(优先级最高值) 2. **末尾补位**:将堆最后一个元素移至堆顶 3. **下沉调整(ShiftDown)**: - 比较新堆顶与其子节点的优先级 - 若违反堆序(如最大堆中父节点 < 子节点),与**优先级更高的子节点**交换 - 重复下沉直至满足堆性质 ```python # 最大堆出队伪代码 def dequeue(): max_val = heap[0] # 保存堆顶 heap[0] = heap.pop() # 末尾元素补位 index = 0 while True: left = 2*index + 1 # 左子节点 right = 2*index + 2 # 右子节点 largest = index if left < len(heap) and heap[left] > heap[largest]: largest = left if right < len(heap) and heap[right] > heap[largest]: largest = right if largest != index: swap(heap[index], heap[largest]) index = largest else: break return max_val ``` #### 三、执行顺序示例(最大堆) | 操作序列 | 堆内元素变化(括号为优先级) | 出队元素 | |----------|-----------------------------|----------| | 入队A(5) | [A(5)] | - | | 入队B(9) | [B(9), A(5)] | - | | 入队C(3) | [B(9), A(5), C(3)] | - | | **出队** | [A(5), C(3)] | B(9) | | 入队D(7) | [D(7), C(3), A(5)] | - | | **出队** | [A(5), C(3)] | D(7) | > **关键结论**: > - 入队顺序**不影响**出队顺序,**优先级**决定出队次序[^2][^4] > - 堆结构通过动态调整(上浮/下沉)维护全局有序性[^1][^3] #### 四、实现差异 - **最大优先队列**:出队优先级最高的元素(C++:`priority_queue<int>`)[^4] - **最小优先队列**:出队优先级最低的元素(C++:`priority_queue<int, vector<int>, greater<int>>`)[^4] - **相同优先级**:具体实现可能保留插入顺序(如JS示例[^5]),但堆实现通常不保证 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值