gonum/graphを使って最短経路問題を解いてみた
概要
gonum/graph
を使って最短経路問題を解いてみました。今回は、特に、"辺の重みが非負数の場合の単一始点最短経路問題"を対象とします。ですので、具体的にはDijkstra法で解くことになります。
実は、gonum/graph
の中で、Dijkstra法を使って最小経路問題を解く関数はすでに実装されています。
gonum/graph
自体を初めて使用するので、備忘録としてこの記事を書いています。graphを構築することからはじめて、Dijkstra
関数を呼び出して最短経路を求めることをゴールとします。
Dijkstra法とは
Dijkstra法については、wikipediaやその他の記事を参考にしてください。
問題
例として次のようなグラフを考え、ノード0からノード4までの最短経路を求めます。
今回の最短経路は0 -> 1 -> 3 -> 4でその距離は3であることが簡単にわかります。
実装
先に全文を載せておきます。
package main
import (
"fmt"
"math"
"gonum.org/v1/gonum/graph/path"
"gonum.org/v1/gonum/graph/simple"
)
func main() {
g := simple.NewWeightedUndirectedGraph(0, math.Inf(1))
num := 5
start := 0
end := 4
edges := [][]int{
{0, 1, 1},
{0, 2, 2},
{1, 3, 1},
{1, 2, 1},
{2, 4, 3},
{3, 4, 1},
}
for i := 0; i < num; i++ {
node := simple.Node(i)
g.AddNode(node)
}
for _, e := range edges {
from := simple.Node(e[0])
to := simple.Node(e[1])
edge := g.NewWeightedEdge(from, to, float64(e[2]))
g.SetWeightedEdge(edge)
}
shortest := path.DijkstraFrom(simple.Node(start), g)
p, w := shortest.To(int64(end))
for _, node := range p {
fmt.Println(node.ID())
}
fmt.Println(w)
}
graphの構築
num := 5
start := 0
end := 4
edges := [][]int{
{0, 1, 1}, // 始点, 終点, 重み
{0, 2, 2},
{1, 3, 1},
{1, 2, 1},
{2, 4, 3},
{3, 4, 1},
}
初期化
まずグラフを初期化します。
graph/simple
パッケージにいろいろな種類のグラフの初期化用の関数があるようです。
今回は、重み付き無向グラフですので、NewWeightedUndirectedGraph
を使用します。
g := simple.NewWeightedUndirectedGraph(0, math.Inf(1))
NewWeightedUndirectedGraph
の第一引数は、自己ループの重み、第二引数はつながっていない辺の重みです。
ノードの追加
初期化したグラフにノードを追加していきます。
for i := 0; i < num; i++ {
node := simple.Node(i)
g.AddNode(node)
}
simple.Node
でIDのついたノードを作成します。
今回は簡単のために全て連番でIDをつけています。
辺の追加
辺を追加していきます。
始点と終点のノードを作成して、NewWeightedEdge
メソッドで辺を追加します。
*simple.WeightedUndirectedGraph
を使用しているため、自動的に無向辺が生成されます。
for _, e := range edges {
from := simple.Node(e[0])
to := simple.Node(e[1])
edge := g.NewWeightedEdge(from, to, float64(e[2]))
g.SetWeightedEdge(edge)
}
Dijkstra
graph/path
パッケージのDijkstraFrom
関数を使用して最短経路を取得します。
始点のノードを与えると、path.Shortest
が返されるので、To
メソッドで終点を入力すると、(path []graph.Node, weight float64)
が返されます。
shortest := path.DijkstraFrom(simple.Node(start), g)
p, w := shortest.To(int64(end))
for _, node := range p {
fmt.Println(node.ID())
}
fmt.Println(w)
出力は次のようになります。
0
1
3
4
3
確かに最短経路である0 -> 1 -> 3 -> 4とその距離3が返されています。
まとめ
gonum/graph
を利用することで自分で実装することなく最短経路問題が解くことができました。gonum/graph
はAtCoderでも使用できるので、もし最短経路問題が出てきたら使用してみたいと思います。
Discussion