一、 棧
- 1. 為什么要學習棧?
棧是什么?為什么要學習它?現在先來說說棧的輝煌作用吧!在計算機領域中,棧是一種不可忽略的概念,無論從它的結構上,還是存儲數據方面,它對於學習數據結構的人們來說,都是非常重要的。那么就會有人問,棧究竟有什么作用,讓我們這么重視它?首先,棧具有非常強大的“記憶”功能,它可以保存對你有作用的數據,也可以被叫做保存現場;其次,當咱們調用一個帶參函數時候, 被調用的函數的形參,在編譯器編譯的時候,這些形參都需要一定的空間存放他們,這時計算機就會默認幫你保存到棧中了!
- 2. 棧的定義
棧的作用,這是一個咱們生活中處處用到,但是卻又沒發現的一種現象,例如當你拿個籃子去買蘋果,那么你最先挑選的蘋果就是在籃子的最底下,最后挑選的蘋果就在籃子的最上邊,那么這就造成了這么一種現象:先拿進籃子的蘋果,要最后才能取出來;相反,最后拿進籃子的蘋果,就能最先取出來!
棧是限定只能在表尾進行插入和刪除的線性表。
我們把允許插入和刪除的一端稱作棧頂(Top),另一端稱作棧底(bottom)。不含任何數據元素的棧被稱作空棧,棧也被稱為先進后出的線性表(具有線性關系)。
而棧的特殊性,就是在表中想進行插入和刪除的操作,只能在棧頂進行。這也就使得了:棧底是非常穩定的,因為先進來的元素都被放在了棧底。
棧的插入操作:叫做進棧,也叫作壓棧,入棧。
棧的刪除操作:叫做出棧,也叫彈棧。
- 3. 進棧出棧變化形式
現在請大家思考這樣的一個問題:最先進棧的元素,是不是只能最后才能出來呢?
答案是不一定的,這個問題就要細分情況了。棧對線性表的插入和刪除的位置進行了限制,並沒有對元素的進出時間進行限制,這也就是說,在不是所有元素都進棧的情況下,事先進去的元素也可以先出站,只要確保一點:棧元素是從棧頂出棧就可以了!
舉例來說,現在有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。
現在思考一下,有沒有312這樣的出棧次序?
答案是肯定不會的。因為3先出棧,就意味着3曾經進棧,既然3都進棧了,那就意味着1、2已經進棧了,此時,2一定是在1的上面,就是更接近棧頂,那么出棧只能是321,不然不滿足123依次進棧的要求,所以此時不會發生1比2先出棧的情況。
上述的這個簡單例子,就可以看出來,只是3個棧元素,就有5種可能的出棧次序,如果元素量多,那么出棧的變化將會更多。
二、 棧的抽象數據類型
對於棧來講,理論上線性表的操作特性它都具備,但是由於它的特殊性,所以針對它的操作上,也會有些變化。特別是插入和刪除的操作,我們改名為push和pop,英文翻譯為壓和彈!
由於棧也是線性表,那么咱們之前講的線性表的順序存儲和鏈式存儲,對於棧來說,必然也是同樣適用的!
一、 ACM算法:順序棧的實現
既然棧是線性表的特例,那么棧的順序存儲其實也是線性表順序存儲的簡化,我們簡稱為順序棧。線性表是用數組來實現的,那么對於棧這種只能一頭進行插入和刪除的線性表來說,咱們一般也是用數組下標為0的一端,作為棧底。
那么此時,我們定義一個StackSize表示數組的元素個數,top變量來指示棧頂元素在數組中的位置,那么就意味着top可以變大變小。如果此棧存在元素,那么top必然要小於StackSize,棧為空則top=-1,棧存在一個元素,top=0。
棧的結構定義:
typedef int SElemType; /* SElemType 類型根據實際情況而定,這里假設為int */
typedef struct
{
SElemType data[MAXSIZE];
int top; /* 棧頂指針 */
}SqStack;
- 1. 棧的順序存儲結構進棧操作
若現在有一個棧,StackSize是5,那么棧的普通情況、空棧、滿棧的情況分別如下:
對於棧的插入,即進棧操作,有如下所示:
因此對於進棧的操作push,其代碼如下:
/* 插入元素e為新的棧頂元素 */ Status Push(SqStack *S,SElemType e) { if(S->top == MAXSIZE -1 ) /* 滿棧 */ { return ERROR; } S->top++; /* 棧頂指針增加一 */ S->data[s->top] = e; /* 將新元素e插入並賦值給棧頂空間 */ return OK; }
- 1. 棧的順序存儲結構出棧操作
對於棧的刪除,即出棧操作,有如下代碼:
/* 先判斷棧是否為空,不空則刪除S的棧頂元素,用e返回其值,並返回OK,否則返回ERROR */ Statck Pop(SqStack *S, SElemType *e) { if(S->top == -1) return ERROR; *e = S->data[S->top]; /* 將要刪除的棧頂元素賦值給e */ S->top-- ; return OK; }
一、 棧的鏈式存儲結構
- 1. 棧出鏈式存儲結構
棧的鏈式存儲結構,簡稱為鏈棧。
鏈棧簡單的講,就是將棧和鏈表合二為一的使用,那么此時為了簡單,通常就把棧頂放在了鏈表的頭指針位置,此時棧頂指針就替代了頭指針,那么此時數據依次從棧頂進入,而且此時對於鏈表來說也就不再需要頭結點了。
對於鏈棧來說,鏈棧的操作絕大部分都和單鏈表類似,而且基本上不存在棧滿的情況,除非內存已經沒有可以使用的空間。
對於空棧來說,鏈表原定義是頭指針指向空,那么鏈棧的空其實就是top=NULL的時候。
對比一下順序棧與鏈棧,它們在時間復雜度上是一樣的,均為O(1)。對於空間性能,順序棧需要事先確定一個固定的長度,可能會存在內存空間浪費的問題,但它的優勢是存取時定位很方便,而鏈棧則要求每個元素都有指針域,這同時也增加了一些內存開銷,但對於棧的長度無限制。所以它們的區別和線性表中討論的一樣。如果棧的使用過程中元素變化不可預料,有時很小,有時非常大,那么最好是用鏈棧,反之,如果它的變化在可控的范圍內,建議使用順序棧會更好一些。
棧的鏈表
/* Note:Your choice is C IDE */ #include "stdio.h" #include "stdlib.h" typedef struct person{ int arr; struct person *next;//入棧壓棧push出棧彈棧pop }PERSON; PERSON *head; void main()//棧頂是頭指針 { int e; int bh; PERSON *p,*pa,*pb; printf("1.入棧一個元素\n"); printf("2.出棧一個元素\n"); printf("3.打印\n"); for(;;){ printf("請輸入功能編號:"); scanf("%d",&bh); switch(bh){ case 1: printf("輸入元素:"); scanf("%d",&e); p=(PERSON*)malloc(sizeof(PERSON));//分配一個空間 p->arr=e;//將輸入的數據傳入鏈表中 if(head==NULL){ head=p; p->next=NULL; }else{ p->next=head; head=p; } break; case 2: if(head==NULL) { printf("鏈表為空!\n"); break; } pa=head; pb=head->next; head=head->next; printf("-%d-",pa->arr); free(pa); break; case 3: pa=head; printf("\n打印從棧頂到棧底的元素\n"); while(pa){ printf("--%d--",pa->arr); pa=pa->next; } printf("\n"); break; } } }
棧的順序表
#include "stdio.h" #define MAX 5 typedef struct person{ int arr[MAX]; int top; }PERSON; PERSON p; void main(void){ int i,e; int bh; p.top=-1; printf("1.入棧一個元素\n"); printf("2.出棧一個元素\n"); printf("3.打印\n"); for(;;){ scanf("%d",&bh); switch(bh){ case 1: if(p.top>=MAX-1){ printf("\n棧已滿\n"); }else{ printf("輸入元素:"); scanf("%d",&e); p.top++; p.arr[p.top]=e; } break; case 2: e=p.arr[p.top]; printf("刪除的元素是%d\n",e); p.top--; break; case 3: printf("\n打印從棧頂到棧底的元素\n"); for(i=p.top;i>=0;i--){ printf("--%d--",p.arr[i]); } printf("\n"); break; } } }