2分探索木の連想配列で削除方法を変えたら速くなった
連想配列の再発明(線形検索、二分探索木、ハッシュテーブル)してそれぞれの時間計測 - fujidigの雑記の2分探索木バージョンの改良。
クラス名をMyHash2Mに変更し上記の記事のコードに追加してみると、
user system total real Hash 0.032000 0.000000 0.032000 ( 0.031000) MyHash1 32.515000 0.000000 32.515000 ( 32.515000) MyHash2 1.063000 0.016000 1.079000 ( 1.094000) MyHash2M 0.640000 0.000000 0.640000 ( 0.641000) MyHash3 0.219000 0.000000 0.219000 ( 0.218000)
こんな感じで半分くらいの時間に短くなっています。今までの削除方法では木の高さが高くなってしまって効率が悪かったんでしょうね。
ところで、ノードを置き換える場合、「ノード = nil」ではなく、「親ノード.left = nil」のようにしなくてはいけなく面倒ですね。とりあえず、ProcやMethodを使ってやっていますが。
class MyHash include Enumerable #Node = Struct.new( :key, :hash, :value, :left, :right ) class Node def initialize( key, value, left = nil, right = nil ) @key = key @value = value @left = left @right = right end attr_accessor :key, :value, :left, :right def each @left.each{|v| yield v } if @left yield [key, value] @right.each{|v| yield v } if @right end def inspect( level = 0 ) puts ' ' * level + key.inspect if @left puts ' ' * level + 'left:' @left.inspect( level + 1 ) end if @right puts ' ' * level + 'right:' @right.inspect( level + 1 ) end end end def initialize @tree = nil end attr_reader :tree def []( key ) hash = key.hash node = @tree loop do return nil unless node return node.value if node.key.hash == hash && node.key.eql?( key ) if hash < node.key.hash node = node.left else node = node.right end end return nil end def []=( key, value ) hash = key.hash unless @tree @tree = Node.new( key, value ) return value end node = @tree loop do if node.key.hash == hash && node.key.eql?( key ) node.value = value return value end if hash < node.key.hash unless node.left node.left = Node.new( key, value ) return value else node = node.left end else unless node.right node.right = Node.new( key, value ) return value else node = node.right end end end end def delete( key ) hash = key.hash node = @tree set_node = proc{|v| @tree = v } loop do return nil unless node if node.key.hash == hash && node.key.eql?( key ) value = node.value if node.left && node.right # nodeの値より小さい値のうちの最大値を探し、nodeの値を置き換える n = node.left set_n = node.method( :left= ) while n.right set_n = n.method( :right= ) n = n.right end node.key = n.key node.value = n.value set_n.call( n.left ) elsif node.left set_node.call( node.left ) elsif node.right set_node.call( node.right ) else set_node.call( nil ) end return value end if hash < node.key.hash set_node = node.method( :left= ) node = node.left else set_node = node.method( :right= ) node = node.right end end end def each @tree.each{|v| yield v } if @tree end def inspect '{' + map{|key,value| key.inspect+'=>'+value.inspect}.join(', ') + '}' end end