浮動小数点数と数値計算

7月 15th, 2008 admin

あいかわらず「アルゴリズムとデータ構造シリーズ」です。今回は第8章「浮動小数点数と数値計算」です。

浮動小数点数

float型やdouble型の数値はコンピュータの内部では
符号と指数(n)と基数(a)で表現されている。
たとえば12.8なら
基数=0.8
指数=4

0.8*2^4
という具合です。なのでその表現の都合で、あまりにも大きかったり小さかったりする数字は正確には表せないということがあるというお話

数値計算

簡単な数値計算の例として5次方程式を解くというもののコードを書きました。
バイナリサーチを用いて、答えのある範囲をだんだんと絞っていくものです。

RUBY:
  1. #func
  2.  
  3. def func(x)
  4.     x = (x * x * x * x * x) - (10.0 * x * x * x * x) + (25.0 * x * x * x) + ( 40.0 * x * x) + ( 200.0 * x) - 500.0
  5.     return x
  6. end
  7.  
  8.  
  9. def binary_search
  10.   epsilon = 0.00001
  11.   left = 1.0
  12.   right = 3.0
  13.  
  14.   while((right-left).abs> epsilon && func(left).abs> epsilon)
  15.     mid = (left+right)/2.0
  16.     if(func(left)*func(mid)>=0.0)
  17.       left=mid
  18.     else
  19.       right=mid
  20.     end
  21.   end
  22.  
  23.   return left
  24. end
  25.  
  26. d = binary_search
  27. p d
  28. printf("answer is %1f. and func(x) is %1f \n",d,func(d))

まとめ

コンピューターなのに不正確な答えを出すと言うのがなんだか面白いですね。

※このシリーズは私がRubyでアルゴリズムとデータ構造を基礎から学ぶ記録です。全体の目次はこちらへ。

マップとハッシュ(ハッシュマップ)

7月 13th, 2008 admin

今回は「アルゴリズムとデータ構造シリーズ」です。前回に続いて第7章「マップとハッシュ」のハッシュマップの実装について。

ハッシュ値が重複した場合の対策

ハッシュ値が重複した場合には「k^2番目に再ハッシュする」という方法で対応した。
たとえばハッシュ値が2だった場合には
・3(2+1*1)
・6(2+2*2)
・11(2+3*3)
と試していく方法だ。

コード

検索と削除の部分は少しずるをしました。

RUBY:
  1. #Hash Map
  2. $KCODE = "SJIS"
  3.  
  4. class WordSet
  5.   attr_accessor :english, :japanese
  6.   def initialize(english = nil, japanese = nil)
  7.     @english = english
  8.     @japanese = japanese
  9.   end
  10. end
  11.  
  12. class HashMap
  13.   def initialize(size)
  14.     @hash_table_size = size
  15.     @hash_table = Array.new
  16.   end
  17.  
  18.   def make_hash(str)
  19.     weight = 0
  20.     hash = 0
  21.     str.each_byte{|s|
  22.       weight = 0 if weight> 7
  23.       hash += s * (256 ** weight)
  24.       weight+=1
  25.     }
  26.     hash % @hash_table_size
  27.   end
  28.  
  29.   def add_data(ws)
  30.     hash_val = self.make_hash(ws.english)
  31.  
  32.     unless @hash_table[hash_val].nil?
  33.       hash_val = self.rehash(hash_val)
  34.       if(hash_val == -1)
  35.         printf("%sをマップに挿入しようとしましたが、空き位置がありませんでした",ws.english)
  36.         return
  37.       end 
  38.     end
  39.    
  40.     @hash_table[hash_val] = ws
  41.   end
  42.  
  43.   def rehash(first_hash_val)
  44.     #k^2に再ハッシュする
  45.     (1...(@hash_table_size/2)).each do |k|
  46.       hash_val = (first_hash_val+k*k) % @hash_table_size
  47.       return hash_val if @hash_table[hash_val].nil?
  48.     end
  49.     return -1
  50.   end
  51.  
  52.   def find(str)
  53.     hash_val = self.make_hash(str)
  54.     return @hash_table[hash_val]
  55.   end
  56.  
  57.   def delete(str)
  58.     hash_val = self.make_hash(str)
  59.     @hash_table[hash_val] = nil
  60.     return true
  61.   end
  62.  
  63.   def print_all_data
  64.     @hash_table.each do |val|
  65.       printf("%s\t\t%s\n",val.english,val.japanese) unless val.nil?
  66.     end
  67.   end
  68. end
  69.  
  70. init_data = [
  71.                   WordSet.new("lisp","リスプ"),
  72.                   WordSet.new("C","シー"),
  73.                   WordSet.new("ruby","ルビー"),
  74.                   WordSet.new("perl","パール"),
  75.                   WordSet.new("python","パイソン"),
  76.                   WordSet.new("PHP","ピーエイチピー"),
  77.                   WordSet.new("JavaScript","ジャバスクリプト")
  78.                 ]
  79.                
  80. hash_map = HashMap.new(503)
  81.  
  82. init_data.each do |ws|
  83.   hash_map.add_data(ws)
  84. end
  85.  
  86. i = nil
  87. loop do 
  88.   print "コマンドを入力して下さい\n1:表\ 2:検索 3:削除\n"
  89.   i = gets.chomp.to_i
  90.  
  91.   case i
  92.     when 1
  93.       hash_map.print_all_data
  94.     when 2
  95.       print "検索する文字列を入力してください:"
  96.       j = gets.chomp
  97.       if ws = hash_map.find(j)
  98.         printf("%sは日本語で「%s」です。 \n\n",ws.english,ws.japanese)
  99.       else
  100.         print("見つかりませんでした。\n\n")
  101.       end
  102.     when 3
  103.       print "削除する文字列を入力してください:"
  104.       j = gets.chomp
  105.       if hash_map.delete(j)
  106.         printf("削除しました。 \n\n")
  107.       else
  108.         print("見つかりませんでした。\n\n")
  109.       end   
  110.   end
  111. end

まとめ

ハッシュというものがどういうものか(そもそもなんでハッシュと呼ばれているのか)わかって非常にすっきりできた。

※このシリーズは私がRubyでアルゴリズムとデータ構造を基礎から学ぶ記録です。全体の目次はこちらへ。

マップとハッシュ(ハッシュ値生成)

7月 8th, 2008 admin

今回も「アルゴリズムとデータ構造シリーズ」です。本日は第7章「マップとハッシュ」について。

マップとハッシュ

この章で学びたいのは「文字列をキーにして値を取り出せるデータ構造」である。それを実現するにはまずツリーマップという方法があり、それを改良したものがハッシュマップと呼ばれる一般的なハッシュというやつですよ。というのが今回のお勉強である。

ツリーマップ

まず前章でやったツリー構造を使うのがツリーマップである。キーとなる文字列をstrcmp()関数などで数値化して2分木で保持するというものだ。ツリーのどこにデータがあるかによって計算時間にばらつきがでるので安定していないのが欠点である。

ハッシュマップ

ツリーマップの欠点を克服したのがハッシュマップになる。メモリの消費量は多くなるが、なんと計算量オーダはO(1)と最速。すごい!
これはどのようにやるかと言うと
(1)データの検索キーを数値化する(ハッシュ値)
(2)ハッシュ値を記憶する大きな配列を用意し(ハッシュ表という。array[100]とか)、ハッシュ値に対応する位置にデータを格納する(ハッシュ値が78ならarray[78]に)
(3)データを検索するときには、検索キーを数値化し配列の要素を参照する
という感じだ。僕はなんだか本の索引を思い出しました。検索キーからページ番号がわかるので、そのページを開くと情報が得られるという一旦数字に直すところが面白い。

んで、この段階で以下のような疑問が出てきた
・ハッシュ値は重複しないのか
・ハッシュのサイズに合わせていたらハッシュ表がいくら大きくても足りないのではないか
だけど当然こういうのはクリアされているみたいだ。

コード

今回はまずハッシュ値の生成コードを書くところまで行った。
ハッシュ値はハッシュ表のサイズを抑えるために、ある程度小さい数に抑える必要があるため必ず重複してしまうが、重複に対応できるようにするので多少は問題ない。

RUBY:
  1. #make hash
  2. def make_hash(str, hashmax)
  3.   weight = 0
  4.   hash = 0
  5.   str.each_byte{|s|
  6.     weight = 0 if weight> 7
  7.     hash += s * (256 ** weight)
  8.     weight+=1
  9.   }
  10.   hash % hashmax
  11. end

まとめ

今回はハッシュマップの実装の前準備だけを行った。不思議なのはハッシュ表のサイズを素数にすると(上記のコードのhashmax)ハッシュ値の重複が、それが素数でない場合より少なくなるというTipsだ。んーだれか教えて。

※このシリーズは私がRubyでアルゴリズムとデータ構造を基礎から学ぶ記録です。全体の目次はこちらへ。

ツリー構造(2分木)

7月 6th, 2008 admin

今回も「アルゴリズムとデータ構造シリーズ」です。本日は「ツリー構造」について。

ツリー構造とは

ツリー構造とは樹形図のようになったデータ構造のことです。Windowsのファイル構造はツリー構造ですね。
難しそうに感じますが、以前やったリスト構造では、データが次のノードへのポインタを一つしかもっていなかったものを複数にするというだけで実現できます。

2分木

今回はツリー構造の中でも2分木と呼ばれるものを実装しました。2分木とはツリー構造の中でも各ノードが最大2個の子ノードしかもたない構造のことです。さらに今回は2分木の中でも、子ノードの並べ方に順序をつけ検索性をアップしたタイプ(左が小さい、右が大きい)にし、挿入・検索・削除ができるようにしました。
binarytree.png

まとめ

挿入や、検索よりも削除が面倒でした。
ツリー構造にはいろいろな応用パターンがあっておくが深そうです。本書では常に検索効率の良いAVL木やデータベースエンジンでよく使用されているB木(B+木)の解説もあり非常に勉強になりました。

コード

今回はコードが長いので最後にしました

RUBY:
  1. #Binary Tree
  2. $KCODE = "SJIS"
  3.  
  4. class Node
  5.   attr_accessor :left,:right,:value
  6.   def initialize(value)
  7.     @value = value
  8.     @left = nil
  9.     @right = nil
  10.   end
  11. end
  12.  
  13. class BinaryTree
  14.   attr_accessor :tree_root
  15.  
  16.   def initialize
  17.     @tree_root = nil
  18.   end
  19.  
  20.   def create_new_node(value)
  21.     Node.new(value)
  22.   end
  23.  
  24.   def insert_tree(value, node)
  25.     if node.nil?
  26.       @tree_root = self.create_new_node(value)
  27.       return
  28.     end
  29.    
  30.     if node.value> value
  31.       unless node.left.nil?
  32.         self.insert_tree(value,node.left)
  33.       else
  34.         node.left = self.create_new_node(value)
  35.       end
  36.     else
  37.       unless node.right.nil?
  38.         self.insert_tree(value,node.right)
  39.       else
  40.         node.right = self.create_new_node(value)
  41.       end
  42.     end
  43.   end
  44.  
  45.   def find(node, value)
  46.     if node.value> value
  47.       return nil if node.left.nil?
  48.       return self.find(node.left, value)
  49.     end
  50.    
  51.     if node.value <value
  52.       return nil if node.right.nil?
  53.       return self.find(node.right, value)
  54.     end
  55.  
  56.     node
  57.   end
  58.  
  59.   def delete(value)
  60.     direction = 0
  61.     parent_node = nil
  62.     node = @tree_root
  63.    
  64.     while(node != nil && node.value != value) do
  65.       if(node.value> value)
  66.         parent_node = node
  67.         node = node.left
  68.         direction = -1
  69.       else
  70.         parent_node = node
  71.         node = node.right
  72.         direction = 1
  73.       end 
  74.     end
  75.    
  76.     return 0 if node.nil?
  77.  
  78.     if(node.left.nil? || node.right.nil?)
  79.       #左右どちらかがnilだった場合
  80.       if(node.left.nil?)
  81.         parent_node.left = node.right if direction == -1
  82.         parent_node.right = node.right if direction == 1
  83.         @tree_root = node.right if direction == 0
  84.       else
  85.         parent_node.left = node.left if direction == -1
  86.         parent_node.right = node.left if direction == 1
  87.         @tree_root = node.left if direction == 0
  88.       end
  89.     else
  90.       #両者共nilでなかった場合
  91.       left_biggest = node.left
  92.       parent_node = node
  93.       direction = -1
  94.       while(left_biggest.right != nil) do
  95.         parent_node = left_biggest
  96.         left_biggest = left_biggest.right
  97.         direction = 1
  98.       end
  99.      
  100.       node.value = left_biggest.value
  101.       if(direction == -1)
  102.         parent_node.left = left_biggest.left
  103.       else
  104.         parent_node.right = left_biggest.left
  105.       end
  106.     end
  107.     true
  108.   end
  109.  
  110.   def print_tree(depth, node)
  111.     return if node.nil?
  112.    
  113.     self.print_tree(depth+1, node.left)
  114.     (0...depth).each{|i|
  115.       printf("    ")
  116.     }
  117.     printf("%d\n",node.value)
  118.     self.print_tree(depth+1,node.right)
  119.   end
  120. end
  121.  
  122. tree = BinaryTree.new
  123. 10.times{|i|
  124.   tree.insert_tree(rand(99),tree.tree_root)
  125. }
  126. tree.print_tree(0,tree.tree_root)
  127.  
  128. i = nil
  129. while i != 0 do
  130.   print "現在のツリー\n"
  131.   tree.print_tree(0,tree.tree_root)
  132.  
  133.   print "コマンドを入力して下さい\n0:終了 1:追加 2:検索 3:削除\n"
  134.   i = gets.chomp.to_i
  135.  
  136.   case i
  137.     when 1
  138.       print "1~100の範囲で、追加する数字を入力してください:"
  139.       j = gets.chomp.to_i
  140.       tree.insert_tree(j ,tree.tree_root) if (j>= 1 && j <= 100)
  141.     when 2
  142.       print "検索する数字を入力してください:"
  143.       j = gets.chomp.to_i
  144.       if tree.find(tree.tree_root,j)
  145.         printf("%dは見つかりました。 \n",j)
  146.       else
  147.         print("見つかりませんでした。\n")
  148.       end
  149.     when 3
  150.       print "削除する数字を入力してください:"
  151.       j = gets.chomp.to_i
  152.       if tree.delete(j)
  153.         printf("%dは削除しました。 \n",j)
  154.       else
  155.         print("見つかりませんでした。\n")
  156.       end   
  157.   end
  158. end

※このシリーズは私がRubyでアルゴリズムとデータ構造を基礎から学ぶ記録です。全体の目次はこちらへ。

再帰呼び出し

7月 4th, 2008 admin

今日も「アルゴリズムとデータ構造シリーズ」です。本日は「再帰呼び出し」について。

再帰呼び出しとは

関数が自分自身を呼び出すのが「再帰呼び出し」。再帰が何かといわれれば、それはエッシャーの「描く手」であり落語の「頭山」でありGNUの名前の由来であったり、そしてなんといっても亜空の瘴気ヴァニラ・アイス様と答えるのが妥当でしょうか

今回は再帰呼び出しを使って2つ以上の数の最大公約数を求めるスクリプトを書きました。a,b,cと3つの数字があったら、b,cの最大公約数をもとめて、aと(b,cの最大公約数)の最大公約数を求めるというアルゴリズムです。

コード

RUBY:
  1. #recursive
  2.  
  3. class Gcd
  4.   def initialize
  5.     @list = Array.new
  6.   end
  7.  
  8.   def execute
  9.     multi_gcd(@list.size - 1)
  10.   end
  11.  
  12.   def set(i)
  13.     @list.push(i)
  14.   end
  15.  
  16.   #二つの数の最大公約数を求める
  17.   def gcd( a, b)
  18.     a.downto(1) do |num|
  19.       return num if(a%num == 0 && b%num==0)
  20.     end
  21.     nil
  22.   end
  23.  
  24.   def multi_gcd(n)
  25.     #数が2以下ならgcdを呼ぶだけ
  26.     return self.gcd(@list[0],@list[1]) if n == 1
  27.  
  28.     #数が3以上ならmulti_gcdよ呼びなおす
  29.     return self.gcd(@list[n], self.multi_gcd(n-1))
  30.   end
  31. end
  32.  
  33. mg = Gcd.new
  34. mg.set(9)
  35. mg.set(6)
  36. mg.set(27)
  37. p mg.execute

まとめ

再帰をイメージするときいつも、子どもの頃に遊んだ、見た目はアイスの棒みたいな10cm幅ぐらいのロール紙の玩具を思い出します。振ると伸びて戻ってくるあれです。

※このシリーズは私がRubyでアルゴリズムとデータ構造を基礎から学ぶ記録です。全体の目次はこちらへ。