二分木検索とルーティングプロトコルを応用した相対的ソートアルゴリズムを考案した

概要

  • ルーティングプロトコルのリンクステートのような感じで、絶対的な数値を持たずに2つのノードを比較して相対的に距離を算出。
  • 各ノードは、前方ノードへアクセスするための左に生える木と、後方ノードへアクセスするための右に生える木を持つ。
  • ノード間の経路はbranch/merge/loopありの1次元情報・有向グラフとして繋げる。
  • なので到達経路が片方向に複数ある場合や、両方向にある場合もある。
  • ノードからノードへの到達経路の探索、ルーティングのMetricのような感じで最短経路の選択。
  • 再帰チェックでルーティングループ回避。

具体的に出来ること

  • じゃんけんのような相対的に勝ち負けが決まるデータのソート。
  • 干支のように延々ループするデータを相対的に次のノードへアクセス。
  • 複数の歯抜け配列から共通項を頼りに順序を保ってひとつの配列にマージ。
  • 左右双方向に繋げば、無向グラフとして全域木の経路探索も可能。

具体例

じゃんけん

leftのノードに負けて、rightのノードに勝つ、という上下関係。
3つのノードはループしているので右にも左にも経路はあるが、もっとも近いノード(隣接しているノード)を直接的な勝敗とする。

root = HighLow::Root.new([
  ["グー", "チョキ"],
  ["チョキ", "パー"],
  ["パー", "グー"],
])

puts "グーからパーへの経路"
routes = root.traceroutes("グー", "パー")
routes.each {|route|
  puts "方向=#{route.direction}, 距離=#{route.metric}, 経路=#{route.nodes.map(&:value)}"
}
puts

left = root.node("グー")
10.times {
  right = left
  left = left.next_lefts.first
  puts "#{right.value}に勝つのは#{left.value}"
}

実行結果。

グーからパーへの経路
方向=left, 距離=1, 経路=["グー", "パー"]
方向=right, 距離=2, 経路=["グー", "チョキ", "パー"]

グーに勝つのはパー
パーに勝つのはチョキ
チョキに勝つのはグー
グーに勝つのはパー
パーに勝つのはチョキ
チョキに勝つのはグー
グーに勝つのはパー
パーに勝つのはチョキ
チョキに勝つのはグー
グーに勝つのはパー
干支

ループするデータ。
leftのノードは過去、rightのノードは未来、という上下関係。
metric(デフォルトではhop数)で何年の差があるかを算出できる。

root = HighLow::Root.new([
  ['', '', '', '', '', '', '', '', '', '', '', '', '']
])

src = ''
dst = ''

left_route = root.left_traceroutes(src, dst).first
right_route = root.right_traceroutes(src, dst).first
shortest_route = root.shortest_routes(src, dst).first

puts "#{src}年を基準に前の#{dst}年は、#{left_route.metric}年前"
puts "#{src}年を基準に次の#{dst}年は、#{right_route.metric}年後"

sign = shortest_route.direction==:left ? '' : ''
puts "#{src}年を基準に一番近い#{dst}年は、#{shortest_route.metric}#{sign}"

実行結果。

卯年を基準に前の申年は、7年前
卯年を基準に次の申年は、5年後
卯年を基準に一番近い申年は、5年後
複数の歯抜け配列から共通項を頼りに順序を保ってひとつの配列にマージ

文殊の知恵的なマージ。
複数のソートされた配列を共通項を元にマージする。
branchなし、loopなしの一番シンプルな例。
一概にソートできない場合は、渡したProcでオーダーを決める。

alphabets = []
alphabets << [:a, :c, :e]
alphabets << [:a, :b, :d]
alphabets << [:c, :d, :e]
alphabets << [:e, :f]
alphabets << [:b, :c, :f]

pp RelativeComparison.merge(alphabets) {|a,b|
  puts "unknown order: #{a} #{b}"
  0
}

実行結果。

[:a, :b, :c, :d, :e, :f]

一概にソートできないbranchありの例。

data = []
data << [:a, :b, :d]
data << [:a, :c, :d]

このような場合だと、以下2つの式が成り立つ。

  • a < b < d
  • a < c < d

bとcの比較は出来ないのでProcが呼ばれる。

出世魚の名前を辿る

地域ごとのブリの稚魚の名前を辿る。

a = []
a << ['アオコ', 'イナダ', 'ワラサ', 'ブリ']
a << ['ワカシ', 'イナダ', 'ワラサ', 'ブリ']
a << ['ワカナゴ', 'イナダ', 'ワラサ', 'ブリ']
a << ['ツバス', 'ハマチ', 'メジロ', 'ブリ']
a << ['ツバイソ', 'フクラギ', 'ガンド', 'ブリ']
a << ['コゾクラ', 'フクラギ', 'ガンド', 'ブリ']
a << ['ツバス', 'ハマチ', 'マルゴ', 'ブリ']
a << ['ツバス', 'ヤズ', 'ワラサ', 'ブリ']


def print_prev_names(node)
  lefts = node.next_lefts
  return if lefts.length==0

  puts "#{node.value}のひとつ前の名前は#{lefts.length}"
  p lefts.map(&:value)
  puts

  lefts.each {|left|
    print_prev_names(left)
  }
end


root = RelativeComparison::Root.new(a)

node = root.node('ブリ')
print_prev_names(node)
puts


puts "ツバスからブリへの経路"
root.traceroutes('ツバス', 'ブリ').each {|route|
  p route.nodes.map(&:value)
}

実行結果。

ブリのひとつ前の名前は4個
["ワラサ", "メジロ", "ガンド", "マルゴ"]

ワラサのひとつ前の名前は2個
["イナダ", "ヤズ"]

イナダのひとつ前の名前は3個
["アオコ", "ワカシ", "ワカナゴ"]

ヤズのひとつ前の名前は1個
["ツバス"]

メジロのひとつ前の名前は1個
["ハマチ"]

ハマチのひとつ前の名前は1個
["ツバス"]

ガンドのひとつ前の名前は1個
["フクラギ"]

フクラギのひとつ前の名前は2個
["ツバイソ", "コゾクラ"]

マルゴのひとつ前の名前は1個
["ハマチ"]

ハマチのひとつ前の名前は1個
["ツバス"]


ツバスからブリへの経路
["ツバス", "ハマチ", "メジロ", "ブリ"]
["ツバス", "ハマチ", "マルゴ", "ブリ"]
["ツバス", "ヤズ", "ワラサ", "ブリ"]

ブリの名前は以下を参照。
http://jsnfri.fra.affrc.go.jp/kids/buri/kw6.html

全域木の経路探索

ノードを双方向に繋いで、無向グラフとする。
metricを指定することでOSPFのようにコストをmetricとすることが出来る。
無指定であればRIPと同じHop数がmetricとなる。

def regist_bi(root, left, right, metric)
  root.regist_pair(left, right, metric: metric)
  root.regist_pair(right, left, metric: metric)
end


root = RelativeComparison::Root.new

regist_bi(root, :left_node, :top_node, 5)
regist_bi(root, :left_node, :center_node, 4)
regist_bi(root, :left_node, :left_bottom_node, 2)
regist_bi(root, :top_node, :center_node, 2)
regist_bi(root, :top_node, :right_node, 6)
regist_bi(root, :center_node, :left_bottom_node, 3)
regist_bi(root, :center_node, :right_bottom_node, 2)
regist_bi(root, :left_bottom_node, :right_bottom_node, 6)
regist_bi(root, :right_bottom_node, :right_node, 4)


shortest_route = root.shortest_routes(:left_node, :right_node).first

puts "hop count: #{shortest_route.hop_count}"
puts "total metric: #{shortest_route.metric}"
puts

puts "routes (metric)"
puts shortest_route.map {|hop|
  "#{hop.node.value} (#{hop.metric})"
}.join(" => ")

実行結果。

hop count: 3
total metric: 10

routes (metric)
left_node (0) => center_node (4) => right_bottom_node (2) => right_node (4)

最短経路アルゴリズムは実装していないが、このローカルにあるこの程度のデータなら何の問題もない。
今後高速化の必要性を感じたらダイクストラ法やら何やらを実装しようと思う。

全域木図は以下を参照。
http://www.deqnotes.net/acmicpc/dijkstra/

あとがき

RubyGemsは気が向いたらやる。