読者です 読者をやめる 読者になる 読者になる

トポロジカルソートのベンチマークらしきもの

アルゴリズム Ruby

離散最適化理論の課題を提出し終わったので、貼ってみるテスト。トポロジカルソートのためのところは前にちょちょいと書いたんですが

  • 説明用の図
  • 巨大なグラフを作る用の関数
  • 計算時間をplotするための関数

などなどの付近も書いたので、貼ってみます。

例を使って説明する付近はruby-graphvizを使ってみたりしました。結構使いやすい。
clothes.png
あと、グラフのノード数とエッジ数を色々変えて実験しないといけなかったので、こんな感じのトポロジカルソートできそうな、でかいグラフを吐ける関数を用意しておきました。
example.png
グラフの計算時間はO(M+N)になるんですが

  • エッジ数を固定、ノード数を動かす
  • ノード数を固定、エッジ数を動かす
  • 両方を動かす

の3種類の計算時間をplotしてみました。グラフ理論とかの計算量ってパラメータが2つになったりして、色々やりづらかったりするなーと思った。
topological_sort_fixing_edge.png (3 documents)
topological_sort_fixing_node.png (3 documents)
topological_sort.png (3 documents)
id:repose書いたやつも添削っぽいことをしたいんだけど、時間を捻出できるか怪しくなってきた気がする。。。

以下ソースです。

# -*- coding: utf-8 -*-

require "rubygems"
require "graphviz"
require "pp"
require "set"

class Graph
  attr_reader :nodes
  attr_reader :edges
  def initialize(nodes=[])
    @nodes = nodes
    @edges = Hash.new{|h,k| h[k] = []}
  end
  
  def add_node(node)
    @nodes.push node unless @nodes.index(node)
  end
  
  def add_edge(u, v)
    if @nodes.index(u).nil?
      @nodes.push u
    end
    if @nodes.index(v).nil?
      @nodes.push v
    end
    
    unless @edges[u].index(v)
      @edges[u].push v
    end
  end

  def topological_sort(nodes = @nodes.to_set, edges = @edges.clone, result = []) 
    # function for topological sort
    # "clone" is for shallow copy, not deep copy
    diff = nodes - edges.values.flatten.uniq.to_set
    # 入ってくることがない枝をdiffに代入
    diff.each{|item|
      result.push item
      edges.delete(item)
      # グラフから削除
    }
    topological_sort(nodes - diff, edges, result) unless (nodes - diff).empty?
    # 入ってくることがない枝を取り除いたものに対して、再帰的にトポロジカルソートを適用
    return result
  end

  def convert2graphViz(option) 
    # function to plot the graph
    # option must be a hash
    g = GraphViz.new("G", option)
    @nodes.each{|node|
      g.add_node(node)
    }
    @edges.each_pair{|key, value|
      value.each{|item|
        g.add_edge(g.add_node(key), g.add_node(item))
      }
    }
    return g
  end
end

def make_graph_for_topological_sort(param)
  # make the random graph for topological sort 
  min = param[:min]
  max = param[:max]
  interval = param[:interval]
  add_node_param = param[:add_node_param]
  add_edge_param = param[:add_edge_param]

  g = Graph.new
  num = min
  while true
    g.add_node(num.to_s) if rand < add_node_param
    num += 1
    break if num > max
  end
  g.nodes.each{|i|
    g.nodes.each{|j|
      if i < j
        g.add_edge(i, j) if rand < add_edge_param
      end
    }
  }
  return g
end

# 服の例
g = Graph.new

g.add_edge("belt", "jacket")
g.convert2graphViz({:output => "png",
                     :file => "~/discrete_optimization_theory/graph/clothes8.png"}).output
g.add_node("shoes")
g.convert2graphViz({:output => "png",
                     :file => "~/discrete_optimization_theory/graph/clothes7.png"}).output
g.add_edge("tie", "jacket")
g.convert2graphViz({:output => "png",
                     :file => "~/discrete_optimization_theory/graph/clothes6.png"}).output
g.add_edge("pants", "belt")
g.add_edge("pants", "shoes")
g.convert2graphViz({:output => "png",
                     :file => "~/discrete_optimization_theory/graph/clothes5.png"}).output
g.add_edge("shirts", "belt")
g.add_edge("shirts", "tie")
g.convert2graphViz({:output => "png",
                     :file => "~/discrete_optimization_theory/graph/clothes4.png"}).output
g.add_edge("socks", "shoes")
g.convert2graphViz({:output => "png",
                     :file => "~/discrete_optimization_theory/graph/clothes3.png"}).output
g.add_edge("undershorts", "pants")
g.convert2graphViz({:output => "png",
                     :file => "~/discrete_optimization_theory/graph/clothes2.png"}).output
g.add_edge("undershorts", "shoes")
g.add_node("watch")
g.convert2graphViz({:output => "png",
                     :file => "~/discrete_optimization_theory/graph/clothes1.png"}).output

g.convert2graphViz({:output => "png",
                     :file => "~/discrete_optimization_theory/graph/clothes.png"}).output
puts "[#{g.topological_sort.join(", ")}]"


# 巨大なグラフのplot
g = make_graph_for_topological_sort({:min => 0, :max => 100,
                                  :add_node_param => 0.3,
                                  :add_edge_param => 0.2
                                })
g.convert2graphViz({:output => "png",
                     :file => "~/discrete_optimization_theory/graph/example.png"}).output

puts "=" * 30
puts "Edge数を固定、Node数を動かす"

f = File.open("/tmp/topological_sort_fixing_edge.csv", "w")

n = 20
max = 1000
(1..25).each{|x|
  mean_time = 0
  mean_nodes = 0
  mean_edges = 0
  n.times do 
    g = make_graph_for_topological_sort({:min => 0, :max => max,
                                          :add_node_param => 0.04 * x,
                                          :add_edge_param => 0.02 / x**2 
                                        })
    t = Time.now
    g.topological_sort
    mean_time += Time.now - t
    mean_nodes += g.nodes.length
    mean_edges += g.edges.values.flatten.length
  end
  str = "#{mean_nodes / n}, #{mean_edges / n}, #{mean_time / n}"
  puts str
  f.puts str
}
f.close

puts "=" * 30
puts "Node数を固定、Edge数を動かす"

f = File.open("/tmp/topological_sort_fixing_node.csv", "w")

n = 10
max = 1000
(1..25).each{|x|
  mean_time = 0
  mean_nodes = 0
  mean_edges = 0
  n.times do 
    g = make_graph_for_topological_sort({:min => 0, :max => max,
                                          :add_node_param => 0.5,
                                          :add_edge_param => 0.04 * x
                                        })
    t = Time.now
    g.topological_sort
    mean_time += Time.now - t
    mean_nodes += g.nodes.length
    mean_edges += g.edges.values.flatten.length
  end
  str = "#{mean_nodes / n}, #{mean_edges / n}, #{mean_time / n}"
  puts str
  f.puts str
}
f.close

puts "=" * 30
puts "Node数、Edge数を動かす"

f = File.open("/tmp/topological_sort.csv", "w")

n = 10
max = 1000
(1..10).each{|x|
  (1..10).each{|y|
    mean_time = 0
    mean_nodes = 0
    mean_edges = 0
    n.times do 
      g = make_graph_for_topological_sort({:min => 0, :max => max,
                                            :add_node_param => 0.1 * x,
                                            :add_edge_param => 0.01 * y
                                          })
      t = Time.now
      g.topological_sort
      mean_time += Time.now - t
      mean_nodes += g.nodes.length
      mean_edges += g.edges.values.flatten.length
    end
    str = "#{mean_nodes / n}, #{mean_edges / n}, #{mean_time / n}"
    puts str
    f.puts str
    puts "x #{x}, y #{y}"
  }
}
f.close