データ構造とアルゴリズム 第6回 キュー ~ データ構造(2)~ 1 スタック ユニークな回答と多かった回答 ユニーク編 ・自分の家の冷蔵庫(新しいものばかり食べています)。 ・ティッシュを間違って2枚取り出したとき使わなかった1枚は再び 突っ込みます。次に使うときにはそれを使うのでスタックでしょう か。 ・小学校でよく使う上から入れてどんどんためていくファイル。 ・メジャー。 多数編 ・箪笥の服 ・レポートの提出 ・銃(ハンドガン)のマガジン ・食器 ・コピー機の紙 2 キュー ユニークな回答と多かった回答 ユニーク編 ・学校:基本的に先に入った人から出ていく(卒業していく) ・観覧車 ・髪の毛:先に生えたところから切られるから ・「便通」←食べたものは下から出る(笑) 多数編 ・自動販売機 ・スーパー(コンビニを含む)商品の品だし ・シャープペンシル ・エスカレーター ・行列 3 キュー(待ち行列、Queue) 4 本日の内容:キュー キュー 抽象データ型としてのキュー 単方向リストによるキューの実現 循環配列(リングバッファ)によるキューの実現 英国では行列することをqueueingという。 The verb queue means to form a line, and to wait for services. Queue is also the name of this line. 5 待ち行列 レジ A君の レポート B君の レポート C君の レポート 6 キュー (Queue) キュー:要素の挿入がいつも一方の端( ) からしかできず,要素の削除はその反対の端 ( )からしかできないリスト 先に入れた要素ほど,先に出る 別名 先入れ先出しリスト (first-in first-out) 先頭 ↓ a1 末尾 ↓ a2 a3 a4 7 抽象データ型としてのキュー 抽象データ型: データ構造+操作 キュー 操作 要素の挿入 要素を 並べたもの ( ) 要素の取出し ( ) 8 キューの操作 CREATE(Q ) TOP(Q ) 9 キューの操作 Enq (x, Q ):要素xをキューQの( Deq (Q ):キューの( )に入れる. )の要素を削除する <同時にSの先頭の要素を返す場合もある> 10 例 末尾 ↓ Q 先頭 ↓ 初期状態 Enq(A, Q) Enq(B, Q) Enq(C, Q) Deq(Q) Enq(Deq(Q), Q) 11 例 末尾 ↓ 先頭 ↓ Q 初期状態 Enq(A, Q) A Enq(B, Q) B A Enq(C, Q) C B A Deq(Q) C B A Enq(Deq(Q), Q) B C 12 単方向リストによるキューの実現 構造体 struct queue型 Queue型 front rear a0 front->element キューの先頭要素 an-1 a1 rear->element キューの末尾要素 rear->nextは NULL 13 キューの型定義とEnqueueのプログラム例(p38) /* セルを表わす構造体の定義 struct cell { int element; struct cell *next; }; … main() … struct queue { struct cell *front; struct cell *rear; } */ セルのデータ構造はリストのときと同じ。 ここではint型の要素としたが、どのような 型でも良い。 先頭を指すfrontと末尾を指すrearの2つのポ インタから成る構造体でキューを定義 14 Enqのプログラム例(p38) … Main() { struct queue Q; Q.front =Q.rear =NULL; … } void enqueue(init x, struct queue *S) { struct cell *p; p=(struct cell *)malloc(sizeof(struct cell)); if(S ->rear != NULL) S ->rear->next = p; S ->rear = p; if(S ->front == NULL) S ->front = p; S ->rear->element = x; S ->rear->next = NULL; return; } 15 Enqの実現(単方向リスト版) 初期状態 struct queue型 を指すポインタ S front, rearともにNULL 関数enqueueにおいて、仮引数Sは、 struct queue 型を指すポインタ struct queue Q; struct queue型 の変数Q Q front rear Q.front =Q.rear =NULL; … … 教科書では、両方とも Qなので混乱する。 スライドでは、ポインタ をSとしておく void enqueue(init x, struct queue *S) 16 Enqの実現(単方向リスト版) Enq(x, Q) [void enqueue(int x, struct queue *S)] ①新しいセル用のメモリを確保し, S Q front a0 an-1 a1 rear p ① p = (struct cell *)malloc(sizeof(struct cell)); 17 Enqの実現(単方向リスト版) Enq(x, Q) [void enqueue(int x, struct queue *S)] ① 新しいセル用のメモリを確保し, pはそのセルを指すポインタとする ② Sが指しているキューQが S ②if(S ->rear != NULL) S ->rear->next = p; Q front a0 an-1 a1 rear p 18 Enqの実現(単方向リスト版) Enq(x, Q) [void enqueue(int x, struct queue *S)] ① 新しいセル用のメモリを確保し, pはそのセルを指すポインタとする ② Sが指しているキューQが空でなければ、 S->rear->nextが新しいセル を指す(pを代入) S ③ Q front a0 an-1 a1 rear ③ S ->rear = p; p 19 Enqの実現(単方向リスト版) Enq(x, Q) [void enqueue(int x, struct queue *Q)] ① 新しいセル用のメモリを確保し, pはそのセルを指すポインタとする ② Sが指しているキューQが空でなければ、 S->rear->nextが新しいセルを 指す(pを代入) ③ S->rearが新しいセルを指す(pを代入) ④ Sが指しているキューQが空ならば、 ④ if(S ->front == NULL) S ->front = p; S S Q front rear Q p front rear 20 Enqの実現(単方向リスト版) Enq(x, Q) [void enqueue(int x, struct queue *Q)] … ④ Sが指しているキューQが空ならば、 S->frontが新しいセルを指す (pを代入) S ⑤新しいセルに ⑥新しいセルは Q front a0 an-1 a1 rear ⑤ S ->rear->element = x; ⑥ S ->rear->next = NULL; p 21 Deqのプログラム例(p38) void dequeue(struct queue *S) { struct cell *q; if(S->front == NULL) { printf(“Error: Queue is empty. ¥n);exit(1); } else { q = S->front; S->front = S->front->next; free(q); } if(S->front == NULL) S->rear = NULL; return; } 22 Deqの実現(単方向リスト版) Deq(Q)[void dequeue(struct queue *S)] Sが指すQのfront がNULLならキューが空 ① エラーメッセージを表示して関数を終了 S Q front ① if(S->front == NULL) { printf(“Error: Queue is empty. ¥n); exit(1); } rear 23 Deqの実現(単方向リスト版) Deq(Q)[void dequeue(struct queue *S)] … キューQが空ではない場合は ② qにS->frontを代入する(= S q ② q = S->front; a0 front rear ) a1 an-1 Q 24 Deqの実現(単方向リスト版) Deq(Q)[void dequeue(struct queue *S)] … キューQが空ではない場合は ② qにS->frontを代入する(=前の先頭要素へのポインタをqに保持しておく) ③ S->frontを S q a0 front rear Q a1 an-1 ③ S->front = S->front->next; 25 Deqの実現(単方向リスト版) Deq(Q)[void dequeue(struct queue *S)] … キューQが空ではない場合は ② qにS->frontを代入する(=前の先頭要素へのポインタをqに保持しておく) ③ S->frontを前の先頭要素の次(=前の2番目)へのポインタとする ④ 削除するセルの領域を解放 S q ④ free(q); a0 a1 an-1 front rear Q 26 Deqの実現(単方向リスト版) Deq(Q)[void dequeue(struct queue *S)] キューQが要素1個だった場合は (= S->front がNULLの場合は) ⑤ キューQは空とする( ) ⑤ if(S->front == NULL) S->rear = NULL; q S S a0 front front rear Q rear Q 27 Deqの実現(単方向リスト版) Deq(Q)[void dequeue(struct queue *S)] ・いずれの場合も先頭要素が削除される ・教科書p38のプログラムはvoid関数なので、戻り値はなし 元のキューQが要素 1個だった場合 元のキューQが要素2個以上だった場合 S S a1 front rear an-1 front Q rear Q 28 配列によるキューの実現 element [0] [1] front p [p] a0 a1 rear k [k] an-1 [N-1] デ ー タ が あ る 部 分 29 キューの型の定義 (配列版) #define N 7 struct QUEUE/* 構造体queueの定義 */ { int element[N]; int front; int rear; 先頭と末尾の要素の位置を }; 格納するためのメンバー 30 配列によるキューの実現 element [0] (キューの先頭の要素を 返す場合もある) [1] Deq Enq front p rear k [p] a0 [p+1] a1 [k] an-1 [k+1] [N-1] 31 Enq,Deqを繰り返すとどうなるか? N=7の場合 element [0] Enq,Deqを繰り返していくと, データ部分は一方向に移動 し,ついには,配列の端から はみ出してしまう ⇒これを防ぐ手法の一つが, [1] [2] front 3 [3] A [4] B [5] C rear 6 [6] D 32 循環配列(Ring Buffer) element[N-1] element[0] rear an-1 … a1 a0 front 33 循環配列(Ring Buffer) front 1 rear 4 element [0] element [0] [1] A [1] [2] B [2] [2] [3] C [3] [4] D [4] D [4] [5] [5] E [6] F [3] C [6] front 3 rear 6 rear 1 front 5 element [0] G [1] H [5] E [6] F 34 循環配列の問題点 このままでは,「 「 」と 」の区別がつかない キューがFULLの状態 [0] ⇒ 一見問題無いように思えるが… rear front an-1 a0 a1 [N-1] 35 循環配列の問題点(続き) ここからDeqを行うとキュー は空になる frontとrearの関係が, キューがFULLの時と同じ! [0] [0] front [1] an-1 [N-1] [1] rear Deq rear front [N-1] 36 解決方法 「キューがFULLの状態」と「キューが空の状態」を どう区別するか? 方法1: FULLの状態と空の状態を区別するためのフラ グ(キューが空かどうかを表わす変数など)を用意する 方法2: 待ち行列が配列いっぱいにならないようにする etc. 37 解決方法 方法2: 待ち行列が配列いっぱいにならないようにする例 ( ) [0] an-1 rear ( ) [0] rear front front a0 a1 [N-1] [N-1] 38
© Copyright 2024 ExpyDoc