{ }codelper
接口泛型优先队列(堆)P0121

任务队列与中位数

自己写一个优先队列接口,以及一个接收比较方式、实现该接口的泛型二叉堆类。
不要使用语言内置的优先队列。
用这一个堆类同时做出任务队列,以及求优先级中位数的两个整数堆。
第一行是命令数 n,接下来 n 行每行一条命令。
"add name p" 把名为 name、优先级为 p 的任务放入队列。
同一个名字可能出现多次,视为不同的任务。
"next" 从队列中取出优先级最大的任务,输出 "name p"。
优先级相同时,先放入的任务先取出。
队列为空时输出 -。
"median" 输出到目前为止用 add 放入的所有优先级的中位数。
已经取出的任务的优先级也计算在内。
若放入的优先级个数为偶数,输出中间两个值中较小的那个。
若还没有放入任何任务,输出 -。

输入
1 ≤ n ≤ 100,000。
name 为 1 到 10 个小写英文字母。
-2,000,000,000 ≤ p ≤ 2,000,000,000(整数)。
next 和 median 合计不超过 5,000 条。
输出
每条 next 和 median 输出一行
样例 1 · stdin
9
add wash 3
add cook 5
add read 5
median
next
next
add nap -2
median
next
stdout
5
cook 5
read 5
3
wash 3
样例 2 · stdin
6
next
median
add big 2000000000
add low -2000000000
next
median
stdout
-
-
big 2000000000
-2000000000
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 3 种语言题解

本题只接受有接口和泛型的 TypeScript、Java、C#(JS 既没有接口也没有类型参数,Python 虽然能写 Protocol 和类型参数,但运行时不检查;C 和 Craykon 两者都没有;C++ 模板写法差别很大,本题不收)。
同一个堆类只换 T 和比较方式,就能同时用于任务(Task)和整数。
比较方式以函数类型接收:Java Comparator<? super T>、C# Comparison<T>、TS (a: T, b: T) => boolean。
Java 不能写 BinaryHeap<int>,只能存 Integer;C# 泛型直接存 int,不装箱。
内置的优先队列也不同。
Java java.util.PriorityQueue 和 C# PriorityQueue<TElement, TPriority>(.NET 6)都是最小的先出,TS(JavaScript)则根本没有。
用 for-each 遍历或打印 Java 队列,得到的是堆数组的顺序,并没有排好序。
三个陷阱:① 用减法 b - a 比较时,Java、C# 的 int 在 20 亿 − (−20 亿) 处溢出,符号反转。
应使用 Integer.compare、CompareTo(TS 的数是浮点数,不会溢出)。
② 堆不保持相等元素的放入顺序。
两种内置队列也不保证相同优先级的顺序,所以只把优先级交给 C# 内置队列时,相同优先级的任务会乱序取出。
必须把放入顺序也放进比较里。
③ 在 TS 严格模式下 pop() 的返回类型是 T | undefined,检查后才能使用。
用 ! 关掉检查虽然能编译,但从空队列取出时会抛出 TypeError(Java 解答用 null,C# 解答用 TryPop 返回 false 来表示)。
求中位数时,把较小的一半放进最大值在顶上的堆,把较大的一半放进最小值在顶上的堆,并保持较小一半的个数相等或多一个,那么这个堆的堆顶就是答案。

TypeScriptmain.ts · 87 行
import { readFileSync } from "node:fs"; // 우선순위 큐의 약속: 어떤 T든, "먼저 나올 것"을 정하는 비교 함수만 있으면 된다interface PriorityQueue<T> {  push(item: T): void;  pop(): T | undefined; // 비었으면 undefined  peek(): T | undefined;  size(): number;} // 이진 힙: before(a, b)가 참이면 a가 b보다 먼저 나온다class BinaryHeap<T> implements PriorityQueue<T> {  private items: T[] = [];  constructor(private before: (a: T, b: T) => boolean) {}   push(item: T): void {    const a = this.items;    a.push(item);    let i = a.length - 1;    while (i > 0) {      const parent = (i - 1) >> 1;      if (!this.before(a[i], a[parent])) break;      [a[i], a[parent]] = [a[parent], a[i]];      i = parent;    }  }   pop(): T | undefined {    const a = this.items;    if (a.length === 0) return undefined;    const top = a[0];    const last = a.pop() as T;    if (a.length > 0) {      a[0] = last;      let i = 0;      for (;;) {        const l = 2 * i + 1, r = l + 1;        let best = i;        if (l < a.length && this.before(a[l], a[best])) best = l;        if (r < a.length && this.before(a[r], a[best])) best = r;        if (best === i) break;        [a[i], a[best]] = [a[best], a[i]];        i = best;      }    }    return top;  }   peek(): T | undefined { return this.items[0]; }  size(): number { return this.items.length; }} interface Task {  name: string;  priority: number;  order: number; // 넣은 차례: 우선순위가 같으면 먼저 넣은 것부터} // 같은 BinaryHeap을 T = Task, T = number로 쓴다const tasks = new BinaryHeap<Task>((a, b) =>  a.priority !== b.priority ? a.priority > b.priority : a.order < b.order);const low = new BinaryHeap<number>((a, b) => a > b);  // 작은 절반, 가장 큰 것이 위const high = new BinaryHeap<number>((a, b) => a < b); // 큰 절반, 가장 작은 것이 위 function addPriority(p: number): void {  low.push(p);  high.push(low.pop() as number); // 작은 절반의 가장 큰 것을 큰 절반으로  if (high.size() > low.size()) low.push(high.pop() as number);} const lines: string[] = readFileSync(0, "utf8").split("\n");const n: number = Number(lines[0]);const out: string[] = [];for (let i = 1; i <= n; i++) {  const [op, name, p] = lines[i].trim().split(" ");  if (op === "add") {    tasks.push({ name, priority: Number(p), order: i });    addPriority(Number(p));  } else if (op === "next") {    const t = tasks.pop();    out.push(t === undefined ? "-" : `${t.name} ${t.priority}`);  } else {    const m = low.peek();    out.push(m === undefined ? "-" : String(m));  }}console.log(out.join("\n"));
JavaMain.java · 102 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.Comparator;import java.util.List; // 우선순위 큐의 약속: 어떤 T든, 비교 방법(Comparator)만 있으면 된다interface PriorityQueue<T> {    void push(T item);    T pop();   // 비었으면 null    T peek();  // 비었으면 null    int size();} // 이진 힙: order.compare(a, b) < 0 이면 a가 b보다 먼저 나온다class BinaryHeap<T> implements PriorityQueue<T> {    private final List<T> items = new ArrayList<>();    private final Comparator<? super T> order;     BinaryHeap(Comparator<? super T> order) { this.order = order; }     private boolean before(int i, int j) { return order.compare(items.get(i), items.get(j)) < 0; }     private void swap(int i, int j) {        T tmp = items.get(i);        items.set(i, items.get(j));        items.set(j, tmp);    }     public void push(T item) {        items.add(item);        int i = items.size() - 1;        while (i > 0 && before(i, (i - 1) / 2)) {            swap(i, (i - 1) / 2);            i = (i - 1) / 2;        }    }     public T pop() {        if (items.isEmpty()) return null;        T top = items.get(0);        T last = items.remove(items.size() - 1);        if (!items.isEmpty()) {            items.set(0, last);            int i = 0;            while (true) {                int l = 2 * i + 1, r = l + 1, best = i;                if (l < items.size() && before(l, best)) best = l;                if (r < items.size() && before(r, best)) best = r;                if (best == i) break;                swap(i, best);                i = best;            }        }        return top;    }     public T peek() { return items.isEmpty() ? null : items.get(0); }    public int size() { return items.size(); }} record Task(String name, int priority, int order) {} public class Main {    // 같은 BinaryHeap을 T = Task, T = Integer로 쓴다(List<int>처럼 int는 못 넣는다)    static final PriorityQueue<Task> tasks = new BinaryHeap<>(        Comparator.comparingInt(Task::priority).reversed().thenComparingInt(Task::order));    static final PriorityQueue<Integer> low = new BinaryHeap<>(Comparator.reverseOrder()); // 작은 절반, 가장 큰 것이 위    static final PriorityQueue<Integer> high = new BinaryHeap<>(Comparator.naturalOrder()); // 큰 절반, 가장 작은 것이 위     static void addPriority(int p) {        low.push(p);        high.push(low.pop()); // 작은 절반의 가장 큰 것을 큰 절반으로        if (high.size() > low.size()) low.push(high.pop());    }     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        int n = Integer.parseInt(in.readLine().trim());        StringBuilder out = new StringBuilder();        for (int i = 1; i <= n; i++) {            String[] p = in.readLine().trim().split(" ");            switch (p[0]) {                case "add" -> {                    int pri = Integer.parseInt(p[2]);                    tasks.push(new Task(p[1], pri, i));                    addPriority(pri);                }                case "next" -> {                    Task t = tasks.pop();                    out.append(t == null ? "-" : t.name() + " " + t.priority()).append('\n');                }                default -> {                    Integer m = low.peek();                    out.append(m == null ? "-" : m.toString()).append('\n');                }            }        }        System.out.print(out);    }}
C#Main.cs · 103 行
using System;using System.Collections.Generic;using System.Text; // 우선순위 큐의 약속: 어떤 T든, 비교 방법(Comparison<T>)만 있으면 된다interface IPriorityQueue<T>{    void Push(T item);    bool TryPop(out T item);  // 비었으면 false    bool TryPeek(out T item);    int Count { get; }} // 이진 힙: order(a, b) < 0 이면 a가 b보다 먼저 나온다class BinaryHeap<T> : IPriorityQueue<T>{    private readonly List<T> items = new();    private readonly Comparison<T> order;     public BinaryHeap(Comparison<T> order) { this.order = order; }     private bool Before(int i, int j) => order(items[i], items[j]) < 0;     private void Swap(int i, int j) => (items[i], items[j]) = (items[j], items[i]);     public void Push(T item)    {        items.Add(item);        int i = items.Count - 1;        while (i > 0 && Before(i, (i - 1) / 2))        {            Swap(i, (i - 1) / 2);            i = (i - 1) / 2;        }    }     public bool TryPop(out T item)    {        if (items.Count == 0) { item = default!; return false; }        item = items[0];        items[0] = items[^1];        items.RemoveAt(items.Count - 1);        int i = 0;        while (true)        {            int l = 2 * i + 1, r = l + 1, best = i;            if (l < items.Count && Before(l, best)) best = l;            if (r < items.Count && Before(r, best)) best = r;            if (best == i) break;            Swap(i, best);            i = best;        }        return true;    }     public bool TryPeek(out T item)    {        item = items.Count > 0 ? items[0] : default!;        return items.Count > 0;    }     public int Count => items.Count;} record Task(string Name, int Priority, int Order); class Program{    // 같은 BinaryHeap을 T = Task, T = int로 쓴다(C# 제네릭은 int를 포장 없이 담는다)    static readonly IPriorityQueue<Task> tasks = new BinaryHeap<Task>((a, b) =>        a.Priority != b.Priority ? b.Priority.CompareTo(a.Priority) : a.Order.CompareTo(b.Order));    static readonly IPriorityQueue<int> low = new BinaryHeap<int>((a, b) => b.CompareTo(a));  // 작은 절반, 가장 큰 것이 위    static readonly IPriorityQueue<int> high = new BinaryHeap<int>((a, b) => a.CompareTo(b)); // 큰 절반, 가장 작은 것이 위     static void AddPriority(int p)    {        low.Push(p);        low.TryPop(out int top);        high.Push(top); // 작은 절반의 가장 큰 것을 큰 절반으로        if (high.Count > low.Count && high.TryPop(out int small)) low.Push(small);    }     static void Main()    {        int n = int.Parse(Console.ReadLine()!.Trim());        var output = new StringBuilder();        for (int i = 1; i <= n; i++)        {            string[] p = Console.ReadLine()!.Trim().Split(' ');            if (p[0] == "add")            {                int pri = int.Parse(p[2]);                tasks.Push(new Task(p[1], pri, i));                AddPriority(pri);            }            else if (p[0] == "next")                output.Append(tasks.TryPop(out var t) ? $"{t.Name} {t.Priority}" : "-").Append('\n');            else                output.Append(low.TryPeek(out int m) ? m.ToString() : "-").Append('\n');        }        Console.Write(output);    }}