隊列相關習題及詳解(選擇題和綜合題) ——數據結構

隊列的基本概念

隊列的定義

隊列(Queue):隊列簡稱隊,也是一種操作受限的線性表,只允許在表的一端進行插入,而在表的另一端進行刪除。向隊列中插入元素稱爲入隊或進隊;刪除元素稱爲出隊或離隊。這和我們日常生活中的排隊是一致的,最早排隊的也是最早離隊的。其操作的特性是先進先出(First In First Out, FIFO),故又稱爲先進先出的線性表。

隊頭(Front):允許刪除的一端,又稱爲首隊
隊尾(Rear):允許插入的一端
空隊列:不含任何元素的空表

隊列常見的基本操作

InitQueue(&Q):初始化隊列,構造一個空隊列
QueueEmpty(Q):判斷隊列空,若隊列Q爲空返回true,否則返回false
EnQueue(&Q,x):入隊,若隊列Q未滿,將x加入,使之成爲新的隊尾
DeQueue(&Q,&x):出隊,若隊列非空,刪除隊頭元素,並用x返回
GetHead(Q,&x):讀隊頭元素,若隊列Q非空,則將隊頭元素賦值給x

需要注意的是,隊列是操作受限的線性表,所以,不是任何對線性表的操作都可以作爲隊列的操作。比如,不可以隨便讀取隊列中間的某個數據。

隊列的順序存儲結構

隊列的順序存儲

隊列的順序實現是指分配一塊連續的存儲單元存放隊列中的元素,並附設兩個指針front rear分別指示隊頭元素和隊尾元素的位置。設隊頭指針指向隊頭元素,隊尾指針指向隊尾元素的下一個位置

隊列的順序存儲類型可描述爲:

#define MaxSize 50
typedef struct{
    ElemType data[MaxSize];
    int front,rear;
} SqQueue;

在隊列的初始狀態時,有Q.front==Q.rear==0成立,該條件可以作爲判斷空的條件。但能否用Q.rear==MaxSize作爲隊列滿的條件呢?顯然不能,因爲有些情況隊列中只有一個元素,但仍滿足該條件。這時入隊出現“上溢出”,但這種溢出並不是真正的溢出,在data數組中依然存在可以存放元素的空位置,所以是一種“假溢出”。

循環隊列

前面已經指出了順序隊列的缺點,這裏我們引出循環隊列的概念。將順序隊列臆造爲一個環狀的空間,即把存儲隊列元素的表從邏輯上看成一個環,稱爲循環隊列當隊首指針Q.front=MaxSize-1後,再前進一個位置就可以自動到0,這可以利用除法取餘運算(%)來實現

初始時:Q.front=Q.rear=0;
隊首指針進1:Q.front=(Q.front+1)%MaxSize
隊尾指針進1:Q.rear=(Q.rear+1)%MaxSize
隊列長度:(Q.rear+MaxSize-Q.front)%MaxSize

爲了區分隊空還是隊滿的情況,有三種處理方式

1)犧牲一個單元來區分隊空和堆滿,入隊時少用一個隊列單元,這是一種較爲普遍的做法,約定以“隊頭指針在隊尾指針的下一個位置作爲隊滿的標識”
隊滿條件爲:(Q.rear+1)%MaxSize==Q.front
隊空條件爲:Q.front==Q.rear
隊列中元素的個數:(Q.rear=Q.front+MaxSize)%MaxSize

2)類型中增設表示元素個數的數據成員。這樣,則隊空的條件爲Q.size==0;隊滿的條件爲Q.size=MaxSize.這兩種情況都有Q.front==Q.rear

3)類型中增設tag數據成員,以區分堆滿還是隊空。tag等於0的情況下,若因刪除導致Q.front=Q.rear則隊爲空;tag等於1的情況下,若因插入導致Q.front=Q.rear則爲隊滿。

循環隊列的操作

1)初始化

void InitQueue(&Q){
    Q.rear=Q.front=0;           //初始化隊首、隊尾指針
}

2)判隊空



bool isEmpyt(Q){
    if(Q.rear==Q.front) return true;    //隊空條件
    else return false;
}

3)入隊


bool EnQueue(SqQueue &Q,ElemType x){
    if((Q.rear+1)%MaxSize==Q.front) return false;   //隊滿
    Q.data[Q.rear]=x;
    Q.rear=(Q.rear+1)%MaxSize;                  //隊尾指針加1取模
    return true;
}

4)出隊




bool DeQueue(SqQueue &Q,ElemType &x){
    if(Q.rear==Q.front) return false;           //隊空,報錯
    x=Q.data[Q.front];
    Q.front=(Q.front+1)%MaxSize;                //隊頭指針加1取模
    return true;
}

隊列的鏈式存儲結構

隊列的鏈式存儲

隊列的鏈式表示稱爲鏈隊列,它實際上是一個同事帶有隊頭指針和隊尾指針的單鏈表。頭指針向隊頭結點,尾指針指向尾結點,即單鏈表的最後一個結點(注意與順序存儲的不同)。隊列的鏈式存儲如圖所示

這裏寫圖片描述

棧的鏈式存儲類型可以描述爲

typedef struct          //鏈式隊列結點
{
    ElemType data;
    struct LinkNode *next;
}LinkNode;

typedef struct          //鏈式隊列
{
    LinkNode *front,*rear;      //隊列的隊頭和隊尾指針

}LinkQueue;

當Q.front==NULL且Q.rear=NULL時,鏈式隊列爲空

出隊時,首先判斷隊是否爲空,若不空,則去除隊頭元素,將其從鏈表中摘除,並讓Q.front指向下一個結點(若該結點爲最後一個結點,則置Q.front和Q.rear都爲NULL)入隊時,建一個新結點,將新結點插入到鏈表的尾部,並改讓Q.rear指向這個心插入的結點(若原隊列爲空隊,則令Q.front也指向該結點)

不難看出,不設頭結點的鏈式隊列在操作上往往比較麻煩,因此,通常將鏈式隊列設計成一個帶頭結點的單鏈表,這樣插入和刪除操作就統一了。

用單鏈表表示的鏈式隊列特別適合於數據元素變動比較大的情形,而且不存在隊列滿且產生溢出的問題。另外加入程序中要使用多個隊列,與多個棧的情形一樣,最好使用鏈式隊列,這樣就不會出現存儲分配不合理和“溢出”的問題。

鏈式隊列的基本操作

1)初始化

void InitQueue(LinkQueue &Q){
    Q.front=Q.rear=(LinkNode*)malloc(sizeof(LinkNode));//建立頭結點
    Q.front->next=NULL;             //初始爲空
}

2)判隊空

bool IsEmpty(LinkQueue Q){
    if(Q.front==Q.rear) return true;
    else return false;
}

3)入隊


void EnQueue(LinkQueue &Q,ElemType x){
    s=(LinkNode *)malloc(sizeof(LinkNode));
    s->data=x;s->next=NULL; //創建新結點,插入到鏈尾
    Q.rear->next=s;
    Q.rear=s;
}

4)出隊



boole DeQueue(LinkQueue &Q,ElemType &x){
    if(Q.front==Q.rear)     return false;//空隊
    p=Q.front->next;
    x=p->data;
    Q.front->next=p->next;
    if(Q.rear==p)
        Q.rear=Q.front;     //若原隊列中只有一個結點
    free(p);
}

習題部分

選擇題

第一題

棧和隊列的主要區別在於()
A. 它們的邏輯結構不一樣 B. 它們的存儲結構不一樣 C. 所包含的元素不一樣 D. 插入、刪除操作的限定不一樣

第二題

循環隊列存儲在數組A[0…n],則入隊時的操作爲()
A. rear=rear+1 B. rear=(rear+1)mod(n-1) C. rear=(rear+1)modn D. rear=(rear+1)mod(n+1)

第三題

若用數組A[0..5]來實現循環隊列,且當前rear和front的值分別爲1和5,當從隊列中刪除一個元素,再加入兩個元素後,rear和front的值分別爲()
A. 3和4 B. 3和0 C. 5和0 D. 5和1

第四題

已知循環隊列存儲在一維數組A[0…n-1],且隊列非空時,front和rear分別指向隊頭元素和隊尾元素。若初始時隊列爲空,且要求第一個進入隊列的元素存儲在A[0]處,則初始時front和rear的值分別是()

A. 0,0 B. 0,n-1 C. n-1,0 D. n-1,n-1

第五題

循環隊列放在以爲數組A[0…M-1]中,end1指向隊頭元素,end2指向隊尾元素的後一個位置。假設隊列兩段均可進行入隊和出隊操作,隊列中最多能容納M-1個元素。初始時爲空。下列判斷隊空和隊滿的條件中,正確的是()

A. 6 B. 4 C. 3 D. 2

第六題

最適合用作鏈隊的鏈表是()
A. 隊空:end1==en2; 隊滿:end1==(edn2+1)mod M
B. 隊空:end1==en2; 隊滿:end1==(edn1+1)mod (M-1)
C. 隊空:end2==(edn1+1)mod M 隊滿:end1==(edn2+1)mod M
D. 隊空:end1==(edn2+1)mod M 隊滿:end1==(edn1+1)mod (M-1)

第七題

最不適合用作鏈式隊列的鏈表是()
A. 只帶隊首指針的非循環雙鏈表 B. 只帶隊首指針的循環雙鏈表
C. 只帶隊尾指針的循環雙鏈表 D. 只帶隊尾指針的循環單鏈表


解答部分

第一題

棧和隊列的邏輯結構都是線性表,它們的存儲結構可能是順序的也可能是鏈式的,但不能說是它們的主要區別,C的道理也是一樣,只有D纔是棧和隊列的本質區別。不管是順序存儲還是鏈式存儲,棧和隊列都只嗯呢該順序存取,而向量數組是直接(隨機)存取

第二題

這道題需要注意的是,由於數組的下標範圍是0-n,所以數組的容量爲n+1
循環隊列中新元素入隊的操作是rear=(rear+1)MOD maxsize,本題中maxsize=n+1。因此入隊操作應爲rear=(rear+1)MOD(n+1)

第三題

循環隊列中,每刪除一個元素,隊首指針:front=(front+1)%6,每插入一個元素,隊尾指針:rear=(rear+1)%6。上述操作後,front=0,rear=3

第四題

根據題意,第一個元素進入隊列後存儲在A[0]處,此時front和rear值都爲0。入隊時由於要執行(rear+1)%n ,所以如果入隊後指針都指向0,則rear初值爲n-1,而由於第一個元素在A[0]中,插入操作只改變rear指針,所以front爲0不變。

第五題

end1指向隊頭元素,可知出隊的操作是先從A[edn1]讀數,然後end1再+1。end2指向對尾元素的後一個位置,可知入隊操作是先存數到A[end2],然後end2再加1。若把A[0]存儲第一個元素,當隊列初始時,入隊操作是把數據放到A[0],然後end2自增,即可知end2初值爲0;
而end1指向的是隊頭元素,隊頭元素的在數組A中的下標爲0,所以得知end1初值也爲0,可知隊空條件爲end1==end2;

然後考慮隊列滿時,因爲隊列最多能容納M-1個元素,假設隊列存儲在下標爲0到下標爲M-2的M-1個區域,隊頭爲A[0],隊尾爲A[M-2],此時隊列滿,考慮在這種情況下end1和end2的狀態,end1指向隊頭元素,可知end1=0,end2指向對尾元素的後一個位置,可知end2=M-2+1=M-1,所以可知隊滿的條件爲end1==(end2+1)mod M, 選A

最好還是畫圖看着題

第六題

由於對了需在雙端進行操作,選項C和D的鏈表顯然不太適合鏈隊。選項A的鏈表在完成進隊和出隊後還要修改爲循環的,對於隊列來講是多餘的。對於選項B,由於有首指針,適合刪除首結點;由於有尾指針,適合在其後面插入結點,故選B

第七題

由於非循環雙鏈表只帶隊首指針,可在執行入隊操作時需要修改隊尾結點的指針域,而查找隊尾結點需要O(n)的時間。BCD均可在O(1)的時間內找到隊首和隊尾。

綜合題

習題部分

第一題

如果希望循環隊列中的元素都能得到利用,則需設置一個標誌域tag,並以tag的值爲0或1來區分頭指針front和隊尾指針rear相同時的隊列狀態是“空”還是“滿”,試編寫與此結構相應的入隊和出隊算法

第二題

Q是一個隊列,S是一個空棧,實現將隊列中的元素逆置的算法

第三題

利用兩個棧S1/ S2模擬一個隊列,已知棧的4個運算定義如下:

Push(S,x);          //元素x入棧S
Pop(S,x);           //S出棧並將出棧的值賦給x
StackEmpty(S);      //判斷棧是否爲空
StackOverflow(S);   //判斷棧是否滿

那麼如何利用棧的運算來實現該隊列的3個運算(形參由做題這根據要求自己設計)

Enqueue;            //將元素x入隊
Dequeue;            //出隊,並將出隊元素存儲在x中
QueueEmpty;         //判斷隊列是否爲空

解答部分

第一題

在循環隊列的類型結構中,增設一個tag的整形變量,進隊時置tag爲1,出對時置tag爲0(因爲只有入隊操作可能導致隊滿,也只有出對操作可能導致隊空)。隊列Q初始時,tag=0、front=rear=0。這樣的隊列4要素如下:

隊空條件:Q.front=Q.rear 且Q.tag==0
隊滿條件:Q.front==Q.rear且Q.tag==1
進隊操作:Q.data[Q.rear]=x;Q.rear=(Q.rear+1)/MaxSize;Q.tag=1
出隊操作:x.Q.data[Q.front];Q.front=(Q.front+1)/MaxSize;Q.tag=0

1)設“tag”法的循環隊列入隊算法:

int EnQueue1(SqQueue &Q ,ElemType x){
    if(Q.front==Q.rear&&Q.tag==1)
        return 0;               //連個條件都滿足時則隊滿
    Q.data[Q.rear]=x;
    Q.rear=(Q.rear+1)%MaxSize;
    Q.tag=1;                    //可能隊滿
    return 1;
}

2)設“tag”法的循環隊列入隊算法:



int DeQueue1(SqQueue &Q,ElemType &x){
    if(Q.front==Q.rear&&Q.tag==0)
        return 0;               //兩個條件都滿足時則隊空
    x=Q.data[Q.front];
    Q.front=(Q.front+1)%MaxSize;
    Q.tag=0;                    //可能隊空
    return 1;
}

第二題

主要考察隊隊列和棧的特性和操作的理解。只是對隊列的一系列操作是不可能將其中的元素逆置的,而棧可以將入棧的元素逆序提取出來。所以,我們可以將隊列中的元素逐個地出隊列,入棧;全部入棧後再逐個出棧,如隊列。

void Inverser(Stack S,Queue Q){
    //本算法實現將隊列中的元素逆置
    while(!=QueueEmpty(Q)){
        x=DeQueue(Q);           //隊列中全部元素一次出隊
        Push(S,x);              //元素一次入棧
    }
    while(!StackEmpty(S)){
        Pop(S,x);               //棧中全部元素一次出棧
        EnQueue(Q,x);           //再入隊
    }
}

第三題

利用兩個棧S1和S2來模擬一個隊列,當需要向隊列中插入一個元素時,用S1來存放已輸入的元素,即S1執行入棧操作。當需要出隊時,則隊S2執行出棧操作。由於從棧中去除元素的順序是原順序的逆序,所以,必須先將S1中的所有元素全部出棧併入棧到S2中,再在S2中執行出棧操作,即可實現出隊操作,而在執行此操作前必須判斷S2是否爲空,否則會導致順序混亂。當棧S1和S2都爲空時,隊列爲空。

總結如下:
1)對S2的出棧操作用作出隊,若S2爲空,則先將S1中的鄋元素送入S2
2)對S1的入棧操作用作入隊,若S1滿,必須先保證S2爲空,才能將S1中的元素全部插入S2中。

入隊算法


int EnQueue(Stack S1,Stack S2,ElemType e){
    if(!StackOverflow(S1)){
        Push(S1,x);
        return 1;
    }
    if(StackOverflow(S1)&&!StackEmpty(S2)){
        printf("隊列滿\n");
        return 0;
    }
    if(StackOverflow(S1)&&StackEmpty(S2)){
        while(!=StackEmpty(S1)){
            Pop(S1,x);
            Push(S2,x);
        }
    }
    Push(S1,x);
    return 1
}

出隊算法



void DeQueue(Stack S1,Stack S2,ElemType &x){
    if(!StackEmpty(S2)){
        Pop(S2,x);
    }
    else if (StackEmpty(S1)){
        printf("隊列爲空\n");
    }
    else{
        while(!=StackEmpty(S1)){
            Pop(S1,x);
            Push(S2,x);
        }
        Pop(S2,x);
    }
}

判斷隊列爲空的算法:

int QueueEmpty(Stack S1,Stack S2){
    if(StackEmpty(S1)&&StackEmpty(S2))
        return 1;
    else
        return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章