《演算法導論》第十章——基本資料結構(一):棧與佇列

誰不小心的發表於2013-10-15

引言:演算法操作的集合可以隨著時間的改變而增大,減小,或者產生其他變化,我們稱這種集合是動態的。接下來的五章,我們將研究計算機上表示和操作有窮動態集合的一些基本技術。本文主要為你講解棧與佇列的操作和實現,建議將這些操作製作成庫,方便以後的引用,避免重複編碼。系列文章為演算法導論的筆記和相關習題解答。


本文來源:棧與佇列


動態集合的元素

元素包括兩個部分:關鍵字+衛星資料

動態集合上的操作

主要包括兩個大的方面:查詢

我們約定:集合為S,關鍵字為k,指向元素的指標為x

Search(S,k)

Inserrt(S,x)

Delete(S,x)

Minimum(S)

Maximum( S )

Successor (S,x)

Predecessor(S,x)

棧和佇列


一、棧


實現了一種先進先出的策略。通常情況下我們採用陣列加棧頂指示top來實現,約定top==0(或者-1)的時候表示棧為空;同時如果stack的長度是m,那麼要考慮元素個數超過m以後產生的溢位問題。棧的一些定義和基本操作如下:

#ifndef MAXSTACKSIZE
#define MAXSTACKSIZE 200
#endif
typedef int StackDataType;
typedef struct mystack{
  int top;
  StackDataType data[MAXSTACKSIZE];
}Stack,*StackPointer;
extern int IsEmpty(Stack s); 
extern int Push(StackPointer s, StackDataType element);
extern StackDataType Pop(StackPointer s); 

註明:stack的資料結構還可以定義一個指標和大小,來取代陣列。這些操作的實現方式如下:

#include"stack.h"
#include<stdlib.h>
#include<stdio.h>
StackDataType Pop(StackPointer s){ 
  if(s->top==-1){
    printf("the stack is already empty!\n");
    exit(1);
  }

  --(s->top);
  return s->data[s->top+1];
}

int IsEmpty(Stack s){ 
  if(s.top==-1){
    return 1;
  }
  else{
    return 0;
  }
}

int Push(StackPointer s,StackDataType element){
  if(s->top >= MAXSTACKSIZE-1){
    printf("the stack is over flow!\n");
    return 0;
  }
  ++(s->top);
  s->data[s->top]=element;
  return 1;
}


二、佇列


我們可以把佇列想象成排隊等待服務的人群,同樣也可以用 陣列+對頭指標+對尾指標 來實現佇列這個資料結構;其中,head表示下一個要出列的元素,tail表示剛剛進入佇列的元素。另外,為了實現環繞,我們需要把佇列實現成為迴圈陣列的模式,這個可以通過取模運算來實現。這樣,head==tail+1表示佇列已經滿了;但是,此時我們會發現,如果我們讓元素逐個出列,那麼當最後一個元素出列的時候,同樣滿足head=tail+1;也就是說,佇列在空和滿的情況下具有相同的抽象條件。為了對此進行區分,我們用tail表示下一個進入佇列的元素將要進入佇列的位置,那麼,初始化情況下,所以head=tail表示佇列為空,初始情況下,head=tail=0;如果我們用head=tail+1表示佇列為滿的情況,意味著這個情況下,佇列中有一個位置處於沒有利用的狀態,如果要利用這個元素,又會出現head=tail表示空和滿兩種情況。

關於佇列的一些操作例項可以看下圖,從而理解上面一段中的相關情況:


佇列的一些定義的基本的操作如下:

#ifndef MAXQUEUESIZE
#define MAXQUEUESIZE 200
#endif
typedef int QueueDataType;
typedef struct myqueue{
  int tail;
  int head;
  QueueDataType data[MAXQUEUESIZE];
}Queue,*QueuePointer;
extern int IsEmpty(Queue queue);
extern int IsFull(Queue queue);
extern int EnQueue(QueuePointer queuep, QueueDataType element);
extern QueueDataType DeQueue(QueuePointer queuep);

操作實現:

#include"queue.h"
#include<stdlib.h>
#include<stdio.h>
/*make sure the queue is not empty when you delete element from it*/
QueueDataType DeQueue(QueuePointer queue){
  if(queue->head==queue->tail){
    printf("the queue is already empty!\n");
    exit(1);
  }
  QueueDataType temp;
  temp=queue->data[queue->head];
  ++(queue->head);
  if(queue->head==MAXQUEUESIZE)//
    queue->head=0;
  return temp;
}

int IsEmpty(Queue queue){
  if(queue.head==queue.tail){
    return 1;
  }
  else{
    return 0;
  }
}
int IsFull(Queue queue){
  if(queue.head==(queue.tail+1)%MAXQUEUESIZE){
    return 1;
  }
  else
    return 0;
}

int EnQueue(QueuePointer queue,QueueDataType element){
  if(IsFull((*queue))){
    printf("the queue is over flow!\n");
    return 0;
  }
  queue->data[queue->tail]=element;
  ++(queue->tail);
  if(queue->tail==MAXQUEUESIZE)
    queue->tail=0;
  return 1;
}


心得:棧與佇列,分別是兩種FIFO和FILO的資料結構,至於具體實現,棧的增長和佇列進出的方向完全可以由使用者定義。另外,在實際的程式設計中,以迴圈佇列的實現為例,容易出現邏輯錯誤的地方在於條件判斷和邊界條件的設定。其中條件判斷要是充要條件,也就是說,你的程式碼需要能準確反應你的文字表述的意圖,雖然我們設定的條件是佇列滿,但是如果用head=tail,雖然表示了佇列滿的情況,但是它也表示佇列是空的情況,所以是不正確的。比如判斷佇列是滿還是空,如果我們想要將陣列的每個元素都利用,這時就會發現佇列在滿和空的時候滿足同樣的條件head=tail,顯然,這是應該避免的;所以浪費了一個空間,來區分佇列是滿還是空這個情況。經驗:行動前周密的思考能大量節省時間。


練習:程式設計實現2,4,5,6,7:

2,一個棧stack1從低地址到高地址,另外一個棧stack2從高地址到低地址:當top1>=top2時,棧溢位。

4,解答:參見本文中的程式碼,裡面有對溢位情況的處理

5,解答:參見程式碼biqueue.c

6,解答:兩個棧,棧底相連就是一個佇列,但是要注意這兩個棧為空的邊界條件。


7,解答:兩個佇列,參考下圖


相關文章