1. 程式人生 > >資料結構全攻略--線性結構不攻自破之棧和佇列

資料結構全攻略--線性結構不攻自破之棧和佇列

      上篇部落格討論了線性結構的兩種基本的結構順序表和連結串列,它們兩者各有優缺點。總之吧,當我們要儲存容量不固定的資料結構並且要對資料進行多次插入和刪除操作時要多考慮使用連結串列結構,當只涉及對儲存的資料進行只存或只讀操作時應優先選用順序表結構。

繼續討論線性結構--棧和佇列

一、線性結構

 1、棧


      棧是一種特殊的線性表。它的結構非常類似於日常生活中的瓶子,只有一端開口,而插入和刪除的操作只能在瓶子的瓶口處進行,這種一端開口是它的特殊性。這種特殊性也決定了棧中的資料操作順序是先入後出的,也就是說先進入棧中的資料,最後才能出去。

 
棧的基本運算:

                

 

      Note:棧是一種先進後出的線性表,但是並不意味著不會出現先進先出的情況。如:當1、2、3、4順次入棧時,出棧的順序也可能是1、2、3、4,這種情況發生條件是每個數入棧後出棧,然後下一個數入棧。即:1入棧後出棧,然後是2入棧後出棧,其次是3入棧後出棧,最後是4入棧後出棧,這樣順序就變成了1、2、3、4。
      由於棧也是線性表,因此線性表的儲存結構對棧也適用,通常棧有順序棧和鏈棧兩種儲存結構。


  1.1 順序棧

      在順序棧中資料是按照順序進行儲存的。需要我們瞭解的是順序棧中有上溢和下溢的概念。
      上溢:棧頂指標指出棧的外面。我們把順序棧看做一個盒子,那麼當我們把資料放到這個棧中超過盒子的頂部時就放不下了,這時指標指向了棧的外面,這種現象我們稱為上溢。
    下溢:

從空棧中取資料。當棧中沒有資料時,我們再去取資料,看看沒資料,把盒子拎起來看看盒底,還是沒有,這就是下溢。
順序棧的型別定義為:

#define stacksize 100
 typedef char datatype;
 typedef struct{
  datatype data[stacksize];
  int top;
 }seqstack;



    1.1.1 順序棧的基本運算


    (1) 置空棧

Void initstack(seqstack *s)
  {
   s->top=-1;
  }

    (2)判棧空

int stackempty(seqstack *s)
  {
   return s->top==-1;
  }

    (3)判棧滿
  

int stackfull(seqstack *s)
  {
   return s->top==stacksize-1;
  } 

    (4)進棧

Void push(seqstack *s,datatype x)
  {
   if(stackfull(s))
    error(“stack overflow”);
   s->data[++s->top]=x;
  }

     (5)退棧

Datatype pop(seqstack *s)
  {
   if(stackempty(s))
    error(“stack underflow”);
   return S->data[s->top--];
  }

     (6)取棧頂元素
  

Dtatatype stacktop(seqstack *s)
  {
   if(stackempty(s))
    error(“stack underflow”);
   return S->data[s->top];
  }

  1.2 鏈棧

      若是棧中元素的數目變化範圍較大或不清楚棧元素的數目,就應該考慮使用鏈式儲存結構。人們將用鏈式儲存結構表示的棧稱作"鏈棧"。鏈棧通常用一個無頭結點的單鏈表表示。如圖所示:

       和順序棧不同,它沒有固定的結構,也不會出現資料的上溢,它就像是一條一頭固定的鏈子,可以在活動的一頭自由地增加鏈環(結點)而不會溢位。

    1.2.1 基本運算

    (1) 建棧

Void initstack(linkstack *s)
{
  s->top=NULL;
}

     (2)判棧空

Int stackempty (linkstack *s)
{
  return s->top==NULL;
}

      (3) 進棧

Void push(linkstack *s,datatype x)
{
   stacknode *p=(stacknode *)malloc(sizeof(stacknode));
   p->data=x;
   p->next=s->top;
   s->top=p;
}

      (4) 退棧

Datatype pop(linksatck *s)
 {    
    datatype x;    
    stacknode *p=s->top; 

   if(stackempty(s))    
        error(“stack underflow”); 

   x=p->data;    
    s->top=p->next;  
  
    free(p);    
    return x; 
}

     (5) 取棧頂元素

Datatype stacktop(linkstack *s)
{
   if(stackempty(s))
     error(“stack is empty”);
   return s->top->data;
}


2、佇列

           佇列也是一種運算受限的線性表,它的運算限制與棧不同,是兩頭都有限制。佇列就如我們生活中常見的管道,兩端開口。但又和我們常見的管道不同的是,它的插入操作只能在佇列的隊尾(rear)進行(只進不出),而刪除只能在表的隊頭(Front)進行(只出不進),所以佇列的操作原則是先進先出,所以佇列又稱作FIFO表(First In First Out)。


       佇列的基本運算:

               


           佇列也是一種線性結構,所以佇列也有順序儲存和鏈式儲存兩種儲存結構,前者稱順序佇列,後者為鏈隊。


  2.1 順序佇列

          與順序棧類似,順序佇列也有上溢和下溢的情況,它們產生的原因和順序棧類似,這裡就不在細說。由於佇列操作的特殊性(指標移動,元素不同)又出現了假上溢的情況。


           Note:在現實生活中我們隨處可見排隊的情況,當佇列中的人離開佇列後,後面的人會上來補上,當新來人排隊時是在佇列中的尾部進行排列的。我們現在說的佇列和生活中的排隊最大的區別在於前者是指標在移動,後者是元素在移動。
       Note:在佇列中每插入一個元素,佇列的隊尾指標會向後移動一個位置;類似的在佇列中每刪除一個元素,隊頭的指標都會向接近隊尾的方向移動一個位置。


 
     2.1.1 何為假上溢

          上圖中的(c)操作,如果我們繼續向佇列中插入資料,尾指標就要跑到向量空間外面去了,儘管這時整個向量空間是空的,佇列也是空的,卻產生了“上溢”現象,這就是假上溢。

   2.1.2 佇列的基本運算

       (1) 構造空佇列

Void initqueue(cirqueue *q)
{
   q->front=q->rear=0;
   q->count=0;
}

      (2) 判隊空

Int queueempty(cirqueue *q)
{
   return q->count==0;
}

      (3) 判隊滿

Int queuefull(cirqueue *q)
{
   return q->count==queuesize;
}

      (4) 入隊

Void enqueue(cirqueue *q ,datatype x)
{
   if(queuefull(q))
     error(“queue overfolw”);
   q->count++;
   q->data[q->rear]=x;
   q->rear=(q->rear+1)%queuesize;
}

     (5) 出隊

Datatype dequeue(cirqueue *q)
{
   datatype temp;
   if(queueempty(q))
    error(“queue underflow”);
   temp=q->data[q->front];
   q->count--;
   q->front=(q->front+1)%queuesize;
   return temp;
}

     (6) 取隊頭元素

Datatype queuefront(cirqueue *q)
{
   if(queueempty(q))
    error(“queue is empty”);
   return q->data[q->front];
}

 2.2 迴圈佇列

        為了克服線性佇列造成的空間浪費,我們引入迴圈向量的概念,它就好像是把向量空間彎起來,形成一個頭尾相接的環形,這樣當佇列的頭尾指標移動到佇列的尾部時,再進行入隊的操作,就使指標指向佇列的隊頭,也就是從頭開始。這時的佇列就稱迴圈佇列。
      通常我們所使用的大都是迴圈佇列。但由於迴圈的原因,我們就不能當頭尾指標重疊在一起來判斷佇列為空還是滿的情況,為了避免這種情況,我們又引入了邊界條件的概念。


解決方法有:
      ① 另設一個布林變數以區別佇列的空和滿;
      ② 少用一個元素,當入隊時,先測試入隊後尾指標是不是會等於頭指標,如果相等就算隊已滿,不許入隊;
         ③ 使用一個記數器記錄元素總數。

     2.2.1 迴圈佇列的基本運算

       (1) 建空隊

Void initqueue(linkqueue *q)
{
   q->front=q->rear=NULL;
}

         (2) 判隊空

Int queueempty(linkqueue *q)
{
   return q->front==NULL&&q->rear==NULL;
}

          (3) 入隊

Void enqueue(linkqueue *q,datatype x) {    
     queuenode *p=(queuenode *)malloc(sizeof(queuenode));    
     p->data=x;    
     p->next=NULL; 

   if(queueempty(q))    
        q-front=q->rear=p;    
     else
      {    
        q->rear->next=p;    
        q->rear=p;    
     } 
}

      (4) 出隊

Datatype dequeue(linkqueue *q)
{
   datatype x;
   queuenode *p;
   if(queueempty(q))
     error(“queue is underflow”);

    p=q->front;
   x=p->data;
   q->front=p->next;


   if(q->rear==p) 
       q->rear=NULL;


   free(p);
   return x;
}

      (5) 取隊頭元素
  

Datatype queuefront(linkqueue *q)
{
   if(queueempty(q))
     error(“queue is empty”);

   return q->front->data;
}

3、結語

         至此,從頭到尾的把資料結構的線性結構複習了一遍,相信經過上面的複習,對線性結構又有了更深入的瞭解。這部分的知識很重要,但是在軟考中考的知識點卻不深,需要我們掌握幾種基本的線性結構的性質,並能熟練利用幾種性質進行相關的數學運算。複習了基本知識點,接下來就應該進入實戰階段了。

      Suggest:在做題時,不要做完就算了,最好把做的題用到的知識點整理到筆記本上(不論是出錯的還是正確的,當然錯誤的更要加強練習),然後在考試前經常翻看。

接下來將會進入非線性結構部分