データ構造とアルゴリズム

データ構造とアルゴリズム
第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