Transcript URL

アルゴリズムと
データ構造
第7回
キュー
前回の復習(1)
スタック

抽象データ型としてのスタック
スタックの先頭→
(top)
スタックの底→
(bottom)

a4
a3
a2
a1
単方向リストによるスタックの実現
init
an-1
an-2
a0
前回の復習(2)

element
配列によるスタックの実現
[0]
[1]
top
an-1(先頭)
an-2

スタックの利用例
 数式の計算
 逆ポーランド記法
a1
[N-1] a0(底)
前回の演習問題
問題1:
スタックSに次のような一連の操作を実行したとき、
スタックSの内容がどのようになるか示しなさい。
Push(S, a), Push(S, b), Push(S, c), Pop(S),
Pop(S), Push(S, d), Pop(S), Push(S, e).
練習問題
① Push(S,a)
② Push(S,b)
③ Push(S,c)
④ Pop(S)
c
⑤ Pop(S)
edb
⑥ Push(S,d)
⑦ Pop(S)
⑧ Push(S,e)
a
S
前回の演習問題
スタックSの概念図
element
[0] a0 (底)
[1] a1
問題2:
次のアルゴリズムpush(S, x)を
プログラムとして実現しなさい。
push(S, x)
{
if(top < max)
②
{
top ← top + 1
S[top] ← x;
①
}
else{
print(“Stack S over flows.”);
}
}
an-2
top
[max-1]
an-1(先頭)
ス
タ
ッ
ク
が
伸
び
る
方
向
スタックSの概念図
プログラム
#include <stdio.h>
#define max 256 /* スタックの最大サイズ */
struct stack /* 配列(構造体)によるスタック */
{
int top;
char element[max]; /*スタックに保存する
データは文字に限る*/
};
void push(struct stack *S, char x)
{
if(S->top < max)
{
S->top = S->top + 1;
S->element[S->top] = x;
}
else{
printf(“Stack S over flows.”);
}
}
element
[0] a0 (底)
[1] a1
an-2
top
[max-1]
an-1(先頭)
ス
タ
ッ
ク
が
伸
び
る
方
向
本日の内容
スタック
 抽象データ型としてのスタック
 単方向リストによるスタックの実現
 配列によるスタックの実現
 スタックの利用例
 ポーランド記法,逆ポーランド記法
今回の内容
キュー
 抽象データ型としてのキュー
 単方向リストによるキューの実現
 循環配列(リングバッファ)によるキューの実現
Part 2:
キュー(待ち行列、Queue)
英国では行列することを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 ):キューの先頭の要素を削除する
<同時にQの先頭の要素を返す場合もある>
例
末尾
↓
先頭
↓
Q
初期状態
Enq(A, Q)
A
Enq(B, Q)
B
Enq(C, Q)
C
Deq(Q); Deq(Q)
C B
Enq(Deq(Q), Q)
B
C
A
B A
A
単方向リストによるキューの実現
構造体
struct queue型
Queue型
キューの先頭へのポインタ
front
rear
a0
front->element
キューの先頭要素
キューの末尾へのポインタ
an-1
a1
rear->element
キューの末尾要素
rear->nextはNULL
キューの型定義とEnqueueのプログラム例
/* セルを表わす構造体の定義 */
struct cell
セルのデータ構造はリストのときと同じ。
{
int element;
ここではint型の要素としたが、どのよ
struct cell *next;
うな型でも良い。
};
…
main()
…
struct queue
先頭を指すfrontと末尾を指すrear
{
の2つのポインタから成る構造体で
struct cell *front;
キューを定義
struct cell *rear;
}
Enqのプログラム例
…
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型
を指すポインタ
S
front, rearともにNULL

関数enqueueにおいて、仮引数Sは、
struct queue 型を指すポインタ

struct queue Q;
struct queue
型の変数Q
Q.front =Q.rear =NULL;
…
…
void enqueue(init x, struct queue *S)
Q
front
rear
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));
Enqの実現(単方向リスト版)
Enq(x, Q) [void enqueue(int x, struct queue *S)]

① 新しいセル用のメモリを確保し, pはそのセルを指すポイン
タとする
S
② Sが指しているキューQが空でなければ、 S->rear->nextが
新しいセルを指す(pを代入)
②if(S ->rear != NULL) S ->rear->next = p;
Q
front
a0
an-1
a1
rear
p
Enqの実現(単方向リスト版)

Enq(x, Q) [void enqueue(int x, struct queue *S)]
① 新しいセル用のメモリを確保し, pはそのセルを指すポイ
ンタとする
S
② Sが指しているキューQが空でなければ、 S->rear->next
が新しいセルを指す(pを代入)
③ S->rearが新しいセルを指す(pを代入)
Q
front
a0
an-1
a1
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が新しいセル
を指す
S
(pを代入)
④ if(S ->front == NULL) S ->front = p;
S
Q
front
rear
Q
p
front
rear
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
Deqのプログラム例
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
Q
front
rear
① if(S->front == NULL)
{
printf(“Error: Queue is empty. ¥n);
exit(1);
}
Deqの実現(単方向リスト版)
Deq(Q)[void dequeue(struct queue *S)]

…
キューQが空ではない場合は
② qにS->frontを代入する(=前の先頭要素へのポインタをqに保持しておく)
S
q
② q = S->front;
a0
front
rear
Q
a1
an-1
Deqの実現(単方向リスト版)
Deq(Q)[void dequeue(struct queue *S)]

…
キューQが空ではない場合は
② qにS->frontを代入する(=前の先頭要素へのポインタをqに保持しておく)
③ S->frontを前の先頭要素の次(=前の2番目)へのポインタとする
S
q
a0
front
rear
Q
a1
③ S->front = S->front->next;
an-1
Deqの実現(単方向リスト版)
Deq(Q)[void dequeue(struct queue *S)]

…
キューQが空ではない場合は
② qにS->frontを代入する(=前の先頭要素へのポインタをqに保持してお
く)
③ S->frontを前の先頭要素の次(=前の2番目)へのポインタとする
④ 削除するセルの領域を解放
S
q
④ free(q);
a0
front
rear
Q
a1
an-1
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
Deqの実現(単方向リスト版)
Deq(Q)[void dequeue(struct queue *S)]

・いずれの場合も先頭要素が削除される
元のキューQが要素
1個だった場合
元のキューQが要素2個以上だった場合
S
S
a1
front
rear
front
Q
rear
Q
an-1
配列によるキューの実現
element
[0]
[1]
front p
rear k
[p] a0
a1
[k] an-1
[N-1]
デ
ー
タ
が
あ
る
部
分
キューの型の定義 (配列版)
#define N 7
struct QUEUE/* 構造体queueの定義 */
{
int
element[N];
int
front;
int
rear;
先頭と末尾の要素の位置
};
を格納するためのメンバー
配列によるキューの実現
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]
Enq,Deqを繰り返すとどうなるか?

N=7の場合
element
[0]
Enq,Deqを繰り返していく
と,データ部分は一方向に
移動し,ついには,配列の
端からはみ出してしまう
⇒これを防ぐ手法の一つが,
循環配列
[1]
[2]
front 3
[3] A
[4] B
[5] C
rear 6
[6] D
循環配列(Ring Buffer)
element[N-1]
element[0]
rear
an-1
…
a1
a0
front
循環配列(Ring Buffer)
front
1
element
[0]
element
[0]
[1] A
[1]
[2] B
rear
4
[3] C
front
3
[4] D
[5]
[6]
[1] H
[2]
[2]
[3] C
[3]
[4] D
rear
6
rear
1
element
[0] G
[5] E
[6] F
front
5
[4]
[5] E
[6] F
循環配列の問題点
このままでは,「キューがFULLの状態」
と「キューが空の状態」の区別がつかな
い
[0]
キューがFULLの状態

⇒rearの1つ後ろがfront
一見問題無いように思えるが…
rear
front
an-1
a0
a1
[N-1]
循環配列の問題点(続き)
要素1つの状態
ここからDeqを行うと
キューは空になる
キューが空の状態
frontとrearの関係が,
キューがFULLの時と同じ!
[0]
[0]
front [1]
an-1
[N-1]
[1]
rear
Deq
front
[N-1]
rear
解決方法

「キューがFULLの状態」と「キューが空の状態」
をどう区別するか?

方法1: FULLの状態と空の状態を区別するためのフ
ラグ(キューが空かどうかを表わす変数など)を用意
する

方法2: 待ち行列が配列いっぱいにならないようにす
る
etc.
解決方法
方法2: 待ち行列が配列いっぱいにならないようにする例
この状態をFULLとする。
[0]
an-1
rear
この状態を空とする。
[0]
front
front
a0
a1
[N-1]
[N-1]
rear
まとめ
要素の追加
要素の削除
リスト
どこでも可
どこでも可
スタック
先頭
先頭
キュー
末尾
先頭
代表的な操作
Insert
Delete
Push
Pop
Enq
Deq
演習問題
問題1:
次のような一連の操作を実行した後のキューQの内容を
示しなさい。
Enq(Q,a), Enq(Q,b), Enq(Q,c), Deq(Q), Deq(Q),
Enq(Q,d), Deq(Q), Enq(Q,e).
演習問題
問題2:
アルゴリズムEnq(Q,x)をプログラムとして実現しなさい。
Enq(Q,x){
if (number < max){
number ← number+1;
tail ← (tail mod max) + 1;
Q[tail] ← x;
}
else{
print(“Queue Q over flows.”)
}
}
演習問題
名前と学籍番号をご記入のうえ、解答用紙(A4)を提出する
提出先:
工学部電子情報実験研究棟5階
NO.5506室のドアのポストに入れてください
締め切り時間:
来週月曜日(5月26日) 午前9時まで