3.0 棧和佇列
title: 資料結構 | 棧和佇列
date: 2019-12-6 22:34:58
tags: 資料結構
順序棧、鏈棧、迴圈佇列、鏈佇列
- 學習目標
掌握棧和佇列這兩種抽象資料型別的特點,並能在相應的應用問題中正確選用它們。
熟練掌握棧型別的兩種實現方法。
熟練掌握迴圈佇列和鏈佇列的基本操作實現演算法。
理解遞迴演算法執行過程中棧的狀態變化過程。 - 知識點
順序棧、鏈棧、迴圈佇列、鏈佇列
棧
只允許在一端插入和刪除的順序表
允許插入和刪除的一端稱為棧頂 (top) —表尾
另一端稱為棧底(bottom) —表頭
不含元素的空表稱空棧
特點: 先進後出(FILO)或後進先出(LIFO)
ADT
ADT Stack { 資料物件:D={ai|ai∈ElemSet,i=1,2,…,n,n≥0} 資料關係:R={<ai-1,ai>|ai,ai-1∈D,i=2,…,n} 約定:an為棧頂,a1為棧底 基本操作: InitStack(&S) 操作結果:構造一個空的棧S。 DestroyStack(&S) 初始條件: 棧S已經存在。 操作結果: 銷燬棧S。 ClearStack(&S) 初始條件: 棧S已經存在。 操作結果: 將棧S重置為空棧。 StackEmpty(S) 初始條件: 棧S已經存在。 操作結果: 若棧S為空棧,則返回TURE;否則返回FALSE -判定棧是否為空棧是棧在應用程式中經常使用的操作,通常以它作為迴圈結束的條件。 StackLength(S) 初始條件: 棧S已經存在。 操作結果: 返回棧S中的資料元素個數。 GetTop(S,&e) 初始條件: 棧S已經存在且非空。 操作結果: 用e返回棧S中棧頂元素的值。 -這是取棧頂元素的操作,只以 e 返回棧頂元素,並不將它從棧中刪除。 Push(&S,e) 初始條件: 棧S已經存在。 操作結果: 插入元素e為新的棧頂元素。 -這是入棧操作,在當前的棧頂元素之後插入新的棧頂元素。 Pop(&S,&e) 初始條件: 棧S已經存在且非空。 操作結果: 刪除S的棧頂元素並用e返回其值。 -這是出棧操作,不僅以 e 返回棧頂元素,並將它從棧中刪除。 StackTraverse(S,visit ()) 初始條件: 棧S已經存在且非空。 操作結果: 從棧底到棧頂依次對S的每個元素呼叫函式visit ()。一旦visit ()失敗,則操作失敗。 -這是對棧進行從棧底到棧頂的"遍歷"操作,應用較多的場合是,輸出棧中所有資料元素。 }
棧的表示和實現
兩種
- 順序儲存結構__順序棧;
- 鏈式儲存結構__鏈棧;
順序棧的定義
利用一組地址連續的儲存單元依次自棧底到棧頂存放棧的資料元素。
- 棧頂指標top
指向實際棧頂後的空位置,初值為0 - top=0,棧空, 此時出棧,則下溢(underflow)
top=M,棧滿,此時入棧,則上溢(overflow)
順序棧的資料型別
#define STACK_INIT_SIZE 100;//儲存空間初始分配量 #define STACKINCREMENT 10;//儲存空間分配增量 typedef struct { SElemType *base; //在棧構造之前和銷燬之後,值為null SElemType *top; //棧頂指標 int StackSize; //當前已分配的儲存空間,以元素為單位 } SqStack;
順序棧的操作
InitStack
Status InitStack( SqStack &S )
{
S.Base=(SElemType *)malloc(STACK_INIT_SIZE*sizeof(SElemType));
if(!S.Base)
{
return OVERFLOW;
}
S.Top = S.Base;
S.StackSize = STACK_INIT_SIZE;
return OK;
}// InitStack
GetTop
// 用e返回棧S的棧頂元素,若棧空,函式返回ERROR
Status GetTop( SqStack S, SElemType &e)
{
if( S.Top != S.Base ) // 棧空嗎?
{
e = *( S.Top – 1 );
return OK;
}
else
{
return ERROR;
}
}// GetTop
Push
//把元素e入棧
Status Push(SqStack &S, SElemType e )
{
// 若棧滿,追加儲存空間
if( S.Top >= S.Base + S.StackSize )
{
S.Base= (SElemType *)realloc(S.Base,(S.StackSize + STACKINCREMENT) *sizeof(SElemType));
if( !S.Base )
return OVERFLOW; //儲存分配失敗
S.Top = S.Base + S.StackSize;
S.StackSize += STACKINCREMENT;
}
*S.Top = e;
S.Top++;
return OK;
}// Push
Pop
// 出棧
Status Pop( SqStack &S, SElemType &e )
{
if( S.Top == S.Base ) // 空嗎?
{
return ERROR;
}
S.Top --;
e = *S.Top;
return OK;
}// Pop
鏈棧
定義
typedef struct{
SLink top; // 棧頂指標
int length; // 棧中元素個數
}Stack;
typedef struct node{
int data;
struct node *next;
}*SLink;
棧的應用舉例
把10進位制數159轉換成8進位制數
因此,需要先儲存在計算過程中得到的八進位制數的各位,然後逆序輸出,因為它是按"後進先出"的規律進行的,所以用棧最合適。
void conversion ()
{// 對於輸入的任意一個非負十進位制整數,列印輸出與其等值的八進位制數
InitStack(S); // 構造空棧
scanf ("%d",N);
while (N) {
Push(S, N % 8);
N = N/8;
}
while (!StackEmpty(S)) {
Pop(S,e);
printf ( "%d", e );
}
} // conversion
括弧匹配檢驗
現在的問題是,要求檢驗一個給定表示式中的括弧是否正確匹配?
檢驗括號是否匹配的方法可用“期待的急迫程度”這個概念來描述。
status matching(string& exp) {
// 檢驗表示式中所含括弧是否正確巢狀,若是,則返回
// OK,否則返回ERROR
int state = 1;
while (i<=length(exp) && state) {
swith of exp[i] {
case "(": {Push(S,exp[i]); i++; break;}
case ")": {
if (NOT StackEmpty(S) && GetTop(S) = "(")
{ Pop(S,e); i++; }
else{ state = 0 }
break;
}
… }
}
if ( state && StackEmpty(S) )
return OK
else return ERROR;
}
行編輯程式問題
- 一個簡單的行編輯程式的功能是:接受使用者從終端輸入的程式或資料,並存入使用者的資料區。每接受一個字元即存入使用者資料區。
- 較好的做法是,設立一個輸入緩衝區,用以接受使用者輸入的一行字元,然後逐行存入使用者資料區。允許使用者輸入出差錯,並在發現有誤時可以及時更正。
例如,可用一個退格符“#”表示前一個字元無效;可用一個退行符“@”,表示當前行中的字元均無效。
例如,假設從終端接受了這樣兩行字元:
whli##ilr#e(s#s)
outcha@putchar(s=#++);
則實際有效的是下列兩行:
while (s)
putchar(s++);
void LineEdit() {
// 利用字元棧S,從終端接收一行並傳送至呼叫過程
// 的資料區。
InitStack(S); //構造空棧S
ch = getchar(); //從終端接收第一個字元
while (ch != EOF) { //EOF為全文結束符
while (ch != EOF && ch != '\n') {
switch (ch) {
case '#' : Pop(S, c); break; // 僅當棧非空時退棧
case '@': ClearStack(S); break; // 重置S為空棧
default : Push(S, ch); break; // 有效字元進棧,未考慮棧滿情形
}
ch = getchar(); // 從終端接收下一個字元
}
將從棧底到棧頂的字元傳送至呼叫過程的資料區
ClearStack(S); // 重置S為空棧
if (ch != EOF) ch = getchar();
}
DestroyStack(S);
}
表示式求值
- 規則:
先乘除,後加減;
從左到右;
先括號內,後括號外;
- 把運算子和界限符統稱為算符
- “算符優先法”
4 - 10 / 5 + 2 * ( 3 + 8 )
4
4 -
4 - 10
4 - 10 /
4 - 10 / 5
4 - 10 / 5 + => 4 – 2 +
4 - 2 + => 2 +
2 + 2
2 + 2 *
2 + 2 * (
2 + 2 * ( 3
2 + 2 * ( 3 +
2 + 2 * ( 3 + 8
2 + 2 * ( 3 + 8 ) => 2 + 2 * 11 => 2 + 22 => 26
算符優先法
根據這個運算優先關係的規定來實現對錶達式的編譯或解釋執行。
算符間的優先關係
演算法描述
//表示式求值
OpendType EvaluateExpression( ){
InitStack( OPTR );
Push( OPTR, ‘#’ );
InitStack( OPND );
c = getchar( );
while(!(c == ‘#’ && GetTop( OPTR ) == ‘#’) ){
if(!In(c,OP)) // c是運算子?,OP是運算子集合
{
Push( OPND, c);
c = getchar( );
}
else
{
switch( Precede ( GetTop( OPTR), c ))
{
case ‘<’ : //棧頂元素優先權低
Push( OPTR, c );
c = getchar( );
break;
case ‘=‘ : // c為’)’
Pop( OPTR, x );
c = getchar( );
break;
case ‘>’: //退棧並將運算結果入棧
Pop( OPTR, t );
Pop( OPND, b );
Pop( OPND, a );
Push( OPND, Operate( a, t, b ));
break;
}// switch
}// while
return GetTop( OPND );
}// EvaluateExpression
過程的巢狀呼叫
……
漢諾塔問題
……
佇列
定義
一種先進先出的線形表。只允許在表一端插入,在另一端刪除。
- 概念
隊尾rear:插入端,線性表的表尾。
隊頭front:刪除端,線性表的表頭。
FIFO(First In First Out)
ADT
ADT Queue
{
資料物件:D={ai|ai∈ElemSet,i=1,2,…,n,n≥0}
資料關係:R={<ai-1,ai>|ai,ai-1∈D,i=2,…,n}
約定:a1為佇列頭,an為佇列尾
基本操作:
InitQueue( &Q ); // 初始化空佇列
DestroyQueue( &Q ); // 銷燬佇列
ClearQueue( &Q ); // 清空佇列
QueueEmpty( Q ); // 佇列空?
QueueLength( Q ); // 佇列長度
GetHead( Q, &e ); // 取對頭元素
EnQueue( &Q, e ); // 入佇列
DeQueue( &Q, &e ); // 出佇列
QueueTraverse( Q, visit( )); // 遍歷
}//ADT Queue;
雙端佇列簡介
可以從兩端進行插入或者刪除操作的佇列。
佇列的表示和實現
兩種
- 鏈式儲存結構__鏈佇列;
- 順序儲存結構__迴圈佇列;
鏈佇列
型別說明
typedef struct Qnode {
QElemType data;
struct QNode *next;
}QNode,*QueuePtr; // 結點
typedef struct{
QueuePtr fornt; // 隊頭指標
QueuePtr rear; // 隊尾指標
}LinkQueue;
結點定義
typedef struct Qnode{
QElemType data;
struct QNode *Next;
}QNode
鏈佇列的基本操作
初始化
// 初始化一個空佇列
Status InitQueue( LinkQueue &Q )
{
Q.Front=Q.Rear=(QueuePtr)malloc(sizeof(QNode));
if(!Q.front)
{
return OVERFLOW;
}
Q.front->Next=NULL;
}//InitQueue
銷燬
// 銷燬佇列
Status DestroyQueue(LinkQueue &Q){
while(Q.Front){
QRear = Q.Front ->next;
free( Q.Front );
Q.Front= QRear
}
return OK;
}// DestroyQueue
入隊
// 入佇列
Stauts EnQueue( LinkQueue &Q, QEmemType e )
{
p=(QueuePtr)malloc(sizeof(QNode));
if( !p){return OVERFLOW; }//儲存分配失敗
p->data = e; p->next = null ;
Q.Rear->next = p;
Q.Rear=p;
return OK;
}
出隊
// 出佇列 3-3-12-1.swf
Status DeQueue( LinkQueue &Q, QElemType &e )
{
if(Q.Front==Q.rear)
return ERROR;
p = Q.Front->Next ;//p指向隊頭
e = p->data;//取隊頭元素值(可以直接用e完成對隊頭取值)
Q.Front->next = p->next;//頭結點指向原隊頭的下一個節點
if(Q.rear==p) //尾頭同指向,佇列就空了,釋放佇列
Q.rear=Q.front;
free( p );
return OK;
}// Dequeue
迴圈佇列
》 基本思想:把佇列設想成環形,讓sq[0]接在sq[M-1]之後,若rear+1==M,則令rear=0;
》實現:利用“模”運算
》入隊: rear=(rear+1)%M; sq[rear]=x;
》出隊: front=(front+1)%M; x=sq[front];
》隊滿、隊空判定條件
- 會存在判斷隊空和隊滿時,條件均為front==rear的情況
- 解決方案:少用一個元素空間
隊空:front= =rear
隊滿:(rear+1)%M= =front
約定以“佇列頭指標在佇列尾指標的下一位置上”作為佇列呈“滿”狀態的標誌,在同一位置則是空。
迴圈佇列的基本操作
基本模組說明
#define MAXQSIZE 100 //佇列最大長度
typedef struct
{
QElemType *Base; // pBase指向陣列名(通常靜態佇列都使用迴圈佇列)
int Front; // 頭指標,陣列下標,此處規定從零開始
int Rear; // 尾指標
}SqQueue;
初始化
Status InitQueue( SqQueue &Q ){
Q.Base = (QElemTYpe *)malloc( MAXQSIZE * sizeof( QElemType ));
if( !Q.Base ){
return OVERFLOW;}
Q.Front = Q.Rear = 0;
return OK;
}// InitQueue
佇列長度
int QueueLength( SqQueue Q )
{
return Q.Rear – Q.Front + MAXQSIZE) % MAXQSIZE;
}
3 入佇列
Status EnQueue( SqQueue &Q, QElemType e ){
if( (Q.Rear + 1)%MAXQSIZE == Q.Front ){
return ERROR;} // 佇列滿?
Q.Base[Q.Rear] = e;
Q.Rear = ( Q.Rear + 1 ) % MAXQSIZE;
}// EnQueue
出佇列
Status DeQueue( SqQueue &Q, QElemType &e ){
if( Q.Front == Q.Rear ){
return ERROR;} // 對列空?
e = Q.Base[Q.Front];
Q.Front = (Q.Front + 1) % MAXQSIZE;
return OK;
}// DeQueue;
佇列應用舉例
由事件驅動的程式
銀行業務模擬系統
- 顧客到達銀行
選擇一個人數最少的櫃檯排隊,如果該櫃檯沒有人,則直接開始辦理業務,並準備該顧客的業務完成事件。
如果未到下班時間,則準備下一個顧客到達銀行的事件。 - 顧客業務完成
顧客完成業務離開,下一個顧客開始辦理業務,並準備該佇列中下一個顧客的業務完成事件。
初始化事件佇列;
起始事件(第一個顧客到達事件)入隊;
迴圈,直至事件佇列空
事件出隊;
若為到達事件,則
選擇人數最少的櫃檯排隊;
若該櫃檯無人排隊,則業務完成事件入隊;
若未到下班時間,則下一個顧客到達事件入隊;
若為業務完成事件,則
顧客離開;
若該櫃檯還有人排隊,則準備下一個人的業務完成事件;