260 likes | 356 Views
第 7 章 佇列 (Queues). 7.1 佇列簡介. 圖形的問題多且複雜,有時需要堆疊來處理問題,有時也需要佇列資料結構來解決問題,如最短路徑的搜尋。. 佇列 (Queues) 的基本概念是: 先進先出 。. 類比 :銀行客服,或速食店的服務,顧客排成一列,都是先到的先服務。排成一列 先進先出 也就是佇列的特性。. 佇列的資料結構 :可以用來做為 佇列 的資料結構,基本上有兩種,一種是陣列,另一種是鏈結 (linked list) 。. 首先, 考慮以陣列來設計 佇列 的資料結構如下。. typedef struct {
E N D
第 7 章 佇列(Queues) 7.1 佇列簡介 圖形的問題多且複雜,有時需要堆疊來處理問題,有時也需要佇列資料結構來解決問題,如最短路徑的搜尋。 佇列(Queues)的基本概念是:先進先出 。 類比:銀行客服,或速食店的服務,顧客排成一列,都是先到的先服務。排成一列先進先出也就是佇列的特性。
佇列的資料結構:可以用來做為佇列的資料結構,基本上有兩種,一種是陣列,另一種是鏈結 (linked list)。 首先, 考慮以陣列來設計佇列的資料結構如下。 typedef struct { dataType a[MAXSIZE]; int front, rear;} queue; 其中 front 是指向陣列中儲存元素最前的一個,rear 指向陣列中儲存元素最後的一個。 佇列的運算:佇列的運算有 InitializeQueue( ), IsQueueEmpty( ), IsQueueFull( ), Enqueue( ), Dequeue( )。
front rear x x y y 2 dequeues 0 0 1 1 2 2 3 3 4 4 5 5 rear front 如果陣列的 size 為 6,其中有2 個元素 x 與 y,front = 3 是指向陣列中儲存最前的一個元素 x,rear = 4 指向陣列中儲存元素最後的一個元素 y,如下左圖所示: 經過兩次 dequeue 之後,front = 5,rear = 4,此時陣列已經是 empty,如上右圖所示: Queue 是 empty 的條件是 (rear + 1) mod MAXSIZE == front
rear front front rear x y 4 Eequeues c x y b a d 0 1 2 3 4 5 0 1 2 3 4 5 陣列的 size 為 6,front = 3 是指向最前的元素 x,rear = 4 指向最後一個元素 y,如下左圖所示。將陣列視為一個 circular 情況,即第 5 個元素的下一個是第 0 個。 經過4次 Eequeue,加入 a,b,c,d 之後,front = 3,rear = 2,此時陣列已經是 full,如上右圖所示: Queue 是 empty 的條件是 (rear + 1) mod MAXSIZE = front,當 queue 是 full 時,亦滿足相同的條件,即 empty 與 full 無法區別。因此,我們需要調整 full 的條件,即當陣列差一個就全滿時,就是 full 的條件:(rear + 2) mod MAXSIZE == front
InitializeQueue ( ) : 將一 queue 的 front 設定為 0, rear 設為 -1。 其程式如下 : void InitializeQueue (queue *qp) { qp -> front = 0; qp -> rear = MAXSIZE – 1;// 或 -1 }
IsQueueEmpty( ) : 檢查一個 queue 是否為空, 即檢查 front 是否等於 (rear+1) mod MAXSIZE。 其程式如下 : int IsQueueEmpty(queue q) { return (q.front == (q.rear+1) mod MAXSIZE); }
IsQueueFull( ) : 檢查一個 queue 是否已滿, 即檢查 (rear +2) mod MAXSIZE 是否為 front。我們考慮的陣列是 circular 的情況。 其程式如下 : int IsQueueFull ( queue q ) { return ( (q.rear +2) mod MAXSIZE == q.front); }
Enqueue( item ) : 將 item 置於 queue 的最後面。 其程式如下 : void Enqueue ( queue *qp, dataType item) { pq->rear = (pq->rear + 1) mod MAXSIZE; qp ->a[ qp->rear ] = item; }
Dequeue( ) :拿走 queue 的最前面的元素, 同時送回該值. 其程式如下 : dataType Deque ( queue *qp ) { dataType item; item = qp ->a[ qp->front ]; qp->front = (qp->front + 1) mod MAXSIZE; return item; }
佇列的資料結構:鏈結 (linked list)。 以鏈結來設計佇列的資料結構如下。 typedef struct node{ dataType item; struct node *next;} Node; typedef struct Q{ Node *front, *rear; } queue; 佇列的運算:佇列的運算有 InitializeQueue( ), IsQueueEmpty( ), IsQueueFull( ), Enqueue( ), Dequeue( ), CleanQueue( )。
InitializeQueue ( ) : 將一 queue 的 front 與 rear 為 NULL。 其程式如下 : void InitializeQueue (queue *qp) { qp-> front = NULL; qp-> tail = NULL; }
IsQueueEmpty( ) : 檢查一個 queue 是否為空, 即檢查 front 是否等於 NULL。 其程式如下 : int IsQueueEmpty( queue *pq) { return (pq->front == NULL); }
IsQueueFull( ) : 檢查一個 queue 是否已滿, 其實是看主記憶体是否還有空間來執行 malloc( ) 指令,我們可以假設主記憶体夠大。 其程式如下 : int IsQueueFull ( queue *pq ) { return 0; }
Enqueue( item ) : 將 item 置於 queue 的最後面。 其程式如下 : void Enqueue ( queue *qp, dataType item) { Node *pn; pn = (Node *)malloc( sizeof( Node) ); // if (pn==NULL) pn->item = item; pn->next = NULL; if( pq->front == NULL) { pq->front = pn; pq->tail = pn; } else { (pq->rear)->next = pn; pq->rear = pn; } }
Dequeue( ) :拿走 queue 的最前面的元素, 同時送回該值。 其程式如下 : dataType Dequeue ( queue *qp ) { Node *pn; dataType item; item = qp ->front->item; pn = qp ->front; qp->front = (qp->front) ->next; if(qp->front == NULL) qp->rear = NULL; free( pn ); return item; }
CleanQueue( ) :將 queue 全部清乾淨。 其程式如下 : dataType CleanQueue( queue *qp ) { while( !IsQueueEmpty( pq ) ) Dequeue( pq ); }
7.2 父子關係 一個父子關係表如下:
問題:給一個人名 name,找出他所有的孫子。 方法 1:利用兩個 queue q1 與 q2,q1用來儲存所有的兒子名,再利用 q2 依第一個 q1 中兒子的資料來查尋孫子的資料。 因此,程式大約為 step 1: for loop—陣列中如果父名與 name 同,則將子名 Enqeueue 到第一個 q1。 step 2: 從 q1 每 Dequeue 一次,取得一個兒子的名字,就到父子關係表搜尋一次,將其所有兒子的名字 Enqueue 到 q2。直到 q1 為 empty. step3: 將 q2 所有資料列印出來即可。
方法 2:利用一個 queue ,加上一個變數 count 來查尋兒子的個數。 因此,程式大約為 step 1: count =0; step 2: for (i=0; i<=max; i++){ if( name == a[i][1]) { count ++; Enqueue(a[i][2]); } } step 3: while(count > 0){ name = Dequeue( ); count--; search(name);// look table for the sons of name. // enqueue the son’s name } }
關索 關平 關興 q1 關统 關彝 q2 方法 1:利用兩個 queue q1 與 q2。 輸入關羽,欲知其孫子是誰,可由父子關係表推知其子資料如下,儲存於 q1: 由父子關係表知:關平無子,關興有子關统與關彝,關索無子。q2的內容如下所示,即關羽之孫。
關索 關索 關平 關興 關興 q q 關统 關索 關彝 q 方法 2:利用一個 queue q 與 變數 count。 由父子關係表可推知關羽之子資料如下,儲存於 q, 又 count = 3。 Dequeue 關平,count =2。由父子關係表知:關平無子,因此沒有 Enqueue 動作,此時 q 的內容如下: Dequeue 關興, count =1。由父子關係表知:關興有子關统與關彝,將關统與關彝 Enqueue 至 q,此時 q 的內容如下:
關统 關彝 q Dequeue 關索, count =0。由父子關係表知:關索無子,因此沒有 Enqueue 動作,此時 q 的內容如下: 因 count =0,此時 q 的內容就是關羽所有孫子的資料。。
7.3 以堆疊來模擬佇列 一個佇列需要兩個堆疊 s1 與 s2 來處理佇列問題,我們使用堆疊的 operations 來達到佇列 operations 的需求。利用堆疊 s1 來儲存 queue 的資料,使得最先進來的元素放在堆疊 s1 的最上面,最後進來的元素放在堆疊 s1 的最下面。因此,我們需要堆疊 s2 來做調整的工作。 其程式如下 : typedef struct{ stack s1, s2} queue; void InitializeQueue (queue *qp) { InitializeStack(&(qp-> s1)); InitializeStack(&(qp-> s2)); }
int IsQueueEmpty( queue *pq) { return (IsStackEmpty(pq->s1)); } int IsQueueFull ( queue *pq ) { return (IsStackFull(pq->s1)); }
void Enqueue ( queue *qp, dataType item) { while( ! IsStackEmpty(pq->s1) ) { push( &(pq->s2), pop( &(pq->s1)) ); } push(&(pq->s1), item); while( ! IsStackEmpty(pq->s2) ) { push( &(pq->s1), pop( &(pq->s2)) ); } } 先將堆疊 s1 所有東西依相反順序放至堆疊 s2,再將 item 放到堆疊 s1 最下方,之後,再將堆疊 s2所有東西依相反順序放回堆疊 s1。由此動作,可知,堆疊最上面的元素是最早放進去的,也就是最先要被拿走的。
dataType Dequeue ( queue *qp) { return (pop(&(qp->s1))); } 堆疊 s1 最上面的元素是最早放進去的,也就是最先要被拿走的。 問題:如何利用佇列來模擬堆疊。