數據結構-棧和隊列

1.棧

1.1 棧的定義

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

1.jpg

結論:后進先出(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 棧的鏈式存儲
若是棧中元素的數目變化范圍較大或不清楚棧元素的數目,就應該考慮使用鏈式存儲結構。人們將用鏈式存儲結構表示的棧稱作"鏈棧"。鏈棧通常用一個無頭結點的單鏈表表示。如圖所示:

2.jpg

棧的操作是線性表操作的特例。

簡單用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 棧的遞歸和實現

漢諾塔的問題:

3.jpg

解決:

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 隊列的順序存儲

順序存儲如圖:

4.jpg

由于是順序存儲結構的存儲空間是靜態分配的,所以在添加數據的時,有可能沒有剩余空間的情況。

解決這種“假溢出”情況,使用循環隊列。在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正常工作。

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

原創文章,作者:s19930811,如若轉載,請注明出處:http://www.www58058.com/2802

(0)
s19930811s19930811
上一篇 2015-04-07
下一篇 2015-04-07

相關推薦

  • grep與正則表達式

    1.什么是grep grep(Golobal Regular Expression print)是Linux系統中一個強大的文本搜索工具,也是俗稱的搜索三兄弟之一,grep的最大意義就是搜索文本,把匹配的行打印到屏幕上,但不影響原文件的內容;在搜索文本的過程中,可以利用到“正則表達式”來定以自己的搜索匹配模式。 Unix的grep家族包括了grep、egre…

    Linux干貨 2015-10-08
  • 人志建,則無敵—if、case練習

    馬哥21期網絡班-9周博客作業 1、寫一個腳本,判斷當前系統上所有用戶的shell是否為可登錄shell(即用戶的shell不是/sbin/nologin);分別這兩類用戶的個數;通過字符串比較來實現; #!/bin/bash for i in `cut -d: -f7 /etc/passwd`;&…

    Linux干貨 2016-09-05
  • 基于httpd服務實驗構建網站域名DNS解析

    具體組成簡圖 實驗前期準備 httpd的安裝 后期補充 做httpd 服務器的IP 為: 172.18.148.62 安裝DNS域名解析包 bind yum install bind 修改DNS 的基礎配置文件 /etc/named.conf   關閉所有的虛擬機的 防火墻 iptables -F CENTOS7 的系統關閉selinux sete…

    2017-04-16
  • linux用戶和組管理

    linux系統是一個多用戶的系統,每個賬號都干什么用,你必須了如指掌。 【Linux用戶】 即UID分為兩大類:管理員 UID:0                               普通用戶 UID:…

    Linux干貨 2016-08-05
  • iptables歸納總結

    先簡單介紹下iptables IPTABLES的幾點概念  1、容器:包含或者說屬于的關系  2、Netfilter/iptables是表的容器,iptables包含的各個表 (filter,NAT,MANGLE,RAW)  3、iptables的表tables又是鏈的容器 鏈chains:INPUT,OUTPUT,FORWAR…

    Linux干貨 2017-05-02
  • 程序包管理的前端工具YUM及案例一二

    程序包管理前端工具–YUM        yum:yellowdog update modifier        yum工具為CS架構 yum倉庫(yum repository):yum repo   &n…

    Linux干貨 2016-08-24
欧美性久久久久