再帰処理

再帰的な方法を用いてプログラムを組み立ててみよう。

再帰処理による並べ換え

Rubyでは、配列にsortメソッドが備わっているので、 配列に入っているデータを並べ換えてもらうことができる。最近の多くの言語で は、言語処理系の方に標準で並べ換え用のメソッドが用意されているので たくさんのデータを並べ換える具体的な手順を知らなくてもよくなっている。

しかしここでは再帰処理を理解する上での題材として、並べ換え手法の ひとつであるクイックソートを自分の手で作ってみよう。

クイックソートとは

クイックソートはたくさんあるデータ要素を次のような手順で 並べ換える方法である。

【クイックソートの方法】

大きな箱 Xに任意個のデータが入っているものとする。 クイックソートは以下の手順で行なう。

  1. X の中のデータが1個以下なら 並べ換え完了、そうでなければ次に進む
  2. X の中から、任意のデータを1個抜き取り、その値を k とする。
  3. kを抜き取ったあとの中のデータを順次見ていき、それが
    • k より小さかったら左側に置く
    • そうでなければ右側に置く
    ことをデータがなくなるまで繰り返す
  4. 左側に寄せたものをクイックソートする
  5. 右側に寄せたものをクイックソートする
  6. 左右のかたまりの中間に k を置いて 並べ換え完了

クイックソートしてみよう

クイックソートをカードを並べ換える方法に適用して体験して理解し よう。トランプを8枚用意しよう。といっても、すぐには用意できないので 使用済みの紙切れを8枚に切って、それぞれに好きな数字を書こう。

数字を書いた8枚のカード(もっと多くてもよい)が準備できたら、よくシャッ フルする。そうしたら以下の手順で並べ換えよう。

「クイックソートによるカードの並べ換え」

  1. 並べ換えるべき枚数が1枚以下なら終了、それ以上なら次に進む
  2. まず1枚のカードを机の真中に置く
  3. 手に残ったカードから1枚ずつ引いていき
    • 真中に置いたカードの数字より小さかったら左側に置く
    • そうでなければ右側に置く
    ことをカードがなくなるまで繰り返す(このとき左右に置く カードはでたらめに並べるか重ねておいてよい)
  4. 左側に置いた山を左側の机スペースでクイックソートする
  5. 右側に置いた山を右側の机スペースでクイックソートする
  6. おしまい

最初に選ぶカードに何を選んでもクイックソートが成功することを確認しよう

クイックソートを行なうメソッド

クイックソートを行なうメソッド qsort を定義しよう。

次のような設計とする。

これを実際にメソッドにしてみよう。いきなりメソッドにするのは難しいの で、ことばで骨子を考える。

def qsort(data)		 # dataは数値の入った配列
  if データが1個以下なら
    dataそのものを返して並べ換え完了
  else
    left(左側配列)を新規作成
    right(右側配列)を新規作成
    変数dataからデータを1個取り出す
    これを k とする

    残ったdata全てに対して以下を繰り返す
      if k より小さかったら
         leftに追加
      else
         rightに追加
      end
    繰り返し終わり

    「leftをqsortしたもの」と k と「rightをqsortしたもの」
    をその順に結合したものを結果として並べ換え完了
  end
end

※思い出しておこう※

第5回に学習した配列処理のためのメソッドを 忘れていると次に説明する完成プログラムがさっぱり分からない。 以下の配列に関するメソッドを思い出しておくこと。

上記の「ことば」で定義した qsort メソッドを Rubyプログラムに直そう。

def qsort(data)		# 配列を受け取る

  if data.length <= 1	# データが1個以下なら
    data		# そのものを返して並べ換え完了
  else
    left = Array.new	# left(左側配列)を新規作成
    right = Array.new 	# right(右側配列)を新規作成
    k = data.shift	# 変数dataの先頭を取り出してkにする

    for x in data	# 残ったdata全てに対して繰り返す
      if x < k 	   	# kより小さかったら
         left << x	# 左側に追加
      else
         right << x	# 右側に追加
      end
    end			# 繰り返し終わり

    # qsort(left)の末尾にkを足し、さらにqsort(right)を結合
    qsort(left).push(k) + qsort(right)
  end
end

この qsort メソッドを使って、並べ換えできるか 確認しよう。そのために、配列を読み込んだり、画面に表示したりするメソッド を作っておこう。

これらのメソッドを組み合わせて、プログラムは完成する。

qsort.rb

#!/usr/koeki/bin/ruby
# coding: utf-8

def qsort(data)		# 配列を受け取る

  if data.length <= 1	# データが1個以下なら
    data		# そのものを返して並べ換え完了
  else
    left = Array.new	# left(左側配列)を新規作成
    right = Array.new 	# right(右側配列)を新規作成
    k = data.shift	# 変数dataの先頭を取り出してkにする

    for x in data	# 残ったdata全てに対して繰り返す
      if x < k 	   	# kより小さかったら
         left << x	# 左側に追加
      else
         right << x	# 右側に追加
      end
    end			# 繰り返し終わり

    # qsort(left)の末尾にkを足し、さらにqsort(right)を結合
    qsort(left).push(k) + qsort(right)
  end
end

def readNumArray()
  ary = []
  while line=gets		# 入力が続く間
    datum = line.chomp!.to_i	# 整数に直して
    ary.push(datum)		# aryに追加していく
  end
  ary				# 最後にaryを呼び主に返す
end

def printArray(a)
  printf("[%s]\n", a.join(", "))
end

ary = readNumArray()

puts "整列前:"
printArray(ary)
puts "整列後:"
printArray(qsort(ary))

上記のプログラム、qsort.rb を 保存して実行し、1行に1つずつデータを入れて小さい順に並べ換えできることを 確認せよ。

実行例:

% ./qsort.rb
45
58
5
9
45
20
[C-d]

※問題※

qsortメソッドを書き変えて、数値の大きい順に 並べ換えるようにせよ。実際に実行してそれを確かめよ。


本日の目次