棧的定義
棧(Stack)是限定僅在表尾進行插入和刪除操作的線性表。
我們把允許插入和刪除的一端稱爲棧頂(top),另一端稱爲棧底(bottom),不含任何數據元素的棧稱爲空棧。棧又稱爲後進先出(Last In First Out)的線性表,簡稱 LIFO 結構。
從上面這兩段話,可以確定:首先棧是一個線性表,也就是說,棧元素具有線性關係,即前驅後繼關係,只不過它是一種特殊的線性表。定義中說在線性表的表尾進行插入和刪除操作,這裏表尾是指棧頂,而不是棧底。
棧的插入操作,叫做進棧,也叫壓棧、入棧。
棧的刪除操作,叫做出棧。
進棧出棧變化多端
最先進棧的元素,不一定是最後出棧的。
棧對線性表的插入和刪除的位置進行了限制,並沒有對元素進出的時間進行限制。意思就是說,當並非所有的元素都進棧的情況下,先進去的元素也可以出棧,只要保證棧頂元素出棧就可以了。
比如,現在有 3 個元素 1、2、3,可能的出棧順序有下面幾種:
- 第一種:1、2、3 依次進棧,再 3、2、1依次出棧,顯然出棧順序爲321。
- 第二種:1 進棧,1 出棧,2 進棧,2 出棧,3 進棧,3 出棧。出棧順序爲 123。
- 第三種:1 進棧,2 進棧,2 出棧,1 出棧,3 進棧,3 出棧。出棧順序爲 213。
- 第四種:1 進棧,1 出棧,2 進棧,3 進棧,3 出棧,2 出棧。出棧順序爲 132。
- 第五種:1 進棧,2 進棧,2 出棧,3 進棧,3 出棧,1 出棧。出棧順序爲 231。
棧的抽象數據類型
我們知道,棧是一個特殊的線性表,由於它的特殊性,所以與線性表的操作會略有不同。特別是插入和刪除操作,我們分別叫它 push 和 pop。
棧的抽象數據類型
ADT 棧(stack)
Data
同線性表,元素具有相同的類型,相鄰元素具有前驅和後繼關係。
Operation
initStack(*S):初始化操作,建立一個空棧 S。
destroyStack(*S):若棧存在,則銷燬它。
clearStack(*S):清空棧。
isEmpty(S):若棧爲空返回 true,否則返回 false。
getTop(S, *e):若棧存在且非空,用 e 返回 S 的棧頂元素。
push(*S, e):若棧 S 存在,插入新元素 e 到棧 S 中併成爲棧頂元素。
pop(*S, *e):刪除棧頂元素,並且 e 返回其值。
length(S):返回棧 S 的元素個數。
endADT
由於棧本身就是一個線性表,所以線性表的順序存儲和鏈式存儲對於棧來說,同樣適用。
棧的順序存儲結構及實現
因爲棧是線性表的特例,棧的順序存儲其實是線性表順序存儲的簡化,簡稱爲順序棧。
線性表是用數組實現的,對於棧這種只能一端插入刪除的線性表來說,下標爲 0 的一端作爲棧底比較好,因爲首元素都存在棧底,變化最小。
我們定義一個變量 top 來指示棧頂元素在數組中的位置,若棧的長度爲 stackSize,則棧頂位置 top 必須小於 stackSize。當棧中存在一個元素時,top 等於 0,因此把空棧的判定條件定爲 top = -1。
棧的結構定義
define MAXSIZE = 10
typedef struct {
int data[MAXSIZE];
int top;
} SqStack;
進棧操作
boolean push(SqStack *s, int e) {
if (s->top == MAXSIZE - 1) {
return false;
}
s->top++;
s->data[s->top] = e;
return true;
}
出棧操作
若棧不爲空,則刪除棧頂元素,並把其值賦值給 e。
boolean pop(SqStac *s, int *e) {
if (s->top == -1)
return false;
*e = s->data[s->top];
s->top--;
return true;
}
通過進棧和出棧操作的代碼可以看出,時間複雜度都爲 O(1)。
兩棧共享空間
棧的順序存儲有一個很大的缺陷,必須先確定數組存儲空間大小,而且萬一不夠用了,還要擴容。
對於一個棧,我們只能儘量考慮周全,設計出合適大小的數組來處理,但對於兩個相同類型的棧,我們可以做到最大限度地利用事先開闢的存儲空間來進行操作。
如果我們有兩個相同類型的棧,我們爲它們各自開闢了數組空間,有可能第一個棧已經滿了,而另一個棧還有很多存儲空間。我們完全可以用一個數組來存儲兩個棧。
我們知道,數組有兩個端點,兩個棧有兩個棧底。可以讓一個棧的棧底爲數組的開始端,即下標爲 0 處,另一個棧的棧底爲數組的末端,即下標爲數組長度 n - 1 處,這樣兩個棧如果增加元素,就是兩端點向中間移動,如圖所示。
兩棧共享
top1 和 top2 是棧 1 和棧 2 的棧頂指針,只要二者的值不相等,兩個棧就可以進行入棧操作。當 top1 等於 -1 時,棧 1 爲空;當 top2等於 n 時,棧 2 爲空。當 top2 - top1 = 1 時,棧就滿了。
兩棧共享空間的代碼如下:
typedef struct {
int data[MAXSIZE];
int top1;
int top2;
} SqDoubleStack;
對於兩棧共享空間的插入方法,除了要插入元素值參數外,還需要有一個判斷是棧 1 還是棧 2 的棧號參數。代碼如下:
boolean push(SqDoubleStack *s, int e, int stackNumber) {
if (s->top2 - s->top1 == 1) { /* 棧滿 */
return false;
}
if (stackNumber == 1) {
s->data[++s->top1] = e;
} else if (stackNumber == 2) {
s->data[--s->top2] = e;
}
return true;
}
出棧方法的代碼
boolean pop(SqDoubleStack *s, int *e, int stackNumber) {
if (stackNumber == 1) {
if (s->top1 == -1)
return false;
*e = s->data[s->top1--];
} else if (stackNumber == 2) {
if (s->top2 == MAXSIZE)
return false;
*e = s->data[s->top2++];
}
return true;
}
兩棧共享通常用於兩個棧的空間需求具有相反關係時,也就是一個棧增長時另一個棧在縮短。就像買賣股票一樣,一個人買入時,一定有另一個人在做賣出操作。這樣使用兩棧共享空間存儲方法纔有比較大的意義。
兩棧共享只針對兩個具有相同數據類型的棧。
棧的鏈式存儲結構
棧的鏈式存儲結構,簡稱爲鏈棧。
由於單鏈表有頭指針,而棧頂指針也是必須的,所以把棧頂放在鏈表的頭部。另外,由於已經有了棧頂在頭部,所以單鏈表中的頭結點就不需要了。
對於鏈棧來說,基本不存在棧滿的情況,因爲只要內存有空間,鏈表就可以一直添加結點。
鏈棧的結構代碼如下:
/**
* Node結點
*/
typedef struct StackNode {
int data;
struct StackNode *next;
} StackNode, *pStackNode;
typedef struct LinkStack {
pStackNode top; /* 棧頂指針 */
int count;
} LinkStack;
棧的鏈式存儲結構——進棧操作
假設新結點 s 的值爲 e,top 爲棧頂指針,將 s->next 指向當前棧頂結點,棧元素個數加 1。
boolean push(LinkStack *s, int e) {
pStackNode pNewNode = (pStackNode)malloc(sizeof(StackNode));
if (NULL == newNode)
return false;
pNewNode->data = e;
/* 把當前棧頂元素賦值給新結點的直接後繼 */
pNewNode->next = s->top;
/* 新結點作爲棧頂 */
s->top = pNewNode;
s->count ++;
return true;
}
棧的鏈式存儲——出棧操作
出棧的操作步驟:先用一個變量 p 來存儲要刪除的棧頂結點,將棧頂指針 改爲 p->next,釋放 p。
boolean pop(LinkStack *s, int *e) {
if (stackEmpty(*s)) {
return false;
}
pStackNode pNode = s->top;
*e = pNode->data;
s->top = pNode->next;
free(pNode);
s->count--;
return true;
}
上面出棧操作中,用到判斷棧是否爲空,那麼鏈棧是怎樣判斷是否爲空棧的呢?
boolean stackEmpty(LinkStack *s) {
if (s->top->next == NULL)
return true;
else
return false;
}
總結
從鏈棧的進棧和出棧操作,可以看出時間複雜度都爲 O(1)。
對比順序棧和鏈棧,它們的時間複雜度是一樣的,對於空間性能,順序棧需要先確定一個固定的長度,可能會造成空間浪費,但是它在存取時定位很方便,而鏈棧則要求每個元素都有指針域,無形中增加了內存開銷,但是鏈棧對長度是沒有限制的。
所以它們的區別和線性表的順序存儲以及鏈式存儲的區別是一樣的:如果棧中的元素個數變化較大,那麼使用鏈棧再合適不過,反之,如果元素個數變化較小,使用順序棧會更好一些。
作者:Xiao_Mai
鏈接:https://www.jianshu.com/p/66da0b8935ac
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。