棧和隊列
這周的題目,老師介紹了棧和隊列,所以在這里先簡要碼一下知識。
棧和隊列是兩種操作受限的特殊的線性表。
- 棧:只能在表尾進行插入或刪除操作。允許插入和刪除的一端稱為棧頂(top),另一端稱為棧底(bottom)。不含元素的空表稱空棧。棧的存儲結構:順序棧(順序棧一般可以用一維數組和一個棧頂指針來進行實現)和鏈棧(鏈棧即棧的鏈式存儲結構,可以用單鏈表進行實現)。
- 隊列:只能在表尾進行插入,在表頭進行刪除。
簡要知道了概念,我們可以了解一下C++STL中棧與隊列的模版,運用這些可以讓我們的解題更加便捷。C++基礎:C++標准庫之棧(stack)和隊列(queue)在這篇博客里面已經將這兩個模版寫的非常清楚了,我來簡要碼一下。1、棧(stack)說明及舉例:
使用棧,要先包含頭文件 :#include<stack>
定義棧,以如下形式實現:stack<Type> s;
其中Type為數據類型(如 int,float,char等)。
棧的主要操作:
s.push(item); //將item壓入棧頂
s.pop(); //刪除棧頂的元素,但不會返回
s.top(); //返回棧頂的元素,但不會刪除
s.size(); //返回棧中元素的個數
s.empty(); //檢查棧是否為空,如果為空返回true,否則返回false
2、隊列(queue)說明及舉例:
使用隊列,要先包含頭文件 :#include<queue>
定義隊列,以如下形式實現:queue<Type> q;
其中Type為數據類型(如 int,float,char等)。
隊列的主要操作:
q.push(item) //將item壓入隊列尾部
q.pop() //刪除隊首元素,但不返回
q.front() //返回隊首元素,但不刪除
q.back() //返回隊尾元素,但不刪除
q.size() //返回隊列中元素的個數
q.empty() //檢查隊列是否為空,如果為空返回true,否則返回false
1 移動圓盤
給出n個圓盤的半徑,現在要把這些圓盤依次放在柱子上,當准備把第i個半徑為ai的圓盤放置到柱子上時,如果柱子頂部的圓盤半徑小於ai,那么將柱子頂部的圓盤拿出,如果頂部的盤子半徑仍然小於ai,那么繼續拿出,直到頂部圓盤半徑大於或等於ai為止,此時才把第i個盤子放到柱子上。那么,最后從下往上輸出柱子上的圓盤半徑依次是什么?
輸入格式:
第一行包含一個整數n(n<=100000),表示有n個圓盤要依次放到柱子上。 接下來n行,每行一個整數,表示第i個圓盤的半徑ai (ai<=100000)。
輸出格式:
輸出多行,表示最后柱子上中的圓盤半徑。
輸入樣例:
5
5
3
2
4
1
輸出樣例:
5
4
1
Accepted
先來貼一段我不使用棧的方法來寫的代碼:
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string.h>
using namespace std;
int r[100005]={0},rn[100005]={0};
int main(){
int n,t=1;
int flag=0;
cin >> n;
int i;
for(i=1;i<=n;i++)
cin >> r[i];
rn[t]=r[1];
for(i=2;i<=n;i++){
if(r[i]>rn[t]){
while(t>=1&&rn[t]<r[i])
{ t--;flag=1;}
if(flag==1){t++;flag=0;}
rn[t]=r[i];
}
else{
t++;
rn[t]=r[i];
}
}
for(i=1;i<=t;i++){
cout << rn[i]<< endl;}
return 0;
}
那么這段代碼主要是通過定義兩個數組,從而實現滿足情況的圓盤班級存入另一個數組中,主要是對t的加減進行判斷。
前面已經大體介紹了隊列與棧的思想與用法,那么下面我貼出用棧的方法解決的代碼:
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <stack>
using namespace std;
stack<int> radius;
int main(){
int n,r,rn[100005];
cin >> n;
int i,top;
for(i=0;i<n;i++){
cin >> r;
if(radius.empty()) radius.push(r);
else{
top=radius.top();
while(r>top){
radius.pop();
if(radius.empty()) break;
top=radius.top();
}
radius.push(r);
}
}
i=0;
while (!radius.empty()){
rn[i]=radius.top();
radius.pop();
i++;
}
for(i--;i>=0;i--){
cout << rn[i] << endl;
}
return 0;
}
2 微信號
小明剛認識了新同學小紅,他想要小紅的微信號,小紅不想直接告訴他,所以給了小明一串加密了的數字,並且把解密規則告訴了小明。
解密規則是:首先刪除第1個數,接着把第2個數放在這串數的最后面,再刪除第3個數,並把第4個數放在這串數的最后面……直至只剩最后一個數,把最后一個數也刪除。
按照刪除的順序,把這些數字連在一起就是小紅的微信號。請你按照解密規則幫小明得到小紅的微信號。
輸入格式:
第一行包括一個正整數n(1 < n < 500),表示這串微信號的長度;
第二行包括n個數字,即加密的小紅的微信號。
輸出格式:
輸出解密后的微信號,相鄰數字之間有空格。
輸入樣例:
9
1 2 3 4 5 6 7 8 9
輸出樣例:
1 3 5 7 9 4 8 6 2
Accepted
這一題其實就是使用隊列的方法就可以解決問題,但是特別要注意的是,在輸出的每一個數字后都有一個空格,否則會出現格式錯誤,那么話不多說直接貼代碼:
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <stack>
#include<queue>
using namespace std;
queue<int> q;
int main(){
int n,i,w[1000],we,t=1;
cin >> n;
for(i=1;i<=n;i++){
cin >> we;
q.push(we);
}
while(!q.empty()){
w[t]=q.front();
t++;
q.pop();
if(q.size()==1){
w[t]=q.front();
break;
}
q.push(q.front());
q.pop();
}
for(i=1;i<=t;i++){
cout <<w[i] <<" ";
// if(i!=t) cout <<" ";
// else cout << endl;
}
return 0;
}
3 糖果
學校里有n個孩子,從1到n對這些孩子進行編號。老師將給孩子們分發糖果,第i個孩子希望至少獲得ai個糖果。
老師要求孩子們排隊。 最初,第i個孩子站在隊伍的第i個位置。 然后,老師開始分發糖果。分發糖果的規則是:將m個糖果給隊伍中的第一個孩子,如果這個孩子沒有得到足夠的糖果,那么這個孩子會走到隊伍的盡頭;否則這個孩子就回家了。當隊伍不為空時,重復這個規則一直分發糖果。 如果考慮所有孩子回家的順序。老師想知道,哪個孩子將是這個順序中的最后一個?
輸入格式:
第一行包含兩個整數n,m(1≤n≤100; 1≤m≤100)。 第二行包含n個整數a1,a2,...,an(1≤ai≤100)。
輸出格式:
輸出一個整數,代表最后一個孩子的編號。
輸入樣例:
在這里給出一組輸入。例如:
3 3
2 4 3
輸出樣例:
在這里給出相應的輸出。例如:
2
Accepted
這題很明顯是可以用隊列的方法來解決問題(但是其實我認為不用隊列的代碼其實更簡短)那么我們首先給出普通解法(來源:yjs)
#include<iostream>
using namespace std;
int candy[1000];
int main() {
int n,m,ans;
cin >> n >> m;
for(int i=0;i<n;i++) {
cin >>candy[i];
}
while(1) {
int change=0;
for(int i=0;i<n;i++) {
if(a[i]>0) {
candy[i]-=m;
change=1;
ans=i+1;
}
}
if(change==0) {
break;
}
}
cout << ans;
}
接下來貼出使用隊列的代碼:
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <stack>
#include<queue>
using namespace std;
queue<int> q;
int main(){
int n,i,m,candy[500];
cin >> n >> m;
for(i=1;i<=n;i++){
cin >> candy[i];
if(m<candy[i]) {
q.push(i);
candy[i]-=m;
}
}
if(q.empty()) cout << n <<endl;
else{
while(!q.empty()&&q.size()!=1){
if(candy[q.front()]>m){
candy[q.front()]-=m;
q.push(q.front());
q.pop();
}
else{
q.pop();
}
}
cout << q.front() << endl;
}
return 0;
}
4 誰比我大
給定一個含有n個整數的數列a1,a2,...an。定義函數 f(ai)表示數列中第i個元素ai之后第一個大於ai的元素的下標,若這樣的元素不存在,則f(ai)=0。
輸入格式:
第一行包含一個正整數n(n<=1e6);
第二行包含n個正整數 a1,a2,...an(1<=ai<=1e9)。
輸出格式:
輸出僅一行包含 n個整數,分別代表 f(ai) 的值。
輸入樣例:
5
1 4 2 3 5
輸出樣例:
2 5 4 5 0
Accepted
這一題剛拿到題目其實我就寫了一個函數用一個循環解決了問題,因為一開始覺得這樣做更加的簡便:
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <stack>
#include<queue>
using namespace std;
queue<int> q;
int a[1000005],n;
int find(int num){
int i;
for(i=num+1;i<=n;i++){
if(a[i]>a[num]) return i;
}
return 0;
}
int main(){
int i;
cin >> n;
for(i=1;i<=n;i++)
cin >>a[i];
for(i=1;i<=n;i++) {
cout << find(i) <<" ";
}
return 0;
}
那如何使用棧或隊列的知識解決這一題呢?其實這一題的解決方法是使用棧。那么如何解決呢?其實解決的關鍵在於我將輸入的數,倒一頭從尾到頭輸入棧中然后進行相應處理,那么結合代碼更好理解:
假如a[n-1]>a[n],那么在n-1之前的數,答案肯定不為n。所以設置一個棧,然后倒着從第n個數開始判斷。假如當前處理到a[i]:
- 第一步:把棧中所有小於a[i]的元素出棧;
- 第二步:棧頭元素就是要找的,離a[i]最近的比它大的數;
- 第三步:把a[i]壓進棧;
- 第四步:i=i-1;
當然最后不要忘記倒序輸出答案。
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <stack>
#include<queue>
using namespace std;
stack<int> s;
int a[1000005],n;
int main(){
int i,j;
int top;
int ans[1000005];
cin >> n;
for(i=1;i<=n;i++)
cin >>a[i];
i=1;j=n;
while(j>=1){
top=a[j];
while(!s.empty()&&a[s.top()]<=top)
s.pop();
if(s.empty()) ans[i]=0;
else ans[i]=s.top();
s.push(j);
i++;
j--;
}
for(j=i-1;j>=1;j--)
cout << ans[j] << " ";
return 0;
}
5 后綴表達式
所謂后綴表達式是指這樣的一個表達式:式中不再引用括號,運算符號放在兩個運算對象之后,所有計算按運算符號出現的順序,嚴格地由左而右進行(不用考慮運算符的優先級)。
如:中綴表達式 3(5–2)+7 對應的后綴表達式為:352-7+ 。
請將給出的中綴表達式轉化為后綴表達式並輸出。
輸入格式:
輸入僅一行為中綴表達式,式中所有數字均為個位數,表達式長度小於1000。
輸出格式:
輸出一行,為后綴表達式,式中無空格。
輸入樣例:
2+48+(88+1)/3
輸出樣例:
248+881+3/+
Accepted
(參考:中綴表達式轉換為后綴表達式(思路)、中綴表達式轉換為后綴表達式(C語言代碼+詳解)、洛谷)
具體轉換方式:
1.從左到右進行遍歷
2.運算數,直接輸出.
3.左括號,直接壓入堆棧,(括號是最高優先級,無需比較)(入棧后優先級降到最低,確保其他符號正常入棧)
4.右括號,(意味着括號已結束)不斷彈出棧頂運算符並輸出直到遇到左括號(彈出但不輸出)
5.運算符,將該運算符與棧頂運算符進行比較,
如果優先級高於棧頂運算符則壓入堆棧(該部分運算還不能進行),
如果優先級低於等於棧頂運算符則將棧頂運算符彈出並輸出,然后比較新的棧頂運算符.
(低於彈出意味着前面部分可以運算,先輸出的一定是高優先級運算符,等於彈出是因為同等優先級,從左到右運算)
直到優先級大於棧頂運算符或者棧空,再將該運算符入棧.
6.如果對象處理完畢,則按順序彈出並輸出棧中所有運算符.
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <stack>
using namespace std;
stack<char> sign;
int cmp(char a){
if(a=='*'||a=='/')return 2;
else if(a=='+'||a=='-') return 1;
else if(a=='^') return 3;
else return 0;
}
int main(){
string c;
cin >> c;
for(int i=0;i<c.size();i++){
if(c[i]>='0'&&c[i]<='9') //為數字,直接輸出結果
cout << c[i];
else{
if(c[i]==')'){//如果為右括號
while(sign.top()!='('){//彈出棧並輸出直到遇到左括號
cout << sign.top();
sign.pop();
}
sign.pop();// 彈出左括號
}
else if(c[i]=='(') sign.push(c[i]); // 如果為左括號,直接入棧
else{ //如果為普通的運算符
while(!sign.empty()&&cmp(sign.top())>=cmp(c[i])){
// 一直彈出棧並輸出直到遇到比當前優先級低的運算符,
cout << sign.top();
sign.pop();
}
sign.push(c[i]); //最后把當前運算符壓入棧中
}
}
}
//最后把棧中剩余的運算符依次彈棧輸出
while(!sign.empty()){
cout << sign.top();
sign.pop();
}
return 0;
}
6 后綴表達式計算
Kunkun學長覺得應該讓學弟學妹了解一下這個知識點:后綴表達式相對於中綴表達式更容易讓計算機理解和學習。現在kunkun學長給出一串后綴表達式,你能幫他算出這個后綴表達式的值嗎?
輸入格式:
第一行輸入后綴表達式長度n(1<=n<=25000);
第二行輸入一個字符串表示后綴表達式(每個數據或者符號之間用逗號隔開,保證輸入的后綴表達式合法,每個數包括中間結果保證不超過long long長整型范圍)
輸出格式:
輸出一個整數,即后綴表達式的值。
輸入樣例1:
6
10,2,+
輸出樣例1:
12
輸入樣例2:
14
2,10,2,+,6,/,-
輸出樣例2:
0
Wrong Answer
有了前一題的基礎,這一題處理起來相對會比較簡單,只不過這次我們是讓數字壓入棧中,然后如果碰到運算符,在對前兩個棧中的數字進行相應處理。特別注意數字可能有多位,以及運算時候的順序(特別是減法和除法時,順序不能顛倒)。
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <stack>
using namespace std;
stack<long long> num;
int main(){
int n,i,flag=0;
long long num1,num2;
char ch;
cin >> n;
getchar();
for(i=0;i<n;i++){
cin >> ch;
if(ch>='0'&&ch<='9'&&flag==0) {//如果為數字壓入棧中,注意數可能有多位
num.push(ch-'0');
flag=1;
}
else if(ch>='0'&&ch<='9'&&flag==1) {
num.top()= num.top()*10+(ch-'0');
flag=1;
}
else if(ch==','&&flag==1){
flag=0;
}
else if(ch=='+'){
num1=num.top();
num.pop();
num2=num.top();
num.pop();
num.push(num2+num1);
}
else if(ch=='-'){
num1=num.top();
num.pop();
num2=num.top();
num.pop();
num.push(num2-num1);
}
else if(ch=='*'){
num1=num.top();
num.pop();
num2=num.top();
num.pop();
num.push(num2*num1);
}
else if(ch=='/'){
num1=num.top();
num.pop();
num2=num.top();
num.pop();
num.push(num2/num1);
}
}
cout << num.top() << endl;
return 0;
}
但是當我提交代碼的時候,卻發現有兩個點顯示段錯誤,這是為什么呢?這是因為在這個表達式中存在負數,所以我需要對負數的符號與減號符號區分開。
Accepted
(參考:洛谷)
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <stack>
using namespace std;
stack<long long> num;
int main(){
int n,i,flag=0,flag1=0;
long long num1,num2;
char ch[25005] ;
cin >> n;
getchar();
for(i=0;i<n;i++)
cin >> ch[i];
for(i=0;i<n;i++){
if(ch[i]=='-'&&ch[i+1]>='0'&&ch[i+1]<='9') flag1=1;
if(ch[i]>='0'&&ch[i]<='9'&&flag==0) {//如果為數字壓入棧中,注意數可能有多位
num.push(ch[i]-'0');
flag=1;
}
else if(ch[i]>='0'&&ch[i]<='9'&&flag==1) {
num.top()= num.top()*10+(ch[i]-'0');
flag=1;
}
else if(ch[i]==','&&flag==1&&flag1==0)
flag=0;
else if(ch[i]==','&&flag==1&&flag1==1){ //若為負數
flag=0;
flag1=0;
num.top()= num.top()*(-1);
}
else if(ch[i]=='+'){
num1=num.top();
num.pop();
num2=num.top();
num.pop();
num.push(num2+num1);
}
else if(ch[i]=='-'&&flag1==0){
num1=num.top();
num.pop();
num2=num.top();
num.pop();
num.push(num2-num1);
}
else if(ch[i]=='*'){
num1=num.top();
num.pop();
num2=num.top();
num.pop();
num.push(num2*num1);
}
else if(ch[i]=='/'){
num1=num.top();
num.pop();
num2=num.top();
num.pop();
num.push(num2/num1);
}
}
cout << num.top() << endl;
return 0;
}