棧與隊列
棧與隊列
簡介
堆棧和隊列都屬於線性結構,是兩種在運算上受到某些限制的特殊線性表,他們比一般線性表更簡單,被廣泛應用於類型的程序設計中,可以用來存放許多中間信息,在系統軟件設計以及遞歸問題處理方面都離不開堆棧和隊列。
棧
棧的操作原則是:先進後出,後進先出
二、棧的特點
根據棧的定義可知,最先放入棧中元素在棧底,最後放入的元素在棧頂,而刪除元素剛好相反,最後放入的元素最先刪除,最先放入的元素最後刪除。 也就是說,棧是一種後進先出(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型的指針。
註意: ①和鏈棧類似,無須考慮判隊滿的運算及上溢。 ②在出隊算法中,一般只需修改隊頭指針。但當原隊中只有一個結點時,該結點既是隊頭也是隊尾,故刪去此結點時亦需修改尾指針,且刪去此結點後隊列變空。 ③以上討論的是無頭結點鏈隊列的基本運算。和單鏈表類似,為了簡化邊界條件的處理,在隊頭結點前也可附加一個頭結點,增加頭結點的鏈隊列的基本運算。
棧與隊列