一、问题描述
题目描述
某银行将客户分为了若干个优先级,1 级最高,5 级最低。当你需要在银行办理业务时,优先级高的人随时可以插队到优先级低的人的前面。
现在给出一个人员到来和银行办理业务的时间序列,请你在每次银行办理业务时输出客户的编号。
如果同时有多位优先级相同且最高的客户,则按照先来后到的顺序办理。
输入描述
输入第一行是一个正整数 n
,表示输入的序列中的事件数量。(1 ≤ n ≤ 500)
接下来有 n
行,每行第一个字符为 a
或 p
。
- 当字符为
a
时,后面会有两个正整数num
和x
,表示到来的客户编号为num
,优先级为x
。 - 当字符为
p
时,表示当前优先级最高的客户去办理业务。
输出描述
输出包含若干行,对于每个 p
,输出一行,仅包含一个正整数 num
,表示办理业务的客户编号。
用例
输入
4
a 1 3
a 2 2
a 3 2
p
输出
1
题目解析
本题属于简单的优先队列应用,但需要注意以下几点:
-
优先级处理逻辑:
- 客户的优先级为
x
,数字越小优先级越高。 - 如果优先级相同时,按照先来后到的顺序处理。
- 客户的优先级为
-
队列管理:
- 在输入中记录每个客户的优先级和到来的顺序。
- 使用优先队列(或排序结构)动态维护当前的最高优先级客户。
-
特殊情况处理:
p
的数量可能多于a
的数量。如果此时队列中没有客户办理业务,应当输出空。
解题思路
- 使用 Python 的
heapq
模块实现优先队列(小根堆)。 - 在插入客户时,将优先级和到达顺序一起存入堆中,以便动态管理优先级和到达顺序。
- 在
p
操作时,提取队列中优先级最高且到来时间最早的客户。 - 维护一个备用的字典(或集合)以检查某些客户是否已经办理业务(防止重复处理)。
示例代码
python
import heapq
def handle_bank_events(events):
# 优先队列(小根堆),存储的是 (-优先级, 到达顺序, 客户编号)
queue = []
result = []
count = 0 # 到达顺序计数器
for event in events:
if event[0] == 'a':
_, num, priority = event
# 插入堆中,优先级取反以便实现大优先级在堆顶
heapq.heappush(queue, (-priority, count, num))
count += 1
elif event[0] == 'p':
if queue:
# 取出堆顶元素,即优先级最高且到达时间最早的客户
_, _, customer_num = heapq.heappop(queue)
result.append(customer_num)
else:
# 如果队列为空,输出空字符串(或跳过)
result.append("")
return result
# 输入处理
n = int(input())
events = []
for _ in range(n):
line = input().split()
if line[0] == 'a':
events.append(('a', int(line[1]), int(line[2])))
elif line[0] == 'p':
events.append(('p',))
# 获取结果并输出
output = handle_bank_events(events)
for res in output:
if res != "":
print(res)
关键点说明
-
优先队列的设计:
- 使用 (-优先级, 到达顺序, 客户编号) 的元组存储到堆中,确保按照优先级从高到低排序。如果优先级相同,则按照到达顺序处理。
-
边界情况处理:
- 如果
p
操作时队列为空,优雅地处理并输出空字符串或跳过。
- 如果
-
复杂度分析:
- 插入和删除操作的时间复杂度均为
O(log n)
,在最多 500 次操作内性能良好。
- 插入和删除操作的时间复杂度均为
测试用例
用例 1
输入
4
a 1 3
a 2 2
a 3 2
p
输出
1
用例 2
输入
5
a 1 5
a 2 3
p
a 3 2
p
输出
2
3
用例 3
输入
3
p
a 1 3
p
输出
1
二、JavaScript算法源码
以下是 JavaScript 代码的中文详细注释和逻辑讲解:
代码逻辑
javascript
/* JavaScript Node ACM模式 控制台输入获取 */
const readline = require("readline");
// 创建 readline 接口,用于从控制台读取输入
const rl = readline.createInterface({
input: process.stdin, // 输入流
output: process.stdout, // 输出流
});
const lines = []; // 存储输入的行
let n; // 存储输入的行数
// 监听输入事件
rl.on("line", (line) => {
lines.push(line); // 将输入的行存入 lines 数组
if (lines.length === 1) {
n = lines[0] - 0; // 第一行是行数 n,转换为数字
}
// 当输入的行数达到 n + 1 时(第一行是 n,后面是 n 行数据)
if (n && lines.length === n + 1) {
lines.shift(); // 移除第一行(n)
const seq = lines.map((line) => line.split(" ")); // 将每行按空格分割成数组
getResult(n, seq); // 调用算法函数
lines.length = 0; // 清空 lines 数组,准备下一轮输入
}
});
// 算法入口
function getResult(n, seq) {
// 创建优先队列,自定义比较函数
const pq = new PriorityQueue((a, b) =>
a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]
);
// 遍历每行输入
for (let i = 0; i < n; i++) {
const tmp = seq[i]; // 当前行的指令和数据
switch (tmp[0]) {
case "a": // 如果是 "a" 指令
const num = Number(tmp[1]); // 获取第二个参数(数字)
const x = Number(tmp[2]); // 获取第三个参数(优先级)
pq.offer([x, i, num]); // 将 [x, i, num] 加入优先队列
break;
case "p": // 如果是 "p" 指令
const cust = pq.poll(); // 从优先队列中取出优先级最高的元素
if (cust) console.log(cust[2]); // 如果存在,输出第三个参数(数字)
else console.log(""); // 如果队列为空,输出空字符串
}
}
}
// 基于堆实现优先队列
class PriorityQueue {
constructor(cpr) {
this.queue = []; // 存储队列元素的数组
this.cpr = cpr; // 自定义比较函数
}
// 交换两个元素
swap(a, b) {
const tmp = this.queue[a];
this.queue[a] = this.queue[b];
this.queue[b] = tmp;
}
// 上浮操作(插入元素后调整堆)
swim() {
let c = this.queue.length - 1; // 当前节点的索引
while (c >= 1) {
const f = Math.floor((c - 1) / 2); // 父节点的索引
// 如果当前节点比父节点优先级高,交换位置
if (this.cpr(this.queue[c], this.queue[f]) < 0) {
this.swap(c, f);
c = f; // 继续向上比较
} else {
break; // 否则退出循环
}
}
}
// 入队操作
offer(val) {
this.queue.push(val); // 将元素加入队列
this.swim(); // 调整堆
}
// 下沉操作(删除元素后调整堆)
sink() {
let f = 0; // 当前节点的索引
while (true) {
let c1 = 2 * f + 1; // 左子节点的索引
let c2 = c1 + 1; // 右子节点的索引
let c; // 优先级更高的子节点的索引
let val1 = this.queue[c1]; // 左子节点的值
let val2 = this.queue[c2]; // 右子节点的值
// 选择优先级更高的子节点
if (val1 && val2) {
c = this.cpr(val1, val2) < 0 ? c1 : c2;
} else if (val1 && !val2) {
c = c1;
} else if (!val1 && val2) {
c = c2;
} else {
break; // 如果没有子节点,退出循环
}
// 如果子节点比当前节点优先级高,交换位置
if (this.cpr(this.queue[c], this.queue[f]) < 0) {
this.swap(c, f);
f = c; // 继续向下比较
} else {
break; // 否则退出循环
}
}
}
// 出队操作
poll() {
this.swap(0, this.queue.length - 1); // 交换堆顶和最后一个元素
const res = this.queue.pop(); // 移除最后一个元素(原堆顶)
this.sink(); // 调整堆
return res; // 返回移除的元素
}
// 查看堆顶元素
peek() {
return this.queue[0];
}
// 获取队列大小
size() {
return this.queue.length;
}
}
代码讲解
-
输入处理:
- 使用
readline
模块从控制台读取输入。 - 第一行是行数
n
,后面是n
行指令和数据。 - 每行指令和数据按空格分割成数组,存储在
seq
中。
- 使用
-
优先队列:
- 使用堆实现优先队列,支持插入(
offer
)和删除(poll
)操作。 - 自定义比较函数
cpr
,用于比较元素的优先级。
- 使用堆实现优先队列,支持插入(
-
指令处理:
- 如果是
"a"
指令,将[x, i, num]
加入优先队列,其中x
是优先级,i
是索引,num
是数字。 - 如果是
"p"
指令,从优先队列中取出优先级最高的元素,并输出其num
值。
- 如果是
-
堆操作:
swim
:插入元素后,从下往上调整堆。sink
:删除元素后,从上往下调整堆。swap
:交换两个元素的位置。
-
输出结果:
- 根据指令处理结果,输出相应的值。
示例解析
输入
5
a 10 1
a 20 2
p
a 30 3
p
运行结果
10
20
- 解析:
- 插入
[1, 0, 10]
和[2, 1, 20]
。 - 第一次
p
指令取出优先级最高的[1, 0, 10]
,输出10
。 - 插入
[3, 2, 30]
。 - 第二次
p
指令取出优先级最高的[2, 1, 20]
,输出20
。
- 插入
总结
- 该代码实现了一个基于堆的优先队列,支持插入和删除操作。
- 通过自定义比较函数,可以灵活定义优先级规则。
- 适用于需要动态维护优先级顺序的场景。
如果有其他问题,欢迎随时提问!
三、Java算法源码
以下是 Java 代码的中文详细注释和逻辑讲解:
代码逻辑
java
import java.util.PriorityQueue;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in); // 创建 Scanner 对象,用于读取输入
int n = Integer.parseInt(sc.nextLine()); // 读取第一行输入,表示操作的数量
String[][] arr = new String[n][]; // 创建一个二维数组,用于存储每行的操作和参数
for (int i = 0; i < n; i++) {
arr[i] = sc.nextLine().split(" "); // 将每行输入按空格分割成数组
}
getResult(n, arr); // 调用算法函数
}
public static void getResult(int n, String[][] arr) {
// 创建优先队列,自定义比较器
PriorityQueue<int[]> pq =
new PriorityQueue<>((a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]);
// 遍历每行操作
for (int i = 0; i < n; i++) {
String[] tmp = arr[i]; // 获取当前行的操作和参数
switch (tmp[0]) { // 根据操作类型进行处理
case "a": // 如果是 "a" 操作
int num = Integer.parseInt(tmp[1]); // 获取第二个参数(数字)
int x = Integer.parseInt(tmp[2]); // 获取第三个参数(优先级)
pq.offer(new int[] {x, i, num}); // 将 [x, i, num] 加入优先队列
break;
case "p": // 如果是 "p" 操作
int[] poll = pq.poll(); // 从优先队列中取出优先级最高的元素
if (poll != null) System.out.println(poll[2]); // 如果存在,输出第三个参数(数字)
else System.out.println(""); // 如果队列为空,输出空字符串
}
}
}
}
代码讲解
-
输入处理:
- 使用
Scanner
从控制台读取输入。 - 第一行是操作的数量
n
。 - 接下来的
n
行是具体的操作和参数,每行按空格分割成数组,存储在二维数组arr
中。
- 使用
-
优先队列:
- 使用
PriorityQueue
实现优先队列。 - 自定义比较器
(a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]
:- 首先比较优先级
a[0]
和b[0]
,优先级小的排在前面。 - 如果优先级相同,则比较插入顺序
a[1]
和b[1]
,先插入的排在前面。
- 首先比较优先级
- 使用
-
操作处理:
- 如果是
"a"
操作:- 将
[x, i, num]
加入优先队列,其中x
是优先级,i
是插入顺序,num
是数字。
- 将
- 如果是
"p"
操作:- 从优先队列中取出优先级最高的元素,并输出其
num
值。 - 如果队列为空,输出空字符串。
- 从优先队列中取出优先级最高的元素,并输出其
- 如果是
-
输出结果:
- 根据操作类型处理结果,输出相应的值。
示例解析
输入
5
a 10 1
a 20 2
p
a 30 3
p
运行结果
10
20
- 解析:
- 插入
[1, 0, 10]
和[2, 1, 20]
。 - 第一次
p
操作取出优先级最高的[1, 0, 10]
,输出10
。 - 插入
[3, 2, 30]
。 - 第二次
p
操作取出优先级最高的[2, 1, 20]
,输出20
。
- 插入
总结
- 该代码实现了一个基于优先队列的操作处理系统。
- 通过自定义比较器,可以灵活定义优先级规则。
- 适用于需要动态维护优先级顺序的场景。
如果有其他问题,欢迎随时提问!
四、Python算法源码
以下是 Python 代码的中文详细注释和逻辑讲解:
代码逻辑
python
import queue # 导入 queue 模块,用于实现优先队列
# 输入获取
n = int(input()) # 读取第一行输入,表示操作的数量
seq = [input().split() for _ in range(n)] # 读取接下来的 n 行输入,每行按空格分割成列表
# 定义一个客户类,实现自定义优先级
class Customer:
def __init__(self, num, x, index):
"""
:param num: 客户编号
:param x: 客户优先级
:param index: 客户先来后到顺序
"""
self.num = num # 客户编号
self.x = x # 客户优先级
self.index = index # 客户插入顺序
def __lt__(self, other):
"""
自定义比较方法,用于优先队列的排序
:param other: 另一个 Customer 对象
:return: 当前对象是否优先级更高
"""
if self.x != other.x:
return self.x < other.x # 优先级小的排在前面
else:
return self.index < other.index # 如果优先级相同,先插入的排在前面
# 算法入口
def getResult(n, seq):
pq = queue.PriorityQueue() # 创建一个优先队列
for i in range(n): # 遍历每行操作
tmp = seq[i] # 获取当前行的操作和参数
if tmp[0] == 'a': # 如果是 "a" 操作
num = int(tmp[1]) # 获取第二个参数(客户编号)
x = int(tmp[2]) # 获取第三个参数(优先级)
pq.put(Customer(num, x, i)) # 将 Customer 对象加入优先队列
elif tmp[0] == 'p': # 如果是 "p" 操作
if pq.qsize() > 0: # 如果队列不为空
customer = pq.get() # 取出优先级最高的 Customer 对象
print(customer.num) # 输出客户编号
else:
print("") # 如果队列为空,输出空字符串
# 算法调用
getResult(n, seq) # 调用算法函数
代码讲解
-
输入处理:
- 使用
input()
读取输入。 - 第一行是操作的数量
n
。 - 接下来的
n
行是具体的操作和参数,每行按空格分割成列表,存储在seq
中。
- 使用
-
优先队列:
- 使用
queue.PriorityQueue
实现优先队列。 - 自定义
Customer
类,实现__lt__
方法,用于定义优先级规则:- 首先比较优先级
x
,优先级小的排在前面。 - 如果优先级相同,则比较插入顺序
index
,先插入的排在前面。
- 首先比较优先级
- 使用
-
操作处理:
- 如果是
"a"
操作:- 将
Customer(num, x, i)
加入优先队列,其中num
是客户编号,x
是优先级,i
是插入顺序。
- 将
- 如果是
"p"
操作:- 从优先队列中取出优先级最高的
Customer
对象,并输出其num
值。 - 如果队列为空,输出空字符串。
- 从优先队列中取出优先级最高的
- 如果是
-
输出结果:
- 根据操作类型处理结果,输出相应的值。
示例解析
输入
5
a 10 1
a 20 2
p
a 30 3
p
运行结果
10
20
- 解析:
- 插入
Customer(10, 1, 0)
和Customer(20, 2, 1)
。 - 第一次
p
操作取出优先级最高的Customer(10, 1, 0)
,输出10
。 - 插入
Customer(30, 3, 2)
。 - 第二次
p
操作取出优先级最高的Customer(20, 2, 1)
,输出20
。
- 插入
总结
- 该代码实现了一个基于优先队列的操作处理系统。
- 通过自定义
Customer
类和__lt__
方法,可以灵活定义优先级规则。 - 适用于需要动态维护优先级顺序的场景。
如果有其他问题,欢迎随时提问!
五、C/C++算法源码:
以下是 C++ 代码的中文详细注释和逻辑讲解:
代码逻辑
cpp
#include <iostream>
#include <queue>
#include <vector>
#include <string>
using namespace std;
// 定义一个客户类,实现自定义优先级
class Customer {
public:
int num; // 客户编号
int x; // 客户优先级
int index; // 客户先来后到顺序
// 构造函数
Customer(int num, int x, int index) : num(num), x(x), index(index) {}
// 重载 < 运算符,用于优先队列的比较
bool operator<(const Customer& other) const {
if (x != other.x) {
return x > other.x; // 优先级小的排在前面
} else {
return index > other.index; // 先来后到顺序小的排在前面
}
}
};
// 算法入口
void getResult(int n, vector<vector<string>>& seq) {
priority_queue<Customer> pq; // 创建一个优先队列
for (int i = 0; i < n; i++) { // 遍历每行操作
vector<string> tmp = seq[i]; // 获取当前行的操作和参数
if (tmp[0] == "a") { // 如果是 "a" 操作
int num = stoi(tmp[1]); // 获取第二个参数(客户编号)
int x = stoi(tmp[2]); // 获取第三个参数(优先级)
pq.push(Customer(num, x, i)); // 将 Customer 对象加入优先队列
} else if (tmp[0] == "p") { // 如果是 "p" 操作
if (!pq.empty()) { // 如果队列不为空
Customer customer = pq.top(); // 取出优先级最高的 Customer 对象
pq.pop(); // 从队列中移除该对象
cout << customer.num << endl; // 输出客户编号
} else {
cout << "" << endl; // 如果队列为空,输出空字符串
}
}
}
}
int main() {
int n;
cin >> n; // 读取第一行输入,表示操作的数量
cin.ignore(); // 忽略换行符
vector<vector<string>> seq(n); // 创建一个二维向量,用于存储每行的操作和参数
for (int i = 0; i < n; i++) { // 读取接下来的 n 行输入
string line;
getline(cin, line); // 读取整行输入
size_t pos = 0;
string token;
while ((pos = line.find(' ')) != string::npos) { // 按空格分割字符串
token = line.substr(0, pos);
seq[i].push_back(token); // 将分割后的字符串加入当前行的向量
line.erase(0, pos + 1);
}
seq[i].push_back(line); // 将最后一个字符串加入当前行的向量
}
// 调用算法
getResult(n, seq);
return 0;
}
代码讲解
-
输入处理:
- 使用
cin
读取输入。 - 第一行是操作的数量
n
。 - 接下来的
n
行是具体的操作和参数,每行按空格分割成字符串,存储在二维向量seq
中。
- 使用
-
优先队列:
- 使用
priority_queue
实现优先队列。 - 自定义
Customer
类,重载<
运算符,用于定义优先级规则:- 首先比较优先级
x
,优先级小的排在前面。 - 如果优先级相同,则比较插入顺序
index
,先插入的排在前面。
- 首先比较优先级
- 使用
-
操作处理:
- 如果是
"a"
操作:- 将
Customer(num, x, i)
加入优先队列,其中num
是客户编号,x
是优先级,i
是插入顺序。
- 将
- 如果是
"p"
操作:- 从优先队列中取出优先级最高的
Customer
对象,并输出其num
值。 - 如果队列为空,输出空字符串。
- 从优先队列中取出优先级最高的
- 如果是
-
输出结果:
- 根据操作类型处理结果,输出相应的值。
示例解析
输入
5
a 10 1
a 20 2
p
a 30 3
p
运行结果
10
20
- 解析:
- 插入
Customer(10, 1, 0)
和Customer(20, 2, 1)
。 - 第一次
p
操作取出优先级最高的Customer(10, 1, 0)
,输出10
。 - 插入
Customer(30, 3, 2)
。 - 第二次
p
操作取出优先级最高的Customer(20, 2, 1)
,输出20
。
- 插入
总结
- 该代码实现了一个基于优先队列的操作处理系统。
- 通过自定义
Customer
类和重载<
运算符,可以灵活定义优先级规则。 - 适用于需要动态维护优先级顺序的场景。
如果有其他问题,欢迎随时提问!