1. 程式人生 > >佇列相關習題及詳解(選擇題和綜合題) ——資料結構

佇列相關習題及詳解(選擇題和綜合題) ——資料結構

佇列的基本概念

佇列的定義

佇列(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;
}