第3章 棧 和 隊列

http://blog.csdn.net/xiaoxiaopengbo/article/details/51705954ios

轉載自:http://blog.csdn.net/hguisu/article/details/7674195

1.棧

1.1 棧的定義c++

棧是一種特殊的線性表。其特殊性在於限定插入和刪除數據元素的操做只能在線性表的一端進行。以下所示:算法

結論:後進先出(Last In First Out),簡稱爲LIFO線性表。數組

 

棧的基本運算有六種:app

構造空棧:InitStack(S)、ide

判棧空: StackEmpty(S)、測試

判棧滿: StackFull(S)、大數據

進棧: Push(S,x)、可形象地理解爲壓入,這時棧中會多一個元素ui

退棧: Pop(S) 、 可形象地理解爲彈出,彈出後棧中就無此元素了。spa

取棧頂元素: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); } 

 

2.隊列

2.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;  
}    

2. 4.隊列的應用【舉例1】銀行排隊【舉例2】模擬打印機緩衝區。在主機將數據輸出到打印機時,會出現主機速度與打印機的打印速度不匹配的問題。這時主機就要停下來等待打印機。顯然,這樣會下降主機的使用效率。爲此人們設想了一種辦法:爲打印機設置一個打印數據緩衝區,當主機須要打印數據時,先將數據依次寫入這個緩衝區,寫滿後主機轉去作其餘的事情,而打印機就從緩衝區中按照先進先出的原則依次讀取數據並打印,這樣作即保證了打印數據的正確性,又提升了主機的使用效率。因而可知,打印機緩衝區實際上就是一個隊列結構。【舉例3】CPU分時系統在一個帶有多個終端的計算機系統中,同時有多個用戶須要使用CPU運行各自的應用程序,它們分別經過各自的終端向操做系統提出使用CPU的請求,操做系統一般按照每一個請求在時間上的前後順序,將它們排成一個隊列,每次把CPU分配給當前隊首的請求用戶,即將該用戶的應用程序投入運行,當該程序運行完畢或用完規定的時間片後,操做系統再將CPU分配給新的隊首請求用戶,這樣便可以知足每一個用戶的請求,又可使CPU正常工做

相關文章
相關標籤/搜索