【資料結構】棧
基礎知識
關於棧你需要知道的基礎知識:
- LIFO(後進先出的線性表)
- 通常使用順序表表示棧(方便定位)
- 常用操作:push(入棧),pop(出棧),empty(判斷是否為空),top(取棧頂)
手寫棧
許多情況下我們可以使用C++STL庫的#include棧,但我們仍要學會手寫棧。
- 初始化操作InitStack(SqStack &S)
//構造空棧S S.base=(ElemType*)malloc(STACK_INIT_SIZE*sizeof(ElemType)); //為順序棧動態分配儲存空間 if(!S.base)exit(OVERFLOW); S.top=S.base; S.stacksize=STACK_INIT_SIZE; return OK;
- 銷燬棧操作DestroyStack(SqStak &S)
//若未分配棧空間
if(!S.base)return ERROR;
free(S.base);//回收棧空間
S.base=S.top=NULL;
S.stacksize=0;
return OK;
3.置空棧操作ClearStack(SqStack &S)
//若未分配棧空間
if(!S.base)return ERROR;
S.top=S.base;
return OK;
4.取棧頂元素GetTop(SqStack S,ElemType &e)
//棧空 if(S.top==S.base)return ERROR; e=*(S.top-1); return OK;
5.進棧操作Push(SqStack &S,ElemType e)
if ( S.top-S.base >= S.stacksize ) // 若棧滿則追加儲存空間 { S.base = (ElemType * ) realloc( S.base, ( S.stacksize + STACK_INCREMENT ) * sizeof( ElemType ) ); if ( ! S. base )exit(OVERFLOW); //儲存分配失敗 S.top = S.base + S.stacksize; S.stacksize += STACK_INCREMENT; } * S.top ++ = e;//元素e 插入棧頂,後修改棧頂指標 return OK;
6.出棧操作Pop(SqStack &S,ElemType &e)
if(S.top==S.base)return ERROR;//棧空
e=*--S.top;
return OK;
備註:
棧空:top==base
棧滿:top-base==STACKSIZE
出棧序列
判斷出棧序列
已知入棧序列,判斷出棧序列是否正確。無論是手寫判斷,還是程式碼實現,原理均為:
- 依據給定的出棧序列,在每次將入棧序列中的元素壓入棧之後嘗試進行出棧(出棧直到棧頂!=即將出棧元素為止),若最終所有元素都能成功出棧(棧大小為0),則出棧序列合法。
- 程式碼實現中,設定一個指標,每次成功出棧後,指向下一個即將出棧的元素。
卡特蘭數
一個棧進棧序列為1,2,3……n,問可能的出棧序列有多少個?
- 結論:C(2n,n)/(n+1)=(2n!)/(n!*n!)/(n+1)
- 推導:
#輸出序列的總數目=由左而右掃描由n個1和n個0組成的2n位二進位制數,其中:任何一個位置上之前出現的1的累計數不小於0的累計數的方案種數。
#所以有一半的方案不可行
輸出序列總數目:C(2n,n)-C(2n,n+1)=C(2n,n)/(n+1)
應用
數制轉換
題目:對於輸入的任意一個非負十進位制整數,列印輸出與其等值的八進位制數。
思路:計算過程是由低位到高位順序產生八進位制數的各個數位,而列印輸出恰好與計算過程相反。
stack <int>S;
int N;
void conversion()
{
while(!S.empty())S.pop();
cin >> N;
while(N)
{
S.push(N%8);
N=N/8;
}
while(!S.empty())
{
cout << S.top();
S.pop();
}
}
括號匹配
題目:給定一串括號序列,判斷其是否正確匹配。
思路:設定一個棧,每讀入一個括號:
- 若是右括號:要麼與棧頂對應的左括號抵消,要麼不匹配。
- 若是左括號:入棧。
- 若棧在開始、結束時都為空,則合法匹配。
表示式求值
波蘭表示式
*後續將專門開一個帖子補充。
算符優先演算法
遇運算元——儲存;
遇運算子號aj——與前面的剛掃描過的運算子ai比較:
- 若ai<aj 則儲存aj。(因此已儲存的運算子的優先關係為a1<a2<a3<a4…)
- 若ai>aj 則說明ai是已掃描的運算子中優先順序最高者,可進行運算。
- 若ai=aj 則說明括號內的式子已計算完,需要消去括號。
棧與遞迴
漢諾塔問題
- 遞迴實現:
// 遞迴實現漢諾塔
// n 為漢諾塔圓盤編號,從小到大為 1,2,3,……
void hanoi(int n, char A, char B, char C) {
if(n == 1) {
printf("%c -> %c\n", A, C); // 如果只有一個盤子,從 A 直接移動到 C
}else {
hanoi(n-1, A, C, B); // A 上的盤子,以 C 為輔助,移動到 B
hanoi(1, A, B, C); // 移動 A 上的最大的盤子到 C 上
hanoi(n-1, B, A, C); // 將 B 上的盤子以 A 為輔助,移動到 C
}
}
- 非遞迴實現(棧)
// 儲存函式狀態
struct Status {
int n;
char start, mid, end; // 初始塔,輔助中間塔,最終要移動到的塔
Status(int _n, char _A, char _B, char _C): n(_n), start(_A), mid(_B), end(_C) {}
};
// 採用非遞迴實現漢諾塔問題
// 由於棧的特殊性質,FILO,所以我們需要將原來函式的呼叫方式反過來進行
void hanoiStack(int n, char A, char B, char C) {
stack<Status> myS;
myS.push(Status(n, A, B, C));
while (!myS.empty())
{
Status ns = myS.top();
myS.pop();
if(ns.n == 1) {
printf("%c -> %c\n", ns.start, ns.end);
}else {
myS.push(Status(ns.n-1, ns.mid, ns.start, ns.end));
myS.push(Status(1, ns.start, ns.mid, ns.end));
myS.push(Status(ns.n-1, ns.start, ns.end, ns.mid));
}
}
}
參考連結:
- 漢諾塔問題的非遞迴實現及其思考:https://www.cnblogs.com/veeupup/p/12613921.html
- 組合數學角度:https://zhuanlan.zhihu.com/p/36085324
- 處理首尾遞迴:https://www.cnblogs.com/blogzcan/p/7485372.html
迷宮問題
思路:從每一個位置出發,下一步都有四種選擇(上右下左),先選擇一個方向,如果該方向能夠走下去,那麼就往這個方向走,當前位置切換為下一個位置。如果不能走,那麼換個方向走,如果所有方向都走不了,那麼退出當前位置,到上一步的位置去,當前位置切換為上一步的位置。一致這樣執行下去,如果當前位置是終點,那麼結束。如果走過了所有的路徑都沒能到達終點,那麼無解。
8皇后問題
問題:八皇后問題,是一個古老而著名的問題,是回溯演算法的典型案例。該問題是國際西洋棋棋手馬克斯·貝瑟爾於1848年提出:在8×8格的國際象棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。 高斯認為有76種方案。1854年在柏林的象棋雜誌上不同的作者發表了40種不同的解,後來有人用圖論的方法解出92種結果。計算機發明後,有多種計算機語言可以解決此問題。
思路:
- 從棋盤的第一行開始,從第一個位置開始,依次判斷當前位置是否能夠放置皇后,判斷的依據為:同該行之前的所有行中皇后的所在位置進行比較,如果在同一列,或者在同一條斜線上(斜線有兩條,為正方形的兩個對角線),都不符合要求,繼續檢驗後序的位置。
- 如果該行所有位置都不符合要求,則回溯(跳出這一層遞迴,即出棧)到前一行,改變皇后的位置,繼續試探。
- 如果試探到最後一行,所有皇后擺放完畢,則直接打印出 8*8 的棋盤。最後一定要記得將棋盤恢復原樣,避免影響下一次擺放。