データ構造とアルゴリズム 第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) キュー:要素の挿入がいつも一方の端(末尾) からしかできず,要素の削除はその反対の端( 先頭)からしかできないリスト 先に入れた要素ほど,先に出る 別名 先入れ先出しリスト FIFO (first-in first-out) 先頭 ↓ a1 末尾 ↓ a2 a3 a4 7 抽象データ型としてのキュー 抽象データ型: データ構造+操作 キュー 操作 要素を 並べたもの 要素の挿入 Enqueue, Enq 要素の取出し Dequeue, Deq 8 キューの操作 CREATE(Q ) TOP(Q ) ENQUEUE( x, Q ) ( Enq ( x, Q ) ) DEQUEUE(Q ) ( Deq (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)] ① 新しいセル用のメモリを確保し, pはそのセルを指すポインタとする 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->rear->nextが新しいセルを 指す(pを代入) 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 ③ S->rearが新しいセルを指す(pを代入) 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が空ならば、 S->frontが新しいセルを指す (pを代入) ④ 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 ⑤新しいセルに要素xを代入 ⑥新しいセルは最後尾なので、そのnextはNULL Q front a0 an-1 a1 rear ⑤ S ->rear->element = x; ⑥ S ->rear->next = NULL; p x 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を代入する(=前の先頭要素へのポインタをqに保持しておく) 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を前の先頭要素の次(=前の2番目)へのポインタとする 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は空とする(S->rearもNULLにする) ⑤ 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] Deq frontが1つ下がる (キューの先頭の要素を 返す場合もある) [1] front p p+1 Enq rearが1つ下がり,そこに 新しいデータが入る rear k k+1 [p] a0 [p+1] a1 [k] an-1 [k+1] x [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の状態」と 「キューが空の状態」の区別がつかない キューがFULLの状態 ⇒rearの1つ後ろがfront 一見問題無いように思えるが… [0] rear front an-1 a0 a1 [N-1] 35 循環配列の問題点(続き) 要素1つの状態 キューが空の状態 ここから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: 待ち行列が配列いっぱいにならないようにする例 この状態をFULLとする。 [0] an-1 rear この状態を空とする。 [0] rear front front a0 a1 [N-1] [N-1] 38
© Copyright 2025 ExpyDoc