1. 程式人生 > >數據結構(四)隊列

數據結構(四)隊列

node 兩種 使用 如果 ini 否則 刪除 棧和隊列 element

隊列

隊列的定義及基本運算

  是一種後進先出的數據結構,在實際問題中還經常使用一種“先進先出”的數據結構: 即插入在表一端進行,而刪除在表的另一端進行,將這種數據結構稱為隊或隊列,把允許插入的一端叫隊尾(rear) ,把允許刪除的一端叫隊頭(front)。

隊列的存儲實現及運算實現

與線性表、棧類似,隊列也有順序存儲和鏈式存儲兩種存儲方法。

基本操作:

(1) InitQueue(&Q):初始化操作。設置一個空隊列。

(2) IsEmpty(Q):判空操作。若隊列為空,則返回 TRUE,否則返回 FALSE。

(3) IsFull(Q):判滿操作。若隊列為滿,則返回 TRUE,否則返回 FALSE。

(4) EnterQueue(&Q,x):進隊操作。在隊列 Q 的隊尾插入 x。操作成 功,返回值為 TRUE,否則返回值為 FALSE。

(5) DeleteQueue(&Q,&x):出隊操作。使隊列 Q 的隊頭元素出隊,並 用 x 帶回其值。操作成功,返回值為 TRUE,否則返回值為 FALSE。

(6) GetHead(Q,&x):取隊頭元素操作。用 x 取得隊頭元素的值。操 作成功,返回 TRUE,否則返回值為 FALSE。

(7) ClearQueue(&Q):隊列置空操作。將隊列 Q 置為空隊列。

(8) DestroyQueue(&Q): 隊列銷毀操作。釋放隊列的空間。

1.順序隊列

循環隊列的類型定義如下:

#define MAXQSIZE  100     //大隊列長度 
typedef struct {   
    QElemType  *base;     //動態分配存儲空間   
    int  front;           //頭指針,若隊列不空,指向隊列頭元素   
    int  rear;            //尾指針,若隊列不空,指向隊列尾元素的下一個位置 
} SqQueue; 

(1)入隊:

int 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;     
    return OK; 
}

(2)出隊:

int 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; 
} 

(3)求循環隊列元素個數:

int QueueLength(SqQueue Q){ 
    return (Q.rear-Q.front+MAXQSIZE) %MAXQSIZE;    
}
2.鏈隊列

  鏈式存儲的隊稱為鏈隊列。和鏈棧類似,用單鏈表來實現鏈隊列,根據隊的先進先出原 則,為了操作上的方便,分別需要一個頭指針和尾指針。隊頭指針始終指向頭結點,隊尾指針指向當前最後一個元素。空的鏈隊列的隊頭指針和隊尾指針均指 向頭結點。

  鏈隊列的形式描述如下:

typedef struct QNode { // 結點類型     
    QElemType   data;     
    struct QNode  *next; 
} QNode, *QueuePtr; 
typedef struct {      //鏈隊列類型     
    QueuePtr  front;  //隊頭指針     
    QueuePtr  rear;   //隊尾指針 
} LinkQueue;

  定義一個指向鏈隊列的指針:LinkQueue Q;

下面是鏈隊列的基本運算的實現。

(1)初始化

 int InitQueue(LinkQueue * Q)  
 { /* 將 Q 初始化為一個空的鏈隊列 */  
     Q->front=(LinkQueueNode *)malloc(sizeof(LinkQueueNode));   
     if(Q->front!=NULL)   
         {   
             Q->rear=Q->front;    
             Q->front->next=NULL;     
                 return(TRUE);   
         }  
     else   return(FALSE);    /* 溢出!*/ 
 } 

(2)入隊

int EnQueue (LinkQueue &Q, QElemType e) { 
    QNode *p; 
    p = (QNode *)malloc(sizeof(QNode)); 
    p->data = e;    
    p->next = NULL; 
    Q.rear->next = p;     Q
    .rear = p;    
    return OK; 
} 

(3)出隊

int DeQueue (LinkQueue &Q, QElemType &e) {
    if (Q.front == Q.rear)  return ERROR; //隊空,出隊失敗   
    p = Q.front->next;    
    e = p->data;                       //隊頭元素放 e 中   
    Q.front->next = p->next;   
    if(Q.rear==p)   Q.rear= Q.front;   //只有一個元素時,此時還要修改隊尾指針 
    free (p);
    return OK;  
}

3.除了棧和隊列之外,還有一種限定性數據結構是雙端隊列。

(1)雙端隊列:可以在雙端進行插入和刪除操作的線性表。

(2)輸入受限的雙端隊列:線性表的兩端都可以輸出數據元素,但是只能在一端輸入數 據元素。

(3)輸出受限的雙端隊列:線性表的兩端都可以輸入數據元素,但是只能在一端輸出數 據元素。

隊列的順序存儲(循環隊列)

1. 順序隊列的假溢出現象

隊列的一種順序存儲稱為順序隊列。與順序棧類似,在隊列的順序存儲結構中,用一組地址連續的存儲單元依次存放從隊頭到隊尾的元素,如一維數組 Queue[MAXSIZE]。

由於隊列中隊頭和隊尾的位置都是動態變化的,因此需要附設兩個指針 front 和 rear。

front:指示隊頭元素在數組中的位置;

rear:指示真實隊尾元素相鄰的下一個位置。

  初始化隊列時,令 front = rear =0;入隊時,直接將新元素送入尾指針 rear 所指的單元, 然後尾指針增 1;出隊時,直接取出隊頭指針 front 所指的元素,然後頭指針增 1。顯然,在 非空順序隊列中,隊頭指針始終指向當前的隊頭元素,而隊尾指針始終指向真正隊尾元素後 面的單元。當 rear==MAXSIZE 時,認為隊滿。但此時不一定是真的隊滿,因為隨著部分元 素的出隊,數組前面會出現一些空單元。由於只能在隊尾入隊,使得上述 空單元無法使用。把這種現象稱為假溢出,真正隊滿的條件是 rear - front=MAXSIZE

2. 循環隊列

  為了解決假溢出現象並使得隊列空間得到充分利用,一個較巧妙的辦法是將順序隊列的數組看成一個環狀的空間,即規定最後一個單元的後繼為第一個單元,我們形象地稱之為循環隊列。假設隊列數組為 Queue[MAXSIZE],當 rear+1=MAXSIZE 時,令 rear=0,即可求得 最後一個單元 Queue[MAXSIZE-1]的後繼:Queue[0]。更簡便的辦法是通過數學中的取模(求 余)運算來實現:rear=(rear+1)mod MAXSIZE,顯然,當 rear+1=MAXSIZE 時,rear=0, 同樣可求得最後一個單元 Queue[MAXSIZE-1]的後繼:Queue[0]。所以,借助於取模(求余) 運算,可以自動實現隊尾指針、隊頭指針的循環變化。進隊操作時,隊尾指針的變化是:rear= (rear+1)mod MAXSIZE ;而出隊操作時,隊頭指針的變化是:front=(front+1)mod MAXSIZE。 下圖給出了循環隊列的幾種情況。

【循環隊列判空判滿問題】

  與一般的非空順序隊列相同,在非空循環隊列中,隊頭指針始終指向當前的隊頭元素,而隊尾指針始終指向真正隊尾元素後面的單元。在下圖 (c)所示 循環隊列中,隊列頭元素是 e3,隊列尾元素是 e5,當 e6、e7和 e8相繼入隊後,隊列空間均被占滿,如上圖 (b)所示, 此時隊尾指針追上隊頭指針,所以有:front =rear。反之,若 e3、e4 和 e5相繼從上圖 (c)的 隊列中刪除,則得到空隊列,如上圖 (a)所示,此時隊頭指針追上隊尾指針,所以也存在關 系式:front = rear。可見,只憑 front = rear 無法判別隊列的狀態是“空”還是“滿”。

兩種處理方法

  一種方法是少用一個元素空間。當隊尾指針所指向的空單元 的後繼單元是隊頭元素所在的單元時,則停止入隊。這樣一來,隊尾指針永遠追不上隊頭指 針,所以隊滿時不會有 front =rear。現在隊列“滿”的條件為(rear+1)mod MAXSIZE=front。 判隊空的條件不變,仍為 rear=front。

另一種是增設一個標誌量的方法,以區別隊列是“空” 還是“滿”。主要介紹損失一個存儲空間以區分隊列空與滿的方法。

循環隊列的類型定義
#define MAXSIZE 50  /*隊列的最大長度*/ 
typedef struct
{  
    QueueElementType  element[MAXSIZE];  /* 隊列的元素空間*/  
    int  front;  /*頭指針指示器*/  
    int  rear ;  /*尾指針指示器*/ 
}SeqQueue;
循環隊列的基本操作

(1) 初始化操作

void InitQueue(SeqQueue * Q) 
{  /* 將*Q 初始化為一個空的循環隊列 */ 
 Q->front=Q->rear=0; 
}

(2) 入隊操作

int EnterQueue(SeqQueue *Q, QueueElementType x) 
{  /*將元素 x 入隊*/ 
    if((Q->rear+1)%MAXSIZE==Q->front)  /*隊列已經滿了*/ 
        return(FALSE); 
    Q->element[Q->rear]=x; 
    Q->rear=(Q->rear+1)%MAXSIZE;  /* 重新設置隊尾指針 */ 
    return(TRUE);  /操作成功/ 
} 

(3)出隊操作

int DeleteQueue(SeqQueue *Q, QueueElementType * x) 
    { /*刪除隊列的隊頭元素,用 x 返回其值*/ 
        if(Q->front==Q->rear)  /*隊列為空*/ 
            return(FALSE); 
        *x=Q->element[Q->front]; 
        Q->front=(Q->front+1)%MAXSIZE;  /*重新設置隊頭指針*/ 
        return(TRUE);  /*操作成功*/ 
    } 

  這裏采用了第一種處理假溢出問題的方法。如果采用第二種方法,則需要設置一個標誌 量 tag。初始化操作即產生一個空的循環隊列,此時 Q->front = Q->rear=0,tag=0;當空 的循環隊列中有第一個元素入隊時,則 tag=1,表示循環隊列非空;當 tag=1 且 Q->front=Q->rear 時,表示隊滿。

數據結構(四)隊列