1. 程式人生 > >資料結構與演算法05-棧與佇列

資料結構與演算法05-棧與佇列

棧是限定僅在表尾進行插入和刪除操作的線性表。佇列是隻允許在一端進行插入操作、而在別一端進行刪除的操作的線性表。

棧的定義

棧是限定僅在表尾進行插入和刪除操作的線性表。我們把允許插入和刪除的一端稱為棧頂。另一端稱為棧底,不含任何資料元素的棧稱為空棧。又稱後進先出線性表(LIFO結構)

理解:首先它是一個線性表,也就是說,棧元素具有線性關係,即前驅後繼關係。只不過它是一種特殊的線性表而已。特殊之處在於限制了這個線性表的插入和刪除位置,它始終只在棧頂進行。這也就使得:棧底是固定的,最先棧的只能在棧底。 棧的插入操作,叫作進棧,也稱壓棧、入棧。 棧的刪除操作,也叫出棧,也有叫作彈棧。

棧的抽象資料型別

ADT 棧(stack) Data 同線性表。元素具胡相同型別,相鄰元素具有前驅和後繼關係 Operation InitStack(*S);初始化操作,建立一個空棧S DestroyStack(*S):若棧存在,則銷燬它 ClearStack(S):將棧清空 StackEmpty(S):若棧為空返回true,否則返回false GetTop(S,*e):若棧存在且非空,用e返回S的棧頂元素 Push(*S,e):若棧存在,輸入新元素e到棧S中併成為棧頂元素 Pop(*S,*e):刪除棧S中棧頂元素,並用e返回其值 StackLength(S):返回棧S的元素個數 endADT 由於棧本身就是一個線性表,那麼前面學到的線性表的順序儲存和鏈式儲存,對於棧來說,也是同樣適用的。

棧的順序儲存結構及實現

既然棧是線性表的特例,那麼棧的順序儲存其實也是線性表表順序儲存的簡化,我們簡稱為順序棧。線性表是用陣列來實現的,想想看,對於棧這種只能一頭插入刪除的線性表來說,用陣列哪一端來作為棧頂和棧底較好?對沒錯,下標為0的一端作為棧底比較好,因為首元素都存在棧底,變化最小,所以讓它作棧底。我們定義一個top變數來指示棧頂元素在陣列中的位置。若儲存棧的長度為StackSize,則棧頂位置top必須小於StackSize。當棧存在一個元素時,top等於0,因此通常把空棧的判斷條件定為top等於-1; 棧的定義:

typedef    int   SElemType
typedef   struct
{
    SElemType   data[MAXSIZE];
    int   top;
}

進棧操作

Status  Push(SqStack  *S,SElemType  e)
{
   if(S->top==MAXSIZE-1)//棧滿
    {
         return  ERROR;
     }
     S->top++;
     S->data[S->top] = e;
     return OK;
}

出棧操作

Status Pop(SqStack *S,SElemType  *e)
{
   if(S->top==-1) return ERROR;
   *e=S->data[S->top];
     S->top--;
     return OK;
}

進棧出棧沒有涉及到任何迴圈語句,因此時間複雜度均是O(1)

兩棧共享空間

如果我們有兩個相同型別的棧,我們為它們各自開闢了陣列空間,極有可能是第一個棧已經滿了,再進棧就舉出了,而另一個棧還有很多儲存空間空閒。這又何必呢,我們完全可以用一個數組來儲存兩個棧,只不過需要點小技巧。 陣列有兩個端點,兩個棧有兩個棧底,讓一個棧底為陣列的始端,即為下標0處,另一個棧為陣列的末端,即下標為陣列長度n-1處。這樣,兩個棧如果增加元素,就是兩個端點向中間延伸。 關鍵思路是:它們是陣列的兩端,向中間靠攏。top1和top2是棧1和棧2的棧頂指標,可以想象,只要它們不見面就可以一直使用: 棧1空時,top1等於-1; 棧2空時,top2等於n 極端情況下棧2空,棧1的top1等於n-1時,就是棧1滿。反之,當棧1為空時,top2等於0時,為棧2滿。但更多的情況,其實就是我剛才說的,兩個棧見面之時,也就是兩個指標之間相差1時,即top1+1 = top2為棧滿 程式碼如下: //兩棧共享空間結構

typedef  struct
{
   SElemType  data[MAXSIZE];
   int   top1;
   int   top2;
}SqDoubleStack;

//壓棧時,除了插入元素值引數外,還需要有一個判斷是棧1還是棧2的棧號引數stackNumber

Status  Push(SqStack  *S,SElemType  e, int  stackNumber)
{
   if(S->top1+1==S->top2)//棧滿
    {
         return  ERROR;
     }
     if(stackNumber==1){
          //棧1
          S->data[++S->top1] = e;
     }else if(stackNumber==2){
          S->data[--S->top2] = e;
     }
     return OK;
}

Status Pop(SqDoubleStack  *S,SElemType  *e,int  stackNumber)
{
    if(stackNumber==1){
       if(S->top1==-1) return ERROR;
       *e = S->data[S->top1--];
    }else if(stackNumber==2){
       if(S->top2==MAXSIZE) return ERROR;
       *e = S->data[S->top2++];
    }
    return OK;
}

棧的鏈式儲存結構及實現

都已經有了棧頂在頭部了,單鏈表中比較常用的頭結點也就失去了意義,通常對於鏈棧來說,不需要頭結點的。

對於鏈棧來說,基本不存在棧滿的情況,除非記憶體已經滑可以使用的空間,如果真的發生,那麼此時的計算機系統已面臨宕機崩潰的情況,而不是這個鏈棧是否溢位的問題。 程式碼結構如下:

typedef  struct  StackNode
{
   SElemType  data;
   struct StackNode  *next;
} StackNode,*LinkStackPtr

typedef  struct  LinkStack
{
      LinkStackPtr   top;
      int   count;
} LinkStack;

鏈棧—進棧

Status  Push(LinkStack *S,SElemType  e)
{
LinkStackPtr  s = (LinkStackPtr  )malloc(sizeof(StackNode));
s->data=e;
s->next =S->top;
S->top = s;
S->count++;
return OK;
}

鏈棧—出棧

Status  Pop(LinkStack  *S,SElemType  *e)
{
    LinkStackPtr  p;
    if(StackEmpty(*S))
       return ERROR;
     *e = S->top->data;
      p=S->top;
      S->top = S->top->next;
      free(p);
      S->count--;
      return OK;
}

對於空間效能,順序棧需要確定一個固定長度,可能會存在記憶體空間浪費的問題,但它的優勢是存取時定位很方便,而鏈棧則要求每個元素都有指標域,這同時也增加了一些記憶體的開銷,但對於棧的長度無限制。

即:如果棧的使用過程中元素變化不可預料,有時很小,有時很大,那麼最好是用鏈棧,反之,如果它的變化的可控範圍內,建議使用順序棧會更好一些。