1. 程式人生 > >棧與隊列

棧與隊列

.com data 結果 無法 fin 特點 共享存儲 ont 軟件

棧與隊列

簡介

堆棧和隊列都屬於線性結構,是兩種在運算上受到某些限制的特殊線性表,他們比一般線性表更簡單,被廣泛應用於類型的程序設計中,可以用來存放許多中間信息,在系統軟件設計以及遞歸問題處理方面都離不開堆棧和隊列。

棧的操作原則是:先進後出,後進先出

二、棧的特點

根據棧的定義可知,最先放入棧中元素在棧底,最後放入的元素在棧頂,而刪除元素剛好相反,最後放入的元素最先刪除,最先放入的元素最後刪除。 也就是說,棧是一種後進先出(Last In First Out)的線性表,簡稱為LIFO表。

三、棧的運算

1.初始化棧:INISTACK(&S)

將棧S置為一個空棧(不含任何元素)。

2.進棧:PUSH(&S,X)

將元素X插入到棧S中,也稱為 “入棧”、 “插入”、 “壓入”。

3.出棧: POP(&S)

刪除棧S中的棧頂元素,也稱為”退棧”、 “刪除”、 “彈出”。

4.取棧頂元素: GETTOP(S)

取棧S中棧頂元素。

5.判棧空: EMPTY(S)

判斷棧S是否為空,若為空,返回值為1,否則返回值為0。

技術分享

棧總是處於棧空、棧滿或不空不滿三種狀態之一,它們是通過棧頂指針top的值體現出來的。 規定:top的值為下一個進棧元素在數組中的下標值。 棧空時(初始狀態),top=0; 棧滿時,top=MAXN.

三、棧的五種運算 (一) 進棧 1) 進棧算法 (1) 檢查棧是否已滿,若棧滿,進行“溢出”處理。 (2) 將新元素賦給棧頂指針所指的單元。 (3) 將棧頂指針上移一個位置(即加1)。

(二) 出棧 1) 出棧算法 (1) 檢查棧是否為空,若棧空,進行“下溢”處理。 (2)將棧頂指針下移一個位置(即減1) 。 (3)取棧頂元素的值,以便返回給調用者。

四.棧的共享存儲單元 有時,一個程序設計中,需要使用多個同一類型的棧,這時候,可能會產生一個棧空間過小,容量發生溢出,而另一個棧空間過大,造成大量存儲單元浪費的現象。 為了充分利用各個棧的存儲空間,這時可以采用多個棧共享存儲單元,即給多個棧分配一個足夠大的存儲空間,讓多個棧實現存儲空間優勢互補。

4、兩個棧共享同一存儲空間  當程序中同時使用兩個棧時,可以將兩個棧的棧底設在向量空間的兩端,讓兩個棧各自向中間延伸。當一個棧裏的元素較多,超過向量空間的一半時,只要另一個棧的元素不多,那麽前者就可以占用後者的部分存儲空間。 只有當整個向量空間被兩個棧占滿(即兩個棧頂相遇)時,才會發生上溢。因此,兩個棧共享一個長度為m的向量空間和兩個棧分別占用兩個長度為m/2┘和┌m/2┐的向量空間比較,前者發生上溢的概率比後者要小得多。

隊列

1、定義  隊列(Queue)是只允許在一端進行插入,而在另一端進行刪除的運算受限的線性表

技術分享

(1)允許刪除的一端稱為隊頭(Front)。   (2)允許插入的一端稱為隊尾(Rear)。   (3)當隊列中沒有元素時稱為空隊列。   (4)隊列亦稱作先進先出(First In First Out)的線性表,簡稱為FIFO表。  隊列的修改是依先進先出的原則進行的。新來的成員總是加入隊尾(即不允許"加塞"),每次離開的成員總是隊列頭上的(不允許中途離隊),即當前"最老的"成員離隊。

隊列的存儲結構:

順序隊列 隊列的順序存儲結構稱為順序隊列,順序隊列實際上是運算受限的順序表,和順序表一樣,順序隊列也是必須用一個數組來存放當前隊列中的元素。由於隊列的隊頭和隊尾的位置是變化的,因而要設兩個指針和分別指示隊頭和隊尾元素在隊列中的位置。

技術分享

(3) 順序隊列的基本操作 隊列也有隊空、隊滿或不空不滿三種情況。 1.第一種表示方法 規定:head指向隊首元素的位置,tail指向隊尾元素的位置。隊列初始狀態設為head=0,tail=-1. 當隊列非空時,tail>=head; 當隊列空時, head>tail; 當隊列滿時, tail=maxsize-1. 2.第二種表示方法 規定:head指向隊首元素的前一個位置,tail指向隊尾元素的位置。隊列初始狀態設為head=tail=-1. 1)當隊列非空時,tail>head; 2)當隊列空時, head=tail;3)當隊列滿時, tail=maxsize-1.   ①入隊時:將新元素插入rear所指的位置,然後將rear加1。   ②出隊時:刪去front所指的元素,然後將front加1並返回被刪元素。

(4)順序隊列中的溢出現象   ① "下溢"現象  當隊列為空時,做出隊運算產生的溢出現象。“下溢”是正常現象,常用作程序控制轉移的條件。   ② "真上溢"現象  當隊列滿時,做進棧運算產生空間溢出的現象。“真上溢”是一種出錯狀態,應設法避免。   ③ "假上溢"現象 從順序存儲的隊列可以看出,有可能出現這樣情況,尾指針指向一維數組最後,但前面有很多元素已經出隊,即空出很多位置,這時要插入元素,仍然會發生溢出。例如,在下圖中,若隊列的最大容量maxsize=4,此時,tail=3,再進隊時將發生溢出。我們將這種溢出稱為“假溢出”。 要克服“假溢出”,可以將整個隊列中元素向前移動,直到頭指針head為零,或者每次出隊時,都將隊列中元素前移一個位置。因此,順序隊列的隊滿判定條件為tail=maxsize-1。但是,在順序隊列中,這些克服假溢出的方法都會引起大量元素的移動,花費大量的時間,所以在實際應用中很少采用,一般采用下面的循環隊列形式。

循環隊列

一)定義 為了克服順序隊列中假溢出,通常將一維數組queue[0]到q[maxsize-1]看成是一個首尾相接的圓環,即queue[0]與queue[maxsize-1]相接在一起。將這種形式的順序隊列稱為循環隊列 。

若tail+1=maxsize,則令tail=0. 這樣運算很不方便,可利用數學中的求模運算來實現。

入隊:tail=(tail+1) mod maxsize;squeue[tail]=x. 出隊:head=(head+1) mod maxsize.

二)循環隊列的變化

在循環隊列中,若head=tail,則稱為隊空, 若(tail+1) mod maxsize=head, 則稱為隊滿,這時,循環隊列中能裝入的元素個數為maxsize-1,即浪費一個存儲單元,但是這樣可以給操作帶來較大方便。


三)循環隊列上五種運算實現

1.進隊列

1)進隊列算法

(1)檢查隊列是否已滿,若隊滿,則進行溢出錯誤處理;

(2)將隊尾指針後移一個位置(即加1),指向下一單元;

(3)將新元素賦給隊尾指針所指單元。

2) 進隊列實現程序

int head=0,tail=0;

int enqueue (elemtype queue[], elemtype x)

{ if ((tail+1)%maxsize = = head) return(1);

else

{

tail=(tail+1)%maxsize;

queue[tail]=x;

return(0);

}

}

2. 出隊列

1)出隊列算法

(1)檢查隊列是否為空,若隊空,則進行下溢錯誤處理;

(2)將隊首指針後移一個位置(即加1);

(3)取隊首元素的值。

2) 出隊列實現程序

int head=0,tail=0;

int dlqueue(elemtype queue[ ],elemtype *p_x )

{

if (head= =tail) return(1);

else

{

head =(head+1) % maxsize;

*p_x=queue[head]];

return(0);

}

}

(3) 隊列初始化 head=tail=0;

(4) 取隊頭元素(註意得到的應為頭指針後面一個位置值)

elemtype gethead(elemtype queue[ ] )

{ if (head= =tail) return(null);

else return (queue[(head+1)%maxsize]);

}

(5) 判隊列空否

int empty(elemtype queue[ ] )

{

if (head= =tail) reurn (1);

else return (0);

}

技術分享

(1) 循環隊列的基本操作

 循環隊列中進行出隊、入隊操作時,頭尾指針仍要加1,朝前移動。

只不過當頭尾指針指向向量上界(QueueSize-1)時,其加1操作的結果是指向向量的下界0。

這種循環意義下的加1操作可以描述為:

① 方法一: if(i+1==QueueSize) //i表示front或rear i=0;

else i++;

② 方法二--利用"模運算"

i=(i+1)%QueueSize;

(2) 循環隊列邊界條件處理  

循環隊列中,由於入隊時尾指針向前追趕頭指針;出隊時頭指針向前追趕尾指針,造成隊空和隊滿時頭尾指針均相等。因此,無法通過條件front==rear來判別隊列是"空"還是"滿"。

【參見動畫演示】   解決這個問題的方法至少有三種:  

 ① 另設一布爾變量以區別隊列的空和滿;  

 ② 少用一個元素的空間。約定入隊前,測試尾指針在循環意義下加1後是否等於頭指針,若相等則認為隊滿(註意:rear所指的單元始終為空);   

③使用一個計數器記錄隊列中元素的總數(即隊列長度)。

(3) 循環隊列的類型定義

#define Queur Size 100 //應根據具體情況定義該值

typedef char Queue DataType; //DataType的類型依賴於具體的應用

typedef Sturet{ //頭指針,隊非空時指向隊頭元素

int front; //尾指針,隊非空時指向隊尾元素的下一位置

int rear; //計數器,記錄隊中元素總數

DataType data[QueueSize]

}CirQueue;

(4) 循環隊列的基本運算   用第三種方法,循環隊列的六種基本運算:  

 ① 置隊空

void InitQueue(CirQueue *Q)

{

Q->front=Q->rear=0;

Q->count=0; //計數器置0

}

  

② 判隊空

int QueueEmpty(CirQueue *Q)

{

return Q->count==0; //隊列無元素為空

}  

 ③ 判隊滿

int QueueFull(CirQueue *Q)

{

return Q->count==QueueSize; //隊中元素個數等於QueueSize時隊滿

}  

 ④ 入隊

void EnQueue(CirQueuq *Q,DataType x)

{

if(QueueFull((Q))

Error("Queue overflow"); //隊滿上溢

Q->count ++; //隊列元素個數加1

Q->data[Q->rear]=x; //新元素插入隊尾

Q->rear=(Q->rear+1)%QueueSize; //循環意義下將尾指針加1   

⑤ 出隊

DataType DeQueue(CirQueue *Q)

{

DataType temp;

if(QueueEmpty((Q))

Error("Queue underflow"); //隊空下溢

temp=Q->data[Q->front];

Q->count--; //隊列元素個數減1

Q->front=(Q->front+1)&QueueSize; //循環意義下的頭指針加1

return temp;

}   

⑥取隊頭元素

DataType QueueFront(CirQueue *Q)

{

if(QueueEmpty(Q))

Error("Queue if empty.");

return Q->data[Q->front];

}

鏈隊列

一、隊列的鏈式存儲結構 隊列的鏈式存儲是用一個線性鏈表來實現一個隊列的方法,線性鏈表表示的隊列稱為鏈隊列。

在鏈隊列中,鏈表的第一個節點存放隊列的隊首結點,鏈表的最後一個節點存放隊列的隊尾首結點,隊尾結點的鏈接指針為空。並設一頭指針指向隊首結點,設一尾指針指向隊尾結點。 當隊列為空時,有head=NULL.

二、鏈隊列上的基本運算

1 .入隊列

1)進隊算法

(1) 為待進隊元素申請一個新結點,給該結點賦值;

(2)將x結點鏈到隊尾結點上;

(3)隊尾指針改為指向x結點。

2)實現程序

NODE *head=NULL, *tail;

Void link_ins_queue(elemtype x)

{

NODE *p;

p=(NODE *)malloc(sizeof(NODE));

p->data=x;

p->link=null;

if (head==null) head=p;

else tail->link=p;

tail=p;

}

2. 出隊列

1)出隊算法

(1) 檢查隊列是否為空,若為空進行下溢錯誤處理;

(2)取隊首元素的值並將隊首指針暫存;

(3)頭指針後移,指向新隊首結點,並刪除原隊首結點。

2) 實現程序

int del_queue(elemtype *p_x)

{ NODE *p;

if (head= =NULL) 

return(1);

*p_x=head->data;

p=head;

head=head->link;

free(p);

return(0);

}

技術分享

註意:   增加指向鏈表上的最後一個結點的尾指針,便於在表尾做插入操作。  鏈隊列示意圖見上圖,圖中Q為LinkQueue型的指針。

註意:  ①和鏈棧類似,無須考慮判隊滿的運算及上溢。  ②在出隊算法中,一般只需修改隊頭指針。但當原隊中只有一個結點時,該結點既是隊頭也是隊尾,故刪去此結點時亦需修改尾指針,且刪去此結點後隊列變空。  ③以上討論的是無頭結點鏈隊列的基本運算。和單鏈表類似,為了簡化邊界條件的處理,在隊頭結點前也可附加一個頭結點,增加頭結點的鏈隊列的基本運算。

棧與隊列