(一)棧的定義
棧是一種重要的線性結構。是我們前面講過的線性表的一種具體形式
棧是限定僅在表尾進行插入和刪除操作的線性表
我們把允許插入和刪除的一端稱為棧頂top,另一端稱為棧底bottom,不含任何元素的棧稱為空棧。棧又稱為后進先出(Last In First Out)的線性表,簡稱LIFO
棧的插入操作,叫做進棧,也稱為壓棧,入棧
棧的刪除操作,叫做出棧,也稱為彈棧
(二)棧的抽象數據類型
ADT 棧(stack) Data 同線性表。元素具有相同的類型,相鄰元素具有前驅和后繼的關系。 Operation InitStack( *s): 初始化操作,建立一個空棧 ClearStack( *s): 將棧清空 StackEmpty( s): 若棧存在,返回true,否則返回false StackLength( s): 返回棧S的元素個數 GetTop( s, *e): 若是棧存在且非空,用e返回S的棧頂元素 Push( *s, e):若是棧存在,則插入新的元素e到棧S中並成為棧頂元素 Pop( *s, *e):若是棧存在且非空,刪除棧頂元素,並用e返回其值 DestroyStack( *s): 若是棧存在,則銷毀他 endADT
注意:
由於棧本身就是一個線性表,那么我們講的線性表的順序存儲和鏈式存儲,對於棧來說,都是適用的。
(三)棧的順序存儲結構
typedef struct { ElemType *base; //棧底指針 ElemType *top; //棧頂指針 int stackSize; //最大容量,這是可修改的 }sqStack;
(四)實現棧之前的預備知識
(1)malloc函數獲取的內存,內存空間上是連續的
malloc出來的空間,只是在虛擬內存中是連續的。而從實際的物理空間到虛擬內存空間還有一個映射的關系。
這個映射是由操作系統來控制的,一般情況下,從虛擬地址無法反查到物理地址。對於連續的虛擬地址空間,也就無法得知是否物理連續。
但由於映射的不確定性,當申請一段內存空間,尤其是比較大的內存長度情況下,物理地址不連續的可能性還是相當大的。
事實上,大多數的編程不需要關注物理空間是否連續。
(2)不同類型指針的步長增長問題
對於不同類型的指針,雖然指針在內存中的大小都是4字節, 但是他們的增長的地址步長與指針本身大小無關,而是與內部儲存數據類型大小有關。 例如: int *a=(int*)malloc(100*size(int)) int b,c; b=a; c=a++; b=4236536 //這是10進制下的地址 c=4236540 //增長了一個int字節大小的步長
char *base = (char *)malloc(sizeof(char) * 100); addr1 = base; addr2 = base+1; ------------------ addr1=5350647 addr2=5350648 //這里增長了一個char類型字節大小的步長
int *base, *top; base = (int *)malloc(sizeof(int) * 100); top = base + 100; printf("size:%d\n",top - base) //重點:兩個指針之間相減,所得到的不是地址大小之差,而是其中含有的元素個數,是100,若是我們想要知道地址之差,可以使用int強制轉換十六進制地址為一個十進制數進行減法運算,結果是400
(3)指針和所指向的數據中間的關系
我們存儲的數據在指針所指向的位置開始,占用了相關大小的字節去存放數據。
所以當我們創建棧的時候,我們的棧頂指針是不能直接讀取,我們往往需要先將他降一,然后才能讀取那塊內存空間獲取數據
例如:我們要獲取棧頂數據11,我們就需要先將棧頂退一,然后才能讀取到數據
(4)realloc函數,再分配空間。用法和誤區
realloc 原型:extern void *realloc(void *mem_address, unsigned int newsize); 用法:#include <stdlib.h> 有些編譯器需要#include <alloc.h> 功能:改變mem_address所指內存區域的大小為newsize長度。 說明:如果重新分配成功則返回指向被分配內存的指針,否則返回空指針NULL。 當內存不再使用時,應使用free()函數將內存塊釋放。 注意:這里原始內存中的數據還是保持不變的。
1、如果有足夠空間用於擴大mem_address指向的內存塊,則分配額外內存,並返回mem_address 這里說的是“擴大”,我們知道,realloc是從堆上分配內存的,當擴大一塊內存空間時, realloc()試圖直接從堆上現存的數據后面的那些字節中獲得附加的字節,如果能夠滿足,自然天下太平。也就是說,如果原先的內存大小后面還有足夠的空閑空間用來分配,加上原來的空間大小= newsize。那么就ok。得到的是一塊連續的內存。 2、如果原先的內存大小后面沒有足夠的空閑空間用來分配,那么從堆中另外找一塊newsize大小的內存。 並把原來大小內存空間中的內容復制到newsize中。返回新的mem_address指針。(數據被移動了)。 老塊被放回堆上。
注意:
1、返回值可能與ptr的值不同,如果是不同的話,那么realloc函數完成后,ptr指向的舊內存已被free掉了,會自動為我們釋放,所以我們不需要關心。只需要釋放新的內存地址即可 2、如果返回NULL值,則分配不成功,而原來的ptr指向的內存還沒有被free掉,要求程序顯式free.
(五)棧的順序存儲結構實現
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define STACK_INIT_SIZE 100 //定義棧的初始大小 #define STACK_INCR_SIZE 10 //定義棧的增長大小 typedef int ElemType; typedef int Status; typedef struct { ElemType *base; //棧底指針 ElemType *top; //棧頂指針 int stackSize; //最大容量,這是可修改的 }sqStack; //四個基礎操作 Status InitStack(sqStack *s); //初始化操作,建立一個空棧 Status ClearStack(sqStack *s); //將棧清空 Status StackEmpty(sqStack s); //若棧存在,返回true,否則返回false int StackLength(sqStack s); //返回棧S的元素個數 Status GetTop(sqStack s, ElemType *e); //若是棧存在且非空,用e返回S的棧頂元素 Status Push(sqStack *s, ElemType e); // 若是棧存在,則插入新的元素e到棧S中並成為棧頂元素 Status Pop(sqStack *s, ElemType *e); //若是棧存在且非空,刪除棧頂元素,並用e返回其值 Status DestroyStack(sqStack *s); //若是棧存在,則銷毀他 int main() { sqStack sk; int i; ElemType e; sk.base = sk.top = NULL; //用於判斷是否存在 //初始化空棧 printf("1.InitStack\n"); InitStack(&sk); printf("2.Push 1-5\n"); for (i = 1; i <= 5; i++) Push(&sk, i); printf("3.Pop number for three times\n"); for (i = 1; i <= 3;i++) { Pop(&sk, &e); printf("Pop %d: %d\n",i, e); } GetTop(sk, &e); printf("4.Get Top:%d\n",e); printf("5.Push 6-10\n"); for (i = 6; i <= 10; i++) Push(&sk, i); printf("6.Get stack length:%d\n", StackLength(sk)); printf("7.Pop number for six times\n"); for (i = 1; i <= 6; i++) { Pop(&sk, &e); printf("Pop %d: %d\n",i, e); } if (!StackEmpty(sk)) { printf("8.Stack is not Empty\n"); ClearStack(&sk); printf("9.Stack is Clear\n"); } printf("10.Stack Empty:%d\n",StackEmpty(sk)); printf("11.destroy Stack"); DestroyStack(&sk); system("pause"); return 0; } //初始化操作,建立一個空棧 Status InitStack(sqStack *s) { s->base = (ElemType *)malloc(STACK_INIT_SIZE*sizeof(ElemType)); if (!s->base) return ERROR; s->top = s->base; //最開始,棧頂就是棧底 s->stackSize = STACK_INIT_SIZE; return OK; } //將棧清空,將棧頂指針移動到棧底即可,容量大小不要修改,數據不需要清空,數據入棧會覆蓋 Status ClearStack(sqStack *s) { if (s == NULL) return ERROR; s->top = s->base; return OK; } //若棧存在,返回true,否則返回false Status StackEmpty(sqStack s) { if (s.base == s.top) return TRUE; return FALSE; } //返回棧S的元素個數 int StackLength(sqStack s) { int length = s.top - s.base; //指針之間運算,是按照其中數據大小字節來算的 return length; } //若是棧存在且非空,用e返回S的棧頂元素,注意:只是獲取棧頂數據,不出棧 Status GetTop(sqStack s, ElemType *e) { if (!e || StackEmpty(s) || !s.base) return ERROR; *e = *(s.top - 1); return OK; } //入棧操作:若是棧存在,則插入新的元素e到棧S中並成為棧頂元素 Status Push(sqStack *s, ElemType e) { ElemType* newStack; if (!s->base) return ERROR; if (s->top-s->base>=s->stackSize) //棧滿,需要再分配 { newStack = (ElemType *)realloc(s->base, (s->stackSize + STACK_INCR_SIZE)*sizeof(ElemType)); //重新分配大小 if (!newStack) //若是分配失敗,會返回NULL { free(s->base); exit(0); //分配失敗,直接退出 } s->base = newStack; //分配后需要將棧頂指針進行移動到新的位置 s->top = s->base + s->stackSize; } *(s->top) = e; s->top++; return OK; } //若是棧存在且非空,刪除棧頂元素(只需要將棧頂指針下移即可),並用e返回其值 Status Pop(sqStack *s, ElemType *e) { if (!s->base || !e || StackEmpty(*s)) return ERROR; *e = *(--s->top); return OK; } //若是棧存在,則銷毀他(直接將棧底指針釋放即可,置為空) Status DestroyStack(sqStack *s) { if (!s->base) //若是棧存在 { s->stackSize = 0; free(s->base); s->base = s->top = NULL; } return OK; }
(六)應用:進制轉換
Status Bin2Dec(sqStack* s,int *val); //二進制轉十進制 Status Bin2Oct(sqStack* s, sqStack* sv); //二進制轉八進制 Status Bin2Hex(sqStack* s, sqStack* sv); //二進制轉十六進制 int main() { sqStack sk; sqStack valsk; //接收轉換為8進制或者16進制的數,需要再使用一個棧 int dec; //接收轉換為10進制數值 char ch; ElemType e; sk.base = sk.top = NULL; //用於存放二進制字符串 valsk.base = valsk.top = NULL; //用於存放八進制和十六進制值 //初始化空棧 InitStack(&sk); InitStack(&valsk); scanf("%c", &ch); while (ch!='#') { Push(&sk, ch); scanf("%c", &ch); } getchar(); //消除鍵盤緩沖區中的回車符
//這里修改后可以測試其他進制轉換,注意:轉換10進制不需要用到第二個棧 if (Bin2Hex(&sk, &valsk)) while (!StackEmpty(valsk)) { Pop(&valsk, &ch); printf("%c", ch); }
DestroyStack(&sk);
DestroyStack(&valsk);
system("pause"); return 0; } //二進制轉十進制 Status Bin2Dec(sqStack* s, int *val) { int i,key,length,v=0; char ch; if (!val || StackEmpty(*s)) return ERROR; length = StackLength(*s); for (i = 0; i < length;i++) { Pop(s, &ch); key = ch - 48; v += key*pow(2, i); } *val = v; return OK; } //二進制轉八進制 Status Bin2Oct(sqStack* s, sqStack* sv) { char ord[3] = { 0 }; char ch; int i,length,val; if (!s || !sv) return ERROR; if (!StackEmpty(*sv)) ClearStack(sv); while (!StackEmpty(*s)) { memset(ord, '0', 3); val = 0; length = StackLength(*s); if (length >= 3) for (i = 2; i >= 0; i--) Pop(s, &ord[i]); else for (i = 2; i >= 3 - length; i--) Pop(s, &ord[i]); for (i = 0; i < 3;i++) val += (ord[2 - i] - 48)*pow(2, i); Push(sv, (char)(val+48)); } return OK; } //二進制轉十六進制 Status Bin2Hex(sqStack* s, sqStack* sv) { char hex[4] = { 0 }; char ch; int i, length, val; if (!s || !sv) return ERROR; if (!StackEmpty(*sv)) ClearStack(sv); while (!StackEmpty(*s)) { memset(hex, '0', 4); val = 0; length = StackLength(*s); if (length >= 4) for (i = 3; i >= 0; i--) Pop(s, &hex[i]); else for (i = 3; i >= 4 - length; i--) Pop(s, &hex[i]); for (i = 0; i < 4; i++) val += (hex[3 - i] - 48)*pow(2, i); if (val < 10) Push(sv, (char)(val + 48)); else Push(sv, 'A' + val - 10); } return OK; }
(七)棧的順序存儲實現另法:使用數組
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXSIZE 100 typedef int ElemType; typedef int Status; typedef struct { ElemType data[MAXSIZE]; int top; }sqStack; //四個基礎操作 Status InitStack(sqStack *s); //初始化操作,建立一個空棧 Status ClearStack(sqStack *s); //將棧清空 Status StackEmpty(sqStack s); //若棧存在,返回true,否則返回false int StackLength(sqStack s); //返回棧S的元素個數 Status GetTop(sqStack s, ElemType *e); //若是棧存在且非空,用e返回S的棧頂元素 Status Push(sqStack *s, ElemType e); // 若是棧存在,則插入新的元素e到棧S中並成為棧頂元素 Status Pop(sqStack *s, ElemType *e); //若是棧存在且非空,刪除棧頂元素,並用e返回其值 Status DestroyStack(sqStack *s); //若是棧存在,則銷毀他 int main() { sqStack sk; int i; ElemType e; //初始化空棧 printf("1.InitStack\n"); InitStack(&sk); printf("2.Push 1-5\n"); for (i = 1; i <= 5; i++) Push(&sk, i); printf("3.Pop number for three times\n"); for (i = 1; i <= 3;i++) { Pop(&sk, &e); printf("Pop %d: %d\n",i, e); } GetTop(sk, &e); printf("4.Get Top:%d\n",e); printf("5.Push 6-10\n"); for (i = 6; i <= 10; i++) Push(&sk, i); printf("6.Get stack length:%d\n", StackLength(sk)); printf("7.Pop number for six times\n"); for (i = 1; i <= 6; i++) { Pop(&sk, &e); printf("Pop %d: %d\n",i, e); } if (!StackEmpty(sk)) { printf("8.Stack is not Empty\n"); ClearStack(&sk); printf("9.Stack is Clear\n"); } printf("10.Stack Empty:%d\n",StackEmpty(sk)); printf("11.destroy Stack"); DestroyStack(&sk); system("pause"); return 0; } //初始化操作,建立一個空棧 Status InitStack(sqStack *s) { if (!s) return ERROR; memset(s->data, 0, MAXSIZE*sizeof(ElemType)); s->top = -1; return OK; } //將棧清空,將棧頂指針移動到棧底即可,數據不需要清空,數據入棧會覆蓋 Status ClearStack(sqStack *s) { if (!s) return ERROR; s->top = -1; return OK; } //若棧存在,返回true,否則返回false Status StackEmpty(sqStack s) { if (s.top == -1) return OK; return FALSE; } //返回棧S的元素個數 int StackLength(sqStack s) { return s.top+1; } //若是棧存在且非空,用e返回S的棧頂元素,注意:只是獲取棧頂數據,不出棧 Status GetTop(sqStack s, ElemType *e) { if (s.top = -1||!e) return ERROR; *e = s.data[s.top]; return OK; } //入棧操作:若是棧存在,則插入新的元素e到棧S中並成為棧頂元素 Status Push(sqStack *s, ElemType e) { if (s->top + 1 == MAXSIZE||!s) return ERROR; s->top++; s->data[s->top] = e; return OK; } //若是棧存在且非空,刪除棧頂元素(只需要將棧頂指針下移即可),並用e返回其值 Status Pop(sqStack *s, ElemType *e) { if (s->top == -1||!s||!e) return ERROR; *e = s->data[s->top]; s->top--; return OK; } //若是棧存在,則銷毀他,數據清空 Status DestroyStack(sqStack *s) { if (!s) return ERROR; memset(s->data, 0, MAXSIZE*sizeof(ElemType)); s->top = -1; return OK; }
引申:兩棧共享空間
對於我們使用數組來構造順序棧,有一個很大的缺陷,就是需要事先確定數組的存儲空間大小,不夠的話擴容不方便,太多了又過於浪費空間。
於是設計出來合適的大小數組,將他的空間從兩頭分別當做一個棧。就可以最大限度的利用數組空間。
例如:一個棧需要的空間大概為50-150,多數情況是在100以下,有少數情況會超過100。 那么當我們使用兩個棧時,若是聲明兩個150空間的數組。則空間浪費較多。 不如一次聲明200空間大小供兩個棧共同使用。雖然有滿棧的情況,不過出現情況不大。而且會最大程度利用了棧的空間。
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXSIZE 10 typedef int ElemType; typedef int Status; //兩棧共享空間結構 typedef struct { ElemType data[MAXSIZE]; int top1; //棧1 棧頂指針 指向數組開始 從做向右 int top2; //棧2 棧頂指針 指向數組結尾 從右向左 }sqDoubleStack; //四個基礎操作 Status InitStack(sqDoubleStack *s); //初始化操作,建立一個空棧 Status ClearStack(sqDoubleStack *s, int stackNumber); //將棧清空 Status StackEmpty(sqDoubleStack s, int stackNumber); //若棧存在,返回true,否則返回false int StackLength(sqDoubleStack s, int stackNumber); //返回棧S的元素個數 Status GetTop(sqDoubleStack s, ElemType *e, int stackNumber); //若是棧存在且非空,用e返回S的棧頂元素 Status Push(sqDoubleStack *s, ElemType e, int stackNumber); // 若是棧存在,則插入新的元素e到棧S中並成為棧頂元素 Status Pop(sqDoubleStack *s, ElemType *e, int stackNumber); //若是棧存在且非空,刪除棧頂元素,並用e返回其值 Status DestroyStack(sqDoubleStack *s); //若是棧存在,則銷毀他 int main() { sqDoubleStack sk; int i; Status st; ElemType e; //初始化空棧 printf("1.InitStack\n"); InitStack(&sk); printf("2.Push 1-10\n"); for (i = 1; i <= 11; i++) { st=Push(&sk, i, i % 2 + 1); if (st == ERROR) printf("2.Push %d to stack-%d failure!\n", i, i % 2 + 1); } printf("3.Pop number for three times\n"); for (i = 1; i <= 5; i++) { Pop(&sk, &e,i%2+1); printf("Pop stack-%d: %d\n", i%2+1, e); } GetTop(sk, &e,1); printf("4.Get stack-1 Top:%d\n", e); GetTop(sk, &e, 2); printf("4.Get stack-2 Top:%d\n", e); printf("5.Get stack-1 length:%d\n", StackLength(sk,1)); if (!StackEmpty(sk,1)) { printf("8.Stack-1 is not Empty\n"); ClearStack(&sk,1); printf("9.Stack-1 is Clear\n"); } printf("5.Get stack-2 length:%d\n", StackLength(sk, 2)); if (!StackEmpty(sk, 2)) { printf("8.Stack-2 is not Empty\n"); ClearStack(&sk, 2); printf("9.Stack-2 is Clear\n"); } printf("10.Stack-1 Empty:%d\n", StackEmpty(sk,1)); printf("10.Stack-2 Empty:%d\n", StackEmpty(sk, 2)); printf("11.destroy Stack"); DestroyStack(&sk); system("pause"); return 0; } //初始化操作,建立一個空棧 Status InitStack(sqDoubleStack *s) { if (!s) return ERROR; memset(s->data, 0, MAXSIZE*sizeof(ElemType)); s->top1 = -1; s->top2 = MAXSIZE; return OK; } //將棧清空,stackNumber為0,兩個都清空,為1清空棧1,2清空棧2 Status ClearStack(sqDoubleStack *s, int stackNumber) { if (!s) return ERROR; if (stackNumber == 0) { s->top1 = -1; s->top2 = MAXSIZE; } else if (stackNumber==1) { s->top1 = -1; } else if (stackNumber==2) { s->top2 = MAXSIZE; } return OK; } //若兩個棧都不存在,返回true,否則返回false Status StackEmpty(sqDoubleStack s, int stackNumber) { if (stackNumber == 0) { if (s.top1 == -1 && s.top2 == MAXSIZE) return OK; } else if (stackNumber == 1) { if (s.top1 == -1) return OK; } else if (stackNumber == 2) { if (s.top2 == MAXSIZE) return OK; } return FALSE; } //返回棧S的元素個數,是指兩個棧的總長 int StackLength(sqDoubleStack s,int stackNumber) { int length = 0; if (stackNumber == 0) length=(s.top1 + 1) + (MAXSIZE - s.top2); else if (stackNumber == 1) length = s.top1 + 1; else if (stackNumber == 2) length = MAXSIZE - s.top2; return length; } //若是棧存在且非空,用e返回S的棧頂元素,注意:只是獲取棧頂數據,不出棧 Status GetTop(sqDoubleStack s, ElemType *e,int stackNumber) { if (!e) return ERROR; if (stackNumber == 1) if (s.top1 == -1) return ERROR; else *e = s.data[s.top1]; else if (stackNumber == 2) if (s.top2 == MAXSIZE) return ERROR; else *e = s.data[s.top2]; return OK; } //入棧操作:若是棧存在,則插入新的元素e到棧S中並成為棧頂元素 Status Push(sqDoubleStack *s, ElemType e, int stackNumber) { if (!s) return ERROR; if (s->top1 + 1 == s->top2) //棧滿,不允許插入 return ERROR; if (stackNumber == 1) s->data[++s->top1] = e; else if (stackNumber==2) s->data[--s->top2] = e; return OK; } //若是棧存在且非空,刪除棧頂元素(只需要將棧頂指針下移即可),並用e返回其值 Status Pop(sqDoubleStack *s, ElemType *e, int stackNumber) { if (StackEmpty(*s, stackNumber)||!s || !e) return ERROR; if (stackNumber == 1) *e = s->data[s->top1--]; else if (stackNumber==2) *e = s->data[s->top2++]; return OK; } //若是棧存在,則銷毀他,兩個棧都銷毀 Status DestroyStack(sqDoubleStack *s) { if (!s) return ERROR; memset(s->data, 0, MAXSIZE*sizeof(ElemType)); s->top1 = -1; s->top2 = MAXSIZE; return OK; }
(八)兩種順序結構棧的比較
方法一的空間是malloc出來的一塊連續空間,操作稍微復雜些。但是當棧滿時,可以進行擴展。
方法二除了使用簡單,對於棧的擴展方面欠缺。
推薦第一種,了解第二種即可
(九)棧的應用:括號匹配
對於()[]{}都是正確的[),{],(}都是匹配失敗的,注意:對於引號里面的括號不進行匹配,()[]'(]'()是正確的
int main() { sqStack sk; ElemType e; char ch; sk.base = sk.top = NULL; //用於判斷是否存在 //初始化空棧,用於存放()[]{}''""這幾個數據,的左半邊進行匹配 InitStack(&sk); scanf("%c", &ch); while (ch!='#') { if (GetTop(sk, &e)) { if ((e == '"'&&ch != '"') || (e == '\''&&ch != '\'')) //若是在單雙引號之間的括號全部舍去 goto GS; if ((e == '"'&&ch == '"') || (e == '\''&&ch == '\'')) //匹配單雙引號,優先級是最高的 { Pop(&sk, &e); //彈出單雙引號 goto GS; } if ((e == '('&&ch == ')') || (e == '['&&ch == ']') || (e == '{'&&ch == '}')) { Pop(&sk, &e); //彈出匹配好的括號 goto GS; } if ((ch == ')'&&e != '(') || (ch == ']'&&e != '[') || (ch == '}'&&e != '{')) //這是當我們獲取的右半邊括號與棧頂不匹配時,直接退出,退出時棧不為空 break; } else //沒有棧頂元素 { if (ch == ')' || ch == ']' || ch == '}') //沒有棧頂元素時,若是我們獲取的括號是右半邊直接匹配失敗 { Push(&sk, ch); //這里必須放一個東西入棧,以保證不為空,為一會判斷做准備,至於入棧的數據隨意 break; } } if (ch == '('||ch=='['||ch=='{'||ch=='\'' || ch == '"') Push(&sk, ch); GS: scanf("%c", &ch); } getchar(); if (StackEmpty(sk)) printf("match success!"); else printf("match failure!"); system("pause"); return 0; }