経路数を計算する
それではおためし。単位行列Aとfig-2に示した隣接行列Bを用意し、A = A * Bを三回繰り返してB^3を求めます。
#include "sparse/matrix.h" #include "sparse/multiplies.h" #include "sparse/fill_diagonal.h" #include <iostream> // cout, endl int main() { using namespace sparse; matrix<float> A; fill_diagonal(A, 4, 1.0f); // 4x4の単位行列 matrix<float> B; // 隣接行列 B.insert(0, 0, 1.0f); B.insert(0, 1, 1.0f); B.insert(0, 2, 1.0f); B.insert(1, 2, 1.0f); B.insert(2, 3, 1.0f); B.insert(3, 1, 1.0f); B.finalize(); // A = A * B を3回行う A = multiplies(A, B, 4, 4, 4); A = multiplies(A, B, 4, 4, 4); A = multiplies(A, B, 4, 4, 4); A.enumerate([](int row, int col, float val) { std::cout << '[' << row << ',' << col << "] " << val << std::endl;}); std::cout << std::endl; }
隣接行列を3回かけたときの成分(i,j)が何を意味するかというと、「頂点iから頂点jへ3つの辺をたどって到達する経路の数」なんです。例えば「[0,3] 2」とありますから、島0から3本の橋を渡って島3にたどり着くルートは2通りです。確かに0→0→2→3と0→1→2→3の2通りですね。
経路の最短/最長距離を求める
さて、ここから面白くなりますよ。
K行L列の行列AとL行M列の行列Bの掛け算関数multipliesは、その成分(row,col)を以下のアルゴリズムで求めています。
val = 0; for ( i = 0; i < L; ++i ) { val = val + A(row, i) * B(i, col);
for-loopの中で乗算と加算が1つずつ現れてますね。この乗算と加算を引数で与えるmultipliesを用意します。
template<typename T, typename U, typename MulOp, typename AddOp> matrix<T> multiplies(const matrix<T>& A, const matrix<U>& B, int k, int l, int m, MulOp nmul, AddOp nadd) { matrix<T> out; // out[row, col] = Σ(A[row,i] * B[i,col] for ( int row = 0; row < k; ++row ) { for ( int col = 0; col < m; ++col ) { T s; T* sp = nullptr; T v; T* vp; T a; T* ap; U b; U* bp; for ( int i = 0; i < l; ++i ) { ap = A.at(row, i, &a); // a = A[row,i] bp = B.at(i, col, &b); // b = B[i,col] vp = nmul(ap, bp, &v); // v = a * b sp = nadd(sp, vp, &s); // s = s + v; } if ( sp != nullptr ) { out.insert(row, col, s); } } } out.finalize(); return out; }
ここで与える乗算/加算関数MulOo/AddOpは、以下のプロトタイプに従うこととします。
T* operation(const T* a, const T* b, T* c)
*aと*bを演算子の両辺とし、演算結果を*cにセットしてcを返します。ただし左辺/右辺が無効値であったときはa,bはnullptrですし、演算結果が無効値ならばnullptrを返します。
ここで乗算と加算を以下のように定義します:
- 乗算:両辺の和を返す。ただしどちらかが無効値であるなら無効値を返す。
- 加算:両辺の「大きくない方」を返す。ただし一方が無効値なら他方を、両辺とも無効値なら無効値を返す。
隣接行列にも手を加え、各成分を(1ではなく)橋の長さに置き換えます。
#include "sparse/matrix.h" #include "sparse/multiplies.h" #include "sparse/fill_diagonal.h" // 乗算 <*> // 0 <*> 0 = 0 // x <*> 0 = 0 // 0 <*> x = 0 // x <*> y = x+y template<typename T> struct nullable_multiplies { T* operator()(const T* ap, const T* bp, T* result) const { if ( ap == nullptr ) return nullptr; if ( bp == nullptr ) return nullptr; *result = (*ap) + (*bp); return result; } }; // 加算 <+> // 0 <+> 0 = 0 // 0 <+> x = x // x <+> 0 = x // x <+> y = min(x,y) template<typename T> struct nullable_min { T* operator()(const T* ap, const T* bp, T* result) const { if ( ap == nullptr && bp == nullptr ) return nullptr; if ( ap == nullptr ) *result = *bp; else *result = ( bp == nullptr ) ? *ap : ((*ap) < (*bp) ? (*ap) : (*bp)); return result; } }; // 加算 <+> // 0 <+> 0 = 0 // 0 <+> x = x // x <+> 0 = x // x <+> y = max(x,y) template<typename T> struct nullable_max { T* operator()(const T* ap, const T* bp, T* result) const { if ( ap == nullptr && bp == nullptr ) return nullptr; if ( ap == nullptr ) *result = *bp; else *result = ( bp == nullptr ) ? *ap : ((*ap) > (*bp) ? (*ap) : (*bp)); return result; } }; #include <iostream> // cout, endl int main() { using namespace sparse; matrix<float> A; fill_diagonal(A, 4, 0.0f); matrix<float> B; B.insert(0, 0, 0.8f); B.insert(0, 1, 1.5f); B.insert(0, 2, 1.8f); B.insert(1, 2, 1.1f); B.insert(2, 3, 2.1f); B.insert(3, 1, 2.2f); B.finalize(); nullable_multiplies<float> mulop; nullable_min<float> addop; A = multiplies(A, B, 4, 4, 4, mulop, addop); A = multiplies(A, B, 4, 4, 4, mulop, addop); A = multiplies(A, B, 4, 4, 4, mulop, addop); A.enumerate([](int row, int col, float val) { std::cout << '[' << row << ',' << col << "] " << val << std::endl;}); std::cout << std::endl; }
この演算で得られる成分(i,j)は、「3本の橋をたどって島iから島jに到達するときの最短路長」なんです。加算の定義を「小さくない方」に差し替えれば最長路長が求まります。