1.棧
1.1 棧的定義
棧是一種特殊的線性表。其特殊性在於限定插入和刪除數據元素的操作只能在線性表的一端進行。如下所示:
結論:後進先出(Last In First Out),簡稱爲LIFO線性表。
棧的基本運算有六種:
構造空棧:InitStack(S)、
判棧空: StackEmpty(S)、
判棧滿: StackFull(S)、
進棧: Push(S,x)、可形象地理解爲壓入,這時棧中會多一個元素
退棧: Pop(S) 、 可形象地理解爲彈出,彈出後棧中就無此元素了。
取棧頂元素:StackTop(S),不同與彈出,只是使用棧頂元素的值,該元素仍在棧頂不會改變。
由於棧也是線性表,因此線性表的存儲結構對棧也適用,通常棧有順序棧和鏈棧兩種存儲結構,這兩種存儲結構的不同,則使得實現棧的基本運算的算法也有所不同。
我們要了解的是,在順序棧中有"上溢"和"下溢"的概念。順序棧好比一個盒子,我們在裏頭放了一疊書,當我們要用書的話只能從第一本開始拿(你會把盒子翻過來嗎?真聰明^^),那麼當我們把書本放到這個棧中超過盒子的頂部時就放不下了(疊上去的不算,哼哼),這時就是"上溢","上溢"也就是棧頂指針指出棧的外面,顯然是出錯了。反之,當棧中已沒有書時,我們再去拿,看看沒書,把盒子拎起來看看盒底,還是沒有,這就是"下溢"。"下溢"本身可以表示棧爲空棧,因此可以用它來作爲控制轉移的條件。
鏈棧則沒有上溢的限制,它就象是一條一頭固定的鏈子,可以在活動的一頭自由地增加鏈環(結點)而不會溢出,鏈棧不需要在頭部附加頭結點,因爲棧都是在頭部進行操作的,如果加了頭結點,等於要在頭結點之後的結點進行操作,反而使算法更復雜,所以只要有鏈表的頭指針就可以了。
1.2 棧的順序存儲
使用c++的面向對象封裝:
- // Test.cpp : Defines the entry point for the console application.
- //
- #include "stdafx.h"
- #include <iostream>
- using namespace std;
- #define MAX 10 // MAXIMUM STACK CONTENT
- class stack
- {
- private:
- int arr[MAX];
- int top;
- public:
- stack()
- {
- inItStack();
- }
- /************************************************************************/
- /* 初始化棧 */
- /************************************************************************/
- void inItStack()
- {
- top=-1;
- }
- /************************************************************************/
- /* 入棧 */
- /************************************************************************/
- void push(int a)
- {
- top++;
- if(top < MAX) {
- arr[top]=a;
- } else {
- cout<<"STACK FULL!!"<<top;
- }
- }
- /************************************************************************/
- /* 出棧 */
- /************************************************************************/
- int pop()
- {
- if(isEmpty()) {
- cout<<"STACK IS EMPTY ";
- return NULL;
- } else {
- int data=arr[top];
- arr[top]=NULL;
- top--;
- return data;
- }
- }
- /************************************************************************/
- /* 是否爲空 */
- /************************************************************************/
- bool isEmpty()
- {
- if(top == -1) return true;
- else return false;
- }
- };
- int main()
- {
- stack a;
- a.push(3);
- a.push(10);
- a.push(1);
- cout<<"Pop:"<<a.pop();
- return 0;
- }
1.3 棧的鏈式存儲
若是棧中元素的數目變化範圍較大或不清楚棧元素的數目,就應該考慮使用鏈式存儲結構。人們將用鏈式存儲結構表示的棧稱作"鏈棧"。鏈棧通常用一個無頭結點的單鏈表表示。如圖所示:
棧的操作是線性表操作的特例。
簡單用c實現:
- // Test.cpp : Defines the entry point for the console application.
- //
- #include "stdafx.h"
- #include <stdio.h>
- #include "stdlib.h"
- #include <iostream>
- using namespace std;
- //宏定義
- #define TRUE 1
- #define FALSE 0
- #define OK 1
- #define ERROR 0
- #define INFEASIBLE -1
- #define OVERFLOW -2
- #define STACKEMPTY -3
- #define LT(a,b) ((a)<(b))
- #define N = 100
- typedef int Status;
- typedef int ElemType;
- typedef struct LNode{
- ElemType data;
- struct LNode *next;
- }LNode, *LinkList;
- typedef struct stack{
- LinkList top;
- } STACK;
- /************************************************************************/
- /* 接口:
- */
- /************************************************************************/
- void InitStack(STACK &S);
- void Push(STACK &S,ElemType e);
- void Pop(STACK &S, ElemType *e);
- ElemType GetTop(STACK S,ElemType *e);
- int StackEmpty(STACK S);
- /************************************************************************/
- /*
- */
- /************************************************************************/
- void InitStack(STACK &S)
- {
- S.top=NULL;
- }
- /************************************************************************/
- /* 入棧
- */
- /************************************************************************/
- void Push(STACK &S,ElemType e)
- {
- LinkList p;
- p = (LinkList )malloc(sizeof(LNode));
- if (!p) exit(OVERFLOW);
- p->data = e;
- p->next = S.top;
- S.top = p;
- }
- /************************************************************************/
- /* 出棧
- */
- /************************************************************************/
- void Pop(STACK &S, ElemType *e)
- {
- LinkList p;
- if(StackEmpty(S)) exit(STACKEMPTY);
- *e = S.top->data;
- p = S.top;
- S.top = p->next;
- free(p);
- }
- /************************************************************************/
- /* 獲取棧頂元素內容
- */
- /************************************************************************/
- ElemType GetTop(STACK S, ElemType *e)
- {
- if(StackEmpty(S)) exit(STACKEMPTY);
- *e = S.top->data;
- }
- /************************************************************************/
- /* 判斷棧S是否空
- */
- /************************************************************************/
- int StackEmpty(STACK S)
- {
- if(S.top==NULL) return TRUE;
- return FALSE;
- }
- void main()
- {
- STACK S;
- InitStack( S);
- Push(S, 3);
- Push(S, 4);
- ElemType e;
- Pop(S,&e);
- cout<<"Pop elem:"<<e;
- }
1.4 棧的應用
1) 數制轉換
2)語法詞法分析
3)表達式求值等
1.5 棧的遞歸和實現
漢諾塔的問題:
解決:
1)如果有一個盤子,直接從X移到Z即可。
2)如果有n個盤子要從X移到Z,Y作爲輔助。問題可以轉化爲,先將上面n-1個從X移動到Y,Z作爲輔助,然後將第n個從X移動到Z,最後將剩餘的n-1個從Y移動到Z,X作爲輔助。
完整實現代碼,包括棧的實現:
- // Test.cpp : Defines the entry point for the console application.
- //
- #include "stdafx.h"
- #include <stdio.h>
- #include "stdlib.h"
- #include <iostream>
- using namespace std;
- //宏定義
- #define TRUE 1
- #define FALSE 0
- #define OK 1
- #define ERROR 0
- #define INFEASIBLE -1
- #define OVERFLOW -2
- #define STACKEMPTY -3
- #define LT(a,b) ((a)<(b))
- #define N = 100
- typedef int Status;
- typedef int ElemType;
- typedef struct LNode{
- ElemType data;
- struct LNode *next;
- }LNode, *LinkList;
- typedef struct stack{
- LinkList top;
- } STACK;
- /************************************************************************/
- /* 接口:
- */
- /************************************************************************/
- void InitStack(STACK &S);
- void Push(STACK &S,ElemType e);
- void Pop(STACK &S, ElemType *e);
- ElemType GetTop(STACK S,ElemType *e);
- int StackEmpty(STACK S);
- /************************************************************************/
- /*
- */
- /************************************************************************/
- void InitStack(STACK &S)
- {
- S.top=NULL;
- }
- /************************************************************************/
- /* 入棧
- */
- /************************************************************************/
- void Push(STACK &S,ElemType e)
- {
- LinkList p;
- p = (LinkList )malloc(sizeof(LNode));
- if (!p) exit(OVERFLOW);
- p->data = e;
- p->next = S.top;
- S.top = p;
- }
- /************************************************************************/
- /* 出棧
- */
- /************************************************************************/
- void Pop(STACK &S, ElemType *e)
- {
- LinkList p;
- if(StackEmpty(S)) exit(STACKEMPTY);
- *e = S.top->data;
- p = S.top;
- S.top = p->next;
- free(p);
- }
- /************************************************************************/
- /* 獲取棧頂元素內容
- */
- /************************************************************************/
- ElemType GetTop(STACK S, ElemType *e)
- {
- if(StackEmpty(S)) exit(STACKEMPTY);
- *e = S.top->data;
- }
- void printStack(STACK S){
- LinkList p;
- p = S.top;
- printf("棧: ");
- while (p) {
- printf("%d ", p->data);
- p = p->next;
- }
- }
- /************************************************************************/
- /* 如果有一個盤子,直接從X移到Z即可。
- 如果有n個盤子要從X移到Z,Y作爲輔助。問題可以轉化爲,先將上面n-1個從X移動到Y,Z作爲輔助,然後將第n個從X移動到Z,最後將剩餘的n-1個從Y移動到Z,X作爲輔助。
- */
- /************************************************************************/
- void move(STACK &Sa,STACK &Sb)
- {
- ElemType e;
- Pop(Sa,&e);
- Push(Sb, e);
- }
- void hanoi(int n,STACK &X,STACK &Y,STACK &Z)
- {
- if(n==1) return move(X, Z); //將圓盤1號直接移到z
- hanoi(n-1,X,Z,Y); //將x上的1大n-1圓盤移到y,z做輔助塔
- move(X, Z); //將編號爲n的圓盤移z
- hanoi(n-1,Y,X,Z); //將y上的1大n-1圓盤移到z,x做輔助塔
- }
- /************************************************************************/
- /* 判斷棧S是否空
- */
- /************************************************************************/
- int StackEmpty(STACK S)
- {
- if(S.top==NULL) return TRUE;
- return FALSE;
- }
- void main()
- {
- STACK Sx, Sy,Sz;
- InitStack( Sx);
- InitStack( Sy);
- InitStack( Sz);
- int i, n = 10;
- for (i = 10 ; i>=1 ;i--) {
- Push(Sx, i);
- }
- printStack(Sx);
- hanoi(n, Sx,Sy,Sz);
- printStack(Sz);
- }
1.隊列
1.1 隊列定義
隊列(Queue)也是一種運算受限的線性表,它的運算限制與棧不同,是兩頭都有限制,插入只能在表的一端進行(只進不出),而刪除只能在表的另一端進行(只出不進),允許刪除的一端稱爲隊尾(rear),允許插入的一端稱爲隊頭 (Front)
,隊列的操作原則是先進先出的,所以隊列又稱作FIFO表(First In First Out)
隊列的基本運算也有六種:
置空隊 :InitQueue(Q)
判隊空: QueueEmpty(Q)
判隊滿: QueueFull(Q)
入隊 : EnQueue(Q,x)
出隊 : DeQueue(Q)
取隊頭元素: QueueFront(Q),不同與出隊,隊頭元素仍然保留。
隊列也有順序存儲和鏈式存儲兩種存儲結構,前者稱順序隊列,後者爲鏈隊。
對於順序隊列,我們要理解"假上溢"的現象。
我們現實中的隊列比如人羣排隊買票,隊伍中的人是可以一邊進去從另一頭出來的,除非地方不夠,總不會有"溢出"的現象,相似地,當隊列中元素完全充滿這個向量空間時,再入隊自然就會上溢,如果隊列中已沒有元素,那麼再要出隊也會下溢。
那麼"假上溢"就是怎麼回事呢?
因爲在這裏,我們的隊列是存儲在一個向量空間裏,在這一段連續的存儲空間中,由一個隊列頭指針和一個尾指針表示這個隊列,當頭指針和尾指針指向同一個位置時,隊列爲空,也就是說,隊列是由兩個指針中間的元素構成的。在隊列中,入隊和出隊並不是象現實中,元素一個個地向前移動,走完了就沒有了,而是指針在移動,當出隊操作時,頭指針向前(即向量空間的尾部)增加一個位置,入隊時,尾指針向前增加一個位置,在某種情況下,比如說進一個出一個,兩個指針就不停地向前移動,直到隊列所在向量空間的尾部,這時再入隊的話,尾指針就要跑到向量空間外面去了,僅管這時整個向量空間是空的,隊列也是空的,卻產生了"上溢"現象,這就是假上溢。
爲了克服這種現象造成的空間浪費,我們引入循環向量的概念,就好比是把向量空間彎起來,形成一個頭尾相接的環形,這樣,當存於其中的隊列頭尾指針移到向量空間的上界(尾部)時,再加1的操作(入隊或出隊)就使指針指向向量的下界,也就是從頭開始。這時的隊列就稱循環隊列。
通常我們應用的大都是循環隊列。由於循環的原因,光看頭尾指針重疊在一起我們並不能判斷隊列是空的還是滿的,這時就需要處理一些邊界條件,以區別隊列是空還是滿。方法至少有三種,一種是另設一個布爾變量來判斷(就是請別人看着,是空還是滿由他說了算),第二種是少用一個元素空間,當入隊時,先測試入隊後尾指針是不是會等於頭指針,如果相等就算隊已滿,不許入隊。第三種就是用一個計數器記錄隊列中的元素的總數,這樣就可以隨時知道隊列的長度了,只要隊列中的元素個數等於向量空間的長度,就是隊滿。
2.2 隊列的順序存儲
順序存儲如圖:
由於是順序存儲結構的存儲空間是靜態分配的,所以在添加數據的時,有可能沒有剩餘空間的情況。
解決這種“假溢出”情況,使用循環隊列。在c語言中,不能用動態分配的一維數組來實現循環隊列。若使用循環隊列,必須設置最大隊列長度,若無法估計最大長度,就使用鏈式隊列。
c實現:
- // Test.cpp : Defines the entry point for the console application.
- //
- #include "stdafx.h"
- #include <stdio.h>
- #include "stdlib.h"
- #include <iostream>
- using namespace std;
- //宏定義
- #define TRUE 1
- #define FALSE 0
- #define OK 1
- #define ERROR 0
- #define INFEASIBLE -1
- #define OVERFLOW -2
- #define QUEUEEMPTY -3
- #define MAX_QUEUE 10 //隊列的最大數據元素數目
- typedef int Status;
- typedef int ElemType;
- typedef struct queue{
- ElemType elem[MAX_QUEUE] ; ///假設當數組只剩下一個單元時認爲隊滿
- int front; //隊頭指針
- int rear; //隊尾指針
- }QUEUE;
- /************************************************************************/
- /* 各項基本操作算法。
- */
- /************************************************************************/
- void InitQueue(QUEUE *&Q);
- void EnQueue(QUEUE *Q,ElemType elem);
- void DeQueue(QUEUE *Q,ElemType *elem);
- int QueueEmpty(QUEUE Q);
- /************************************************************************/
- /*
- 初始化
- 直接使用結構體指針變量,必須先分配內存地址,即地址的指針
- */
- /************************************************************************/
- void InitQueue(QUEUE *&Q)
- {
- Q = (QUEUE *) malloc (sizeof(QUEUE));
- Q->front = Q->rear = -1;
- }
- /************************************************************************/
- /* 入隊
- */
- /************************************************************************/
- void EnQueue(QUEUE *Q, ElemType elem)
- {
- if((Q->rear+1)% MAX_QUEUE == Q->front) exit(OVERFLOW);
- Q->rear = (Q->rear + 1)%MAX_QUEUE;
- Q->elem[Q->rear] = elem;
- }
- /************************************************************************/
- /* 出隊
- */
- /************************************************************************/
- void DeQueue(QUEUE *Q,ElemType *elem)
- {
- if (QueueEmpty(*Q)) exit(QUEUEEMPTY);
- Q->front = (Q->front+1) % MAX_QUEUE;
- *elem=Q->elem[Q->front];
- }
- /************************************************************************/
- /* 獲取隊頭元素內容
- */
- /************************************************************************/
- void GetFront(QUEUE Q,ElemType *elem)
- {
- if ( QueueEmpty(Q) ) exit(QUEUEEMPTY);
- *elem = Q.elem[ (Q.front+1) % MAX_QUEUE ];
- }
- /************************************************************************/
- /* 判斷隊列Q是否爲空
- */
- /************************************************************************/
- int QueueEmpty(QUEUE Q)
- {
- if(Q.front==Q.rear) return TRUE;
- else return FALSE;
- }
- void main()
- {
- QUEUE *Q;
- InitQueue( Q);
- EnQueue( Q, 1);
- EnQueue( Q, 2);
- ElemType e;
- DeQueue( Q,&e);
- cout<<"De queue:"<<e;
- }
注意:InitQueue(QUEUE *&Q) 傳的是指針的地址。
2.3 鏈式隊列:
- // Test.cpp : Defines the entry point for the console application.
- //
- #include "stdafx.h"
- #include <stdio.h>
- #include "stdlib.h"
- #include <iostream>
- using namespace std;
- //宏定義
- #define TRUE 1
- #define FALSE 0
- #define OK 1
- #define ERROR 0
- #define INFEASIBLE -1
- #define OVERFLOW -2
- #define QUEUEEMPTY -3
- typedef int Status;
- typedef int ElemType;
- typedef struct LNode { //鏈式隊列的結點結構
- ElemType elem; //隊列的數據元素類型
- struct LNode *next; //指向後繼結點的指針
- }LNode, *LinkList;
- typedef struct queue{ //鏈式隊列
- LinkList front; //隊頭指針
- LinkList rear; //隊尾指針
- }QUEUE;
- /************************************************************************/
- /* 各項基本操作算法。
- */
- /************************************************************************/
- void InitQueue(QUEUE *Q);
- void EnQueue(QUEUE *Q,ElemType elem);
- void DeQueue(QUEUE *Q,ElemType *elem);
- void GetFront(QUEUE Q,ElemType *elem) ;
- bool QueueEmpty(QUEUE Q);
- /************************************************************************/
- /*初始化隊列Q */
- void InitQueue(QUEUE *Q)
- {
- Q->front = (LinkList)malloc(sizeof(LNode));
- if (Q->front==NULL) exit(ERROR);
- Q->rear= Q->front;
- }
- /*入隊 */
- void EnQueue(QUEUE *Q,ElemType elem)
- {
- LinkList s;
- s = (LinkList)malloc(sizeof(LNode));
- if(!s) exit(ERROR);
- s->elem = elem;
- s->next = NULL;
- Q->rear->next = s;
- Q->rear = s;
- }
- /*出隊 */
- void DeQueue(QUEUE *Q,ElemType *elem)
- {
- LinkList s;
- if(QueueEmpty(*Q)) exit(ERROR);
- *elem = Q->front->next->elem;
- s = Q->front->next;
- Q->front->next = s->next;
- free(s);
- }
- /*獲取隊頭元素內容 */
- void GetFront(QUEUE Q,ElemType *elem)
- {
- if(QueueEmpty(Q)) exit(ERROR);
- *elem = Q.front->next->elem;
- }
- /*判斷隊列Q是否爲空 */
- bool QueueEmpty(QUEUE Q)
- {
- if(Q.front == Q.rear) return TRUE;
- return FALSE;
- }
- void main()
- {
- QUEUE Q;
- InitQueue( &Q);
- EnQueue( &Q, 1);
- EnQueue( &Q, 2);
- ElemType e;
- DeQueue( &Q,&e);
- cout<<"De queue:"<<e;
- }
【舉例1】銀行排隊
【舉例2】模擬打印機緩衝區。
在主機將數據輸出到打印機時,會出現主機速度與打印機的打印速度不匹配的問題。這時主機就要停下來等待打印機。顯然,這樣會降低主機的使用效率。爲此人們設想了一種辦法:爲打印機設置一個打印數據緩衝區,當主機需要打印數據時,先將數據依次寫入這個緩衝區,寫滿後主機轉去做其他的事情,而打印機就從緩衝區中按照先進先出的原則依次讀取數據並打印,這樣做即保證了打印數據的正確性,又提高了主機的使用效率。由此可見,打印機緩衝區實際上就是一個隊列結構。
【舉例3】CPU分時系統
在一個帶有多個終端的計算機系統中,同時有多個用戶需要使用CPU運行各自的應用程序,它們分別通過各自的終端向操作系統提出使用CPU的請求,操作系統通常按照每個請求在時間上的先後順序,將它們排成一個隊列,每次把CPU分配給當前隊首的請求用戶,即將該用戶的應用程序投入運行,當該程序運行完畢或用完規定的時間片後,操作系統再將CPU分配給新的隊首請求用戶,這樣即可以滿足每個用戶的請求,又可以使CPU正常工作。