目录
1.栈
概念
栈,是符合“后进先出”(Last In First Out,LIFO)规则规则的数据结构,有PUSH和POP两种操作,其中PUSH把元素压入“栈顶”,而POP从栈顶把元素“弹出。
STL的stack头文件提供了栈,用"stack<int> s"方式定义,用push()和pop()实现元素的入栈和出栈操作,top()取栈顶元素(但不删除)。
例题(集合栈计算机)
问题描述
有一个专门为了集合运算而设计的“集合栈”计算机。该机器有一个初始为空的栈,并且支持以下操作。
PUSH:空集“{}”入栈。
DUP:把当前栈顶元素复制一份后再入栈。
UNION:出栈两个集合,然会把两者的并集入栈。
INTERSECT:出栈两个集合,然后把二者的交集入栈。
ADD:出栈两个集合,然后把先出栈的集合加入到后出栈的集合中,把结果入栈。
每次操作后,输出栈顶集合的大小(即元素个数)。例如,栈顶元素是A={{},{{}}},下一个元素是B={{},{{{}}}},则:
UNION操作将得到{{},{{}},{{{}}}},输出3。
INTERSECT操作将得到{{}},输出1。
ADD操作将得到{{},{{{】}},{{},{{}}}},输出3.
输入不超过2000个操作,并且保证操作均能顺利进行(不需要对空栈执行出栈操作)。
示例代码
#include<iostream>
#include<cctype>
#include<vector>
#include<string>
#include<map>
#include<algorithm>
#include<stack>
#include<set>
#define ALL(x) x.begin(),x.end()//表示“所有的内容”
#define INS(x) inserter(x,x.begin())//表示“插入的迭代器”
using namespace std;
typedef set<int> Set;
map<Set, int> IDcache;//把集合映射成ID
vector<Set> Setcache;//根据ID取集合
//查找给定集合x的ID。如果找不到,分配一个新ID
int ID(Set x) {
if (IDcache.count(x)) {//如果能找到
return IDcache[x];//输出旧ID
}
Setcache.push_back(x);//如果找不到,压入栈
return IDcache[x] = Setcache.size() - 1;//输出新ID
}
int main() {
stack<int> s;//题目中的栈
int n;
cin >> n;
for (int i = 0; i < n; i++) {//n个操作
string op;
cin >> op;
if (op[0] == 'P') {//“{}入栈”
s.push(ID(Set()));
}
else if (op[0] == 'D') {//栈顶元素入栈
s.push(s.top());
}
else {
Set x1 = Setcache[s.top()];//元素A
s.pop();
Set x2 = Setcache[s.top()];//元素B
s.pop();
Set x;
if (op[0] == 'U') {//并
set_union(ALL(x1), ALL(x2), INS(x));
}
if (op[0] == 'I') {//交
set_intersection(ALL(x1), ALL(x2), INS(x));
}
if (op[0] == 'A') {//加
x = x2;
x.insert(ID(x1));
}
s.push(ID(x));
}
cout << Setcache[s.top()].size() << endl;
}
return 0;
}
分析
本题的集合并不是简单的整数集合或者字符串集合,而是集合的集合。为了方便起见,此处为每个不同的集合分配一个唯一的ID,则每个集合都可以表示成所含元素的ID集合,这样就可以用STL的set<int>来表示了,而整个栈则是一个stack<int>。
2.队列
概念
队列是符合“先进先出”(First In Fisrt Out,FIFO)原则的“公平队列”。
STL队列定义在头文件<queue>中,可以用“queue<int> s”方式定义,用push()和pop()进行元素的入列和出队操作,front()取队首元素(但不删除)。
例题(团队队列)
问题描述
有t个团队的人正在排一个长队。每次新来一个人时,如果他有队友在排队,那么这个新人会插到最后一个队友的身后。如果没有任何一个队友排队,则他会排到长队的队尾。
输入每个团队中所有队员的编号,要求支持如下三种指令(前两种指令可以穿插进行)。
ENQUEUE x:编号为x的人进入长队。
DEQUEUE:长队的队首出队。
STOP:停止模拟。
对于每个DEQUEUE指令,输出出队的人的编号。
示例代码
#include<cstdio>
#include<queue>
#include<map>
using namespace std;
const int maxt = 1000 + 10;
int main(){
int t, kase = 0;
while (scanf("%d", &t) == 1 && t) {
printf("Scenerio #%d\n", ++kase);
//记录所有人的团队编号
map<int, int>team;
for (int i = 0; i < t; i++) {//t个团队
int n, x;
scanf("%d", &n);
while (n--) {
scanf("%d", &x);
team[x] = i;
}
}
//模拟
queue<int> q, q2[maxt];//q是团队的队列,而q2[i]是团队i成员的队列
for (;;) {
int x;
char cmd[10];
scanf("%d", cmd);//命令
if (cmd[0] == 'S') {//停止模拟
break;
}
else if (cmd[0] == 'D') {//长队的队首出列
int t = q.front();//取队列的首号
printf("%d\n", q2[t].front());
q2[t].pop();//出队
if (q2[t].empty()) {//团体t全体出队列
q.pop();
}
}
else if (cmd[0] == 'E') {//编号为x的人进入队列
scanf("%d", &x);
int t = team[x];//找到队伍编号
if (q2[t].empty()) {//团队t进入队列
q.push(t);
}
}
}
printf("\n");
}
return 0;
}
分析
每个团队有一个队列,而团队整体又形成一个队列。
3.优先队列
概念
优先队列是一种抽象数据类型(Abstract Data Type,ADT),行为有些像队列,但先出队列的元素不是先进入队列的元素,而是队列中优先级最高的元素。
STL的优先队列也定义在头文件<queue>里,用"priority_queue<int> pq"来声明。这个pq是一个“越小的整数优先级越低的优先队列”。由于出队元素并不是最先进队的元素,出队的方法由queue的front()变成了top()。
自定义类型也可以组成优先队列,但必须为每个元素定义一个优先级。这个优先级并不需要一个确定的数字,只需要能比较大小即可。
STL的queue头文件提供了优先队列,用"priority_queue<int>s"方式定义,用push()和pop()进行元素的入队和出队操作,top()取队首元素(但不删除)。
例题(丑数)
问题描述
丑数是指不能被2,3,5以外的其他素数整除的数。把丑数从小到大排列起来,结果如下:
1,2,3,4,5,6,8,9,10,12,14, ....
求第1500个丑数。
示例代码
#include<vector>
#include<queue>
#include<set>
#include<iostream>
using namespace std;
typedef long long LL;
const int coeff[3] = { 2,3,5 };
int main(){
priority_queue<LL, vector<LL>, greater<LL> > pq;
set < LL> s;
pq.push(1);
s.insert(1);
for (int i = 1;; i++) {
LL x = pq.top();
pq.pop();
if (i == 1500) {
cout << "The 1500'th ugly number is" << x << ".\n";
break;
}
for (int j = 0; j < 3; j++) {
LL x2 = x*coeff[j];
if (!s.count(x2)) {//如果不存在x2
s.insert(x2);
pq.push(x2);
}
}
}
return 0;
}
分析
从小到大生成各个丑数,最小的丑数是1,而对于任意丑数x,2x,3x,5x也都是丑数。这样可以用一个优先队列保存所有已经生成的丑数,每次取出最小的丑数,生成3个新的丑数。唯一需要注意的是,同一个丑数有多种生成方式,所以需要判断一个丑数是否已经生成过。