今回の学習項目です。
キューとは次の条件を満たすデータ構造です:
キューは、追加と取り出し方法が制限された「リスト構造」とみることもできます。
キューについて学ぶことは次の3点です。
一つの例は、優先度付きプロセスの処理(優先度付きキュー)です。 これは、例えて言えば、「窓口に並んだ列でも払ったお金によって並ぶ順番を変えられる」、というようなシステムです。
また人工知能で探索アルゴリズムとして有名なA*アルゴリズムもキューの応用とみなせます。将棋やオセロのようなゲームにおいて、現在の盤面から可能な「手」によってどのような盤面が生じるかを求め、それらを評価して、次の盤面(手)を選ぶという場合に用いられる方式です。 これは自分の手だけではなく相手の手によって盤面が変化し、しかもその評価が動的に(ダイナミックに)変わる、という性質があります。これは実質的には優先度付きプロセスの処理とほぼ同じものといえます。
最初空っぽのキューに対して次の操作を行ったときのキューの状態を今の説明にしたがって表しなさい:
変数qに空っぽのキューを代入したものとします。 ここで、キュー(q)に対しデータ(x)を入れる操作enqueueを q.enqueue(x) と表すこととします。また、キュー(q)からデータを取り出す操作 dequeueを q.dequeueで表すこととします。 この記法を用いて課題1-1のそれぞれの操作を表しなさい。
キューをRubyのクラスとして定義してみましょう。初めの試みとして以下のように定義することを考えます。
class Queue def initialize() @data = [] # データの記憶用の配列 @head = 0 # 先頭のデータの位置 @tail = -1 # 最後尾のデータの位置 @max = 10 # 記録できるデータの最大個数 @number = 0 # 記録されているデータの個数 end # initialize def enqueue(x) @tail += 1 @number += 1 @data[@tail] = x end # def of enqueue def dequeue ans = @data[@head] @head += 1 @number -= 1 return ans end # def of dequeue def show # キューの内容の表示 return @data[@head..@tail] end # def of show end # classここではメソッドとして initialize、enqueue、dequeue、そしてshowを用意しました。 initializeはクラスのインスタンスを作るための関数でしたね。 ですから、例えば
enqueueとdequeueのメソッドはそれぞれキューに要素を付け加える操作と、キューからデータを取り出す操作です。それぞれのメソッドを実行すると headとtailの値(それにnumberの値)が変化することに注意しましょう(もちろんdataの値も変わります)。 最後にshowはキューの状態を表示するためのもので、配列として要素が表示されます。
Rubyによる実現(その1)では、教科書の64ページ、
および図6.5(a)-(c)に書かれた方法で、配列を用いてキューを表現していた。
しかしこれでは教科書に書いてあるように、キューに新しいデータを格納したり取り出したりしているうちに、
headの値もtailの値も大きくなる一方なので、実際にキューに記憶されているデータは小さなものであっても、そのデータを記憶するために用意された配列は大きいままである。これはキューからは取り除かれているはずのデータが配列に残ったままであることが原因である。
そこでこのような無駄をなくすために、配列の大きさはあまり大きくしないようにし、 headやtailの値を工夫することでメモリの無駄が少ないキューを実現することを考えよう。
そのための方法として、教科書図6.5のように配列を環状のものと考えてみよう。 とは言っても、環状の配列、というものは存在しない。ここでは図6.5(a)に書かれたような(直線上に要素を並べた)配列を図6.5(d)のように見かけ上円のように並べるようにする、ということである。
ここで配列の大きさを固定しておく。仮にそれをnとする(nの値としては50や100を考える)。また、ここでは(話を簡単にするため)配列のインデックスは1から始まるものとする。 だから配列の要素のインデックスがとりうる値の範囲は、1, 2, ..., nとなる。
そして、headやtailの値がnを超えた場合、たとえばn+1やn+2のような場合には、 1や2のような値を与えるようにするのである。 具体的には、このような工夫をしなかった場合の配列のインデックスをx、 円環状の配列のインデックスをyとすると(ここで%は剰余を求める演算)
y = (x-1)%n + 1とするのである。
class Queue def initialize(n) # 記録できるデータの最大個数を引数とする @data = [nil] # データの記憶用の配列。0番目の要素はダミー @head = 1 # 先頭のデータの位置 @tail = 0 # 最後尾のデータの位置 @max = n # 記録できるデータの最大個数 @number = 0 # 記録されているデータの個数 end # initialize def convert(x) # 変換のための関数 y = (x-1) % @max + 1 return y end # def of convert def enqueue(item) if (@number < @max) # データの追加が可能なら @number += 1 # @tailを適切な値に設定するコードを書く(convertを使うこと) @data[@tail] = item else STDERR.print "Queue is empty\n" # warning message end # if end # def of enqueue def dequeue if (@number > 0) # キューに要素があるなら # @numberと@headを適切な値に設定し # (convertを使うこと) # 適切な返り値を返すコードを書く else STDERR.print "Queue is empty\n" # warning message end # if end # def of dequeue def empty # キューが空ならtrue、そうでなければflaseを返す if (@number > 0) # キューに要素があるか return false else return true end # if end # def of empty def show # キューの内容の表示 lst = [] # 改変されていることに注意 for i in 0...@number lst << @data[convert(@head+i)] end # for return lst end # def of show end # class