佇列(Queues)

Download Report

Transcript 佇列(Queues)

第 7 章 佇列(Queues)
7.1 佇列簡介
圖形的問題多且複雜,有時需要堆疊來處理問題,有時
也需要佇列資料結構來解決問題,如最短路徑的搜尋。
佇列(Queues)的基本概念是:先進先出 。
類比:銀行客服,或速食店的服務,顧客排成一列,都是
先到的先服務。排成一列先進先出也就是佇列的特性。
佇列的資料結構:可以用來做為佇列的資料結構,基本上
有兩種,一種是陣列,另一種是鏈結 (linked list)。
首先, 考慮以陣列來設計佇列的資料結構如下。
typedef struct {
dataType a[MAXSIZE];
int front, rear;} queue;
其中 front 是指向陣列中儲存元素最前的一個,rear 指向
陣列中儲存元素最後的一個。
佇列的運算:佇列的運算有
InitializeQueue( ), IsQueueEmpty( ), IsQueueFull( ),
Enqueue( ), Dequeue( )。
如果陣列的 size 為 6,其中有2 個元素 x 與 y,front = 3
是指向陣列中儲存最前的一個元素 x,rear = 4 指向陣列
中儲存元素最後的一個元素 y,如下左圖所示:
front
rear
rear front
2 dequeues
0
1
2
x
y
3
4
5
0
1
2
x
y
3
4
5
經過兩次 dequeue 之後,front = 5,rear = 4,此時陣列已
經是 empty,如上右圖所示:
Queue 是 empty 的條件是 (rear + 1) mod MAXSIZE == front
陣列的 size 為 6,front = 3 是指向最前的元素 x,rear = 4
指向最後一個元素 y,如下左圖所示。將陣列視為一個
circular 情況,即第 5 個元素的下一個是第 0 個。
front
rear
rear
front
b
c
x
y
a
0
1
3
4
5
4 Eequeues
0
1
2
x
y
3
4
5
d
2
經過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
}}
方法 1:利用兩個 queue q1 與 q2。
輸入關羽,欲知其孫子是誰,可由父子關係表推知其子資料如
下,儲存於 q1:
關平
關興
關索
q1
由父子關係表知:關平無子,關興有子關统與關彝,關索無子。
q2的內容如下所示,即關羽之孫。
關统
關彝
q2
方法 2:利用一個 queue q 與 變數 count。
由父子關係表可推知關羽之子資料如下,儲存於 q,
又 count = 3。
關平
關興
關索
q
Dequeue 關平,count =2。由父子關係表知:關平無子,因此
沒有 Enqueue 動作,此時 q 的內容如下:
關興
關索
q
Dequeue 關興, count =1。由父子關係表知:關興有子關统與
關彝,將關统與關彝 Enqueue 至 q,此時 q 的內容如下:
關索
關统
關彝
q
Dequeue 關索, count =0。由父子關係表知:關索無子,因此
沒有 Enqueue 動作,此時 q 的內容如下:
關统
關彝
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 最上面的元素是最早放進去的,也就是最先要被拿走
的。
問題:如何利用佇列來模擬堆疊。