棧和隊列其實是個抽象的資料類型。
對於他們的實現,既可以用數組,也可以用鏈表實現。
對於棧和隊列,看書的時候可能僅僅只是瞭解的一個概念。
因為要在程式中真正運用上去,個人覺得又是一個高度的思想境界。
我只是看了書,瞭解基本概念。
一。棧
定義
棧是僅限在表尾進行插入和刪除的操作線性表。
我們把允許插入和刪除資料的一段稱之為棧頂(top)。另一端稱之為棧底(bottom)
不含任何資料元素的棧稱為空白棧。LIFO後進先出表。
1.棧的數組結構實現,也就是棧順序儲存結構定義。
typedef struct {ElemType data[MAX_SIZE];int top;}SqStack;
棧的常見操作就是進棧push好出棧pop操作
Status push(SqStack * s, ElemType e){if(s->top == MAX_SIZE-1);//棧已經滿了return error;//入棧s->top++;s->data[s->top] = e;return ok;}Status pop(SqStack * s, ElemType *e){if(s->top ==-1)return error;*e = s->data[s->top];s->top--;return ok;}
對於棧的順序結構實現,是有很多的局限性的。
之後有又提出一個概念是 共用棧。
兩個棧共用空間。
棧的順序儲存結構就和線性表的順序儲存一樣,同樣存在對開闢的儲存空間無法很好的確定。
如果此時有兩個相同類類型的棧。他們各自用一個數組空間。此時極有可能其中一個棧想插入元素卻要溢出了。
而另外一個棧還留有很多儲存空間。此時可以稍微 改進下,讓兩個棧共用一個數組空間。
我們將數組的始端作為第一個棧的棧底,數組的末端最為另一個棧的棧底。兩個棧都是像中間延伸。
Status Push(SqStack * s, ElemType e, int stackNumber){ 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;}
2.棧的鏈式實現。
typedef struct StackNode{ElemType data;struct StackNode *next;}StackNode *LinkStackheadtypedef struct LinkStack{LinkStackhead top;//棧頂指標int count;//元素個數}
進棧和出棧操作
Status push(LinkStack * ls,ElemType e){LinkStackhead l = (LinkStack)malloc(sizeof(StackNode));//開闢一Block Storage空間l->data = e;//賦值l->next = ls->top;ls->top = l;ls->count++;return ok;}Status pop(LonkStack * ls ,ElemType *e){LinkStakchead h;if(ls->count == -1)return error;//*e = ls->top;h = ls->topls->top = p->next;free(h)ls->count--;return ok;}
棧的應用。
在現實中,先進後出的式的棧應用:比如檔案瀏覽工具就是一個典型。
其次比如我們程式的執行順序也是可以理解為一個棧的形式 。 A 方法中調用了B,B中調用了C。
那麼要執行完這端代碼,我們先按調用的順序將A,B,C依次壓入棧中,然後C B A依次pop出執行操作。
因此我們可以聯想到一個最常見的應用情境,就是遞迴。
遞迴中有個必定聽到過的的內容,就是Fibonacci數列。
斐波那契的遞迴函式。
int Fbi(int i){if(i<2)return i == 0?0:1return Fbi(i-1)+Fbi(i-2);}
即,我要取得該數列中的第i個數,必須Crowdsourced Security Testing道位於第 i-2和i-1這兩個數,
於是就產生一個遞迴的演算法。
在遞迴中要注意的演算法必須有跳出的條件。
從代碼可以看出,遞迴用到的是選擇結構。
顯然遞迴代碼很優雅,但是優雅是要有代價的,他會不斷調用和建立函數副本,耗費大量的時間和記憶體。
對於斐波那契數列求第i相的演算法有一種改進的做法,
因為這邊我們假設計算 i項的數,
那麼要先求 i-1和i-2項的數,
求i-1項的數則又需要求i-2和i-3項數;
到這邊,我們就看出來了,在求i項時,我們去計算了 i-2項,在求i-1項時,我們有去求了i-2項。
也就是說,其實在計算第i項數,不停的遞迴時,很多計算都是重複的。
那麼覺得可以考慮是否把每項的數在一次計算後就存起來,後面被調用到時在先去尋找該項是否已經計算,有的話就直接取出來,不存在那就計算咯
private Map numsMap = new HashMap<Integer,Integer>();public int Fbi(int i){int x,y;if(i<2)return i == 0?0:1;if(numsMap.get(Integer.valueOf(i-1)) !=null){x = (Integer) numsMap.get(Integer.valueOf(i-1));}else{x=Fbi(i-1);}if(numsMap.get(Integer.valueOf(i-2)) !=null){y = (Integer) numsMap.get(Integer.valueOf(i-2));}else{y=Fbi(i-2);}return x+y;}
抱歉我的java和C的混寫。因為C的話文法不是太熟,為了測試,熟練度java寫起來比較快。
這也算是空間換時間的一種做法。
棧另外一個重要的的應用:四則運算運算式求值。
1.尾碼標記法,(逆波蘭):簡單說就是遇到數字就進棧,遇到符號就棧頂兩個數字出棧。
2.中綴運算式也就是我們平常書寫的四則運算運算式。
尾碼運算式雖簡單,但是怎麼把一個中綴運算式轉成一個尾碼呢?
同樣的,從左往右遍曆,若是數字就輸出,若是符號,判斷其於棧頂符號的優先順序,若是右括弧或是優先順序低於棧內元素的,
則棧頂元素依次出棧並輸出,將當前符號進棧。
二:隊列
定義
隊列是只允許在一端進行插入操作,在另一端進行刪除操作。FIFO線性表。
1.隊列順序儲存結構
相對於棧在插入和刪除上操作時間為O(1),在隊列插入元素只是在數組末端追加一個元素,時間也為O(1),
但是如果是出列一個元素,此時後面的元素都需要往前面移動一個位置,時間就是O(N)。
因此提出了隊頭隊尾指標。front指向隊頭元素,rear指向隊尾元素的下一個位置。
但此時還是會出現一個問題,就是不停的出隊入隊,front和rear一直在往後移動,前面的數組單元空著,但是指標已移動到數組末尾,
此時造成一個假溢出的現象。
於是又提出一個改進思路,迴圈隊列。
隊列頭尾相接的順序儲存結構稱之為迴圈隊列。
此時當在數組最後位置插入元素後,rear指標移動到數組的0的位置,既形成一個迴圈。
但是又會出現一個問題,在我繼續插入元素,rear指標往後移動,就會有rear等於front的時候。上面我們說這個情況的隊列是空隊列出現矛盾,既我們無法判斷當rear等於front時,隊列到底是滿呢,還是空的?
方法一:設定一個標誌位Tag,rear還沒有迴圈到數組0時,tag為0,迴圈到tag設定為1.
方法二:當隊列空時rear = front,,當隊列滿時,我們保留一個元素空間
計算隊列的長度通用公式:(rear -front +QueueSize)%QueueSize;
隊列滿的判斷 (rear -front +QueueSize)%QueueSize ==front;
迴圈隊列的順序結構定義
typedef struct{ElemType data[MAX_SIZE];int front;int rear;}SqQueue;
迴圈隊列的入隊出隊操作
Status EnQueue(SqQueue *Q,ElemType e){if((Q->rear -Q->front +MAXSZIE)%MAXSIZE ==Q->front)return error;Q->data[Q->rear] = e;Q->rear = (Q->rear+1)%MAXSZIE;return ok;}Status DeQueue(Sqqueue *Q,ElemType *e){if(Q->rear == Q->front)rerurn error;*e = Q- >data[Q->front];Q->front = (Q->front+1)%MAXSIZE;return ok;}
2.隊列的鏈式儲存實現
鏈隊列的結構
typedef int Elemtype ;typedef struct QNode{ ElemType data; struct QNode *next;}QNode,*QueuePtr;typedef struct{//隊列的鏈表結構 Queueptr front,rear;}LinkQueue;
鏈式隊列的插入和刪除
Status EnQueue(LinkQueue * Q , ElementType e){Queueptr p = (Queueptr) malloc(sizeof(QNode));if(!s)//分配失敗;exit(OVERFLOW);p->data = e ;P->next = NULL;Q->rear->next = p ;//在隊尾插入Q->rear = s ;//將p作為隊尾節點return ok ;}Status DeQueue(LinkQueue * Q , ElementType *e){Queueptr p ;if( Q-> front == Q-> rear) //空隊列return error;p = Q->front->next;//儲存要刪除的元素*e = p->data;Q->front->next = p->next;if(Q->rear == p)//若隊頭是隊尾,刪除後將隊尾指向隊頭Q->rear = Q->front;free(p);eturn ok;}