链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
题目描述
请你实现一个堆(大根堆)。
操作:
push x:将xxx加入堆。保证xxx为int型整数。不输出任何内容。
top:输出堆顶元素。若堆为空,输出"empty"(不含引号)。
pop:输出堆顶元素,且弹出堆顶。若堆为空,输出"empty"(不含引号)。
输入描述:
第一行输入一个整数nnn (1≤n≤1051\le n \le 10^51≤n≤105),表示操作次数。 接下来的nnn行,每行一个字符串,表示一个操作。保证操作是题目描述中的一种。
输出描述:
按对应操作要求输出。
输入:
11
push 1
top
push 3
top
push 2
top
pop
pop
pop
top
pop
输出:
1
3
3
3
2
1
empty
empty
答案:(优先队列的方法)
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
priority_queue<int>p;
void solve() {
int n, m;
cin >> n;
while (n--) {
string a;
cin >> a;
if (a == "push") {
cin >> m;
p.push(m);
}
else if (a == "top") {
if (!p.empty()) {
cout << p.top() << endl;
}
else {
cout << "empty" << endl;
}
}
else {
if (!p.empty()) {
cout << p.top() << endl;
p.pop();
}
else {
cout << "empty" << endl;
}
}
}
}
signed main() {
cin.tie(0); cout.tie(0); ios::sync_with_stdio(0);
int t = 1;
while (t--) {
solve();
}
return 0;
}
解释:(ai)
您提供的代码实现了一个基于优先队列(在 C++ STL 中是 priority_queue
,默认为最大堆)的模拟,用于处理三种操作:push
(向堆中添加元素)、top
(获取堆顶元素,即最大元素),以及一个未明确标注但实际上是 pop
(移除并返回堆顶元素)的操作。不过,根据您的描述和代码实现,我注意到一些可能需要注意的点:
-
堆的默认行为:C++ STL 中的
priority_queue
默认是一个最大堆,这意味着它的顶部元素(通过top()
访问)始终是队列中的最大元素。如果您需要最小堆,应该在声明时指定比较函数或使用std::priority_queue<int, std::vector<int>, std::greater<int>>
。 -
pop
操作:在您的代码中,对于未明确标注的操作(实际上是pop
),您先输出了堆顶元素,然后执行了pop()
。这是正确的实现方式,但请注意,这与您之前提供的基于数组的最小堆实现中的pop
操作有所不同,那里的pop
是将堆顶元素与堆的最后一个元素交换,然后调整堆。 -
性能考虑:虽然
priority_queue
在大多数情况下表现良好,但如果您需要频繁地进行插入和删除操作,并且关心性能,那么可能需要考虑其他数据结构,如斐波那契堆或配对堆(这些在 C++ STL 中并不可用,但可以在其他库中找到或自己实现)。 -
代码风格和可读性:您的代码使用了
#include<bits/stdc++.h>
和using namespace std;
,这在竞赛编程中很常见,但在生产环境中通常不推荐,因为它们可能导致命名冲突和编译时间增加。此外,使用signed main()
而不是更常见的int main()
也是不寻常的,虽然这不会改变程序的行为,但可能会让一些读者感到困惑。 -
输入/输出:您已经使用了
cin.tie(0); cout.tie(0); ios::sync_with_stdio(0);
来加速输入/输出,这是一个好习惯,尤其是在处理大量输入时。 -
测试案例:您的代码中没有包含测试案例的数量(即
t
),但您已经将其设置为 1,这意味着程序将只处理一组输入。如果您想要处理多组输入,您应该在while (t--)
循环中读取t
的值。 -
错误处理:虽然在这个特定的问题中可能不需要复杂的错误处理,但在实际应用中,考虑输入的有效性和异常情况总是一个好习惯。
综上所述,您的代码在功能上是正确的,但可能需要根据实际应用的需求进行一些调整。如果您确实需要最小堆而不是最大堆,那么请相应地更改 priority_queue
的声明。
标准大根堆写法:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
typedef pair<ll,ll> PII;
vector<int> heap(101010);
int n,sz,tem;
void down(int u){
int t=u,l=2*u,r=2*u+1;
if(l<=sz&&heap[t]<heap[l]){
t=l;
}
if(r<=sz&&heap[t]<heap[r]){
t=r;
}
if(t!=u){
swap(heap[t],heap[u]);
down(t);
}
}
void up(int u){
while(u/2&&heap[u/2]<heap[u]){
swap(heap[u/2],heap[u]);
u/=2;
}
}
int main(){
cin>>n;
while(n--){
string s;
cin>>s;
if(s=="push"){
sz++;
cin>>tem;
heap[sz]=tem;
up(sz);
}else if(s=="top"){
if(sz){
cout<<heap[1]<<endl;
}else{
cout<<"empty"<<endl;
}
}else{
if(sz){
cout<<heap[1]<<endl;
heap[1]=heap[sz];
sz--;
down(1);
}else{
cout<<"empty"<<endl;
}
}
}
}
解释:(ai)
这段代码实现了一个基于数组的最小堆(Min Heap)的模拟,并通过命令行接口(CLI)与用户交互,支持三种操作:push
(向堆中添加元素)、top
(获取堆顶元素,即最小元素)、以及一个未明确标注但实际上是pop
(移除并返回堆顶元素)的操作。不过,需要注意的是,代码中对于pop
操作的实现方式并不是典型的pop
,而是将堆顶元素替换为堆的最后一个元素,然后调整堆以保持其性质。这种实现方式在逻辑上是可行的,但命名上可能会引起混淆。
下面是代码的主要部分及其功能解释:
- 变量定义:
heap
:一个全局的整型向量,用于存储堆中的元素。n
:表示将要进行的操作数量。sz
:表示堆中当前元素的数量。tem
:用于临时存储输入的元素值。
down
函数:- 该函数用于在插入新元素后,从给定的位置
u
开始,向下调整堆,以保持堆的性质(即父节点的值小于或等于其子节点的值)。
- 该函数用于在插入新元素后,从给定的位置
up
函数:- 该函数用于在插入新元素到堆的末尾后,从该位置开始,向上调整堆,直到满足堆的性质。
- 主函数
main
:- 首先读取操作数量
n
。 - 然后进行
n
次循环,每次循环读取一个操作字符串s
。- 如果
s
是"push"
,则读取一个整数tem
,将其添加到堆的末尾(sz+1
的位置),然后通过up
函数调整堆。 - 如果
s
是"top"
,则如果堆不为空(sz > 0
),输出堆顶元素(即heap[1]
),否则输出"empty"
。 - 对于其他情况(实际上是模拟的
pop
操作),如果堆不为空,则输出堆顶元素,将堆的最后一个元素移动到堆顶位置,减小堆的大小sz
,然后通过down
函数调整堆。
- 如果
- 首先读取操作数量
注意:
- 在实际使用中,堆的大小(即
sz
)通常从0开始计数,但在这段代码中,由于堆存储在heap
向量的第一个位置(索引为0)并未使用(或者可以视为哨兵节点),因此堆的大小从1开始计数。这意味着堆的有效元素存储在heap[1]
到heap[sz]
的范围内。 - 代码中未对
heap
向量进行初始化或设置初始大小,但由于使用了vector
的动态特性,这并不影响其功能。然而,在实际应用中,可能需要考虑向量的初始大小以优化性能。 - 代码中对于
pop
操作的命名不够直观,可能会导致理解上的混淆。在实际应用中,建议明确命名以反映其实际功能。