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