SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

疎行列の計算を実装してグラフ理論をかじってみる

  • X ポスト
  • このエントリーをはてなブックマークに追加

経路数を計算する

 それではおためし。単位行列Aとfig-2に示した隣接行列Bを用意し、A = A * Bを三回繰り返してB^3を求めます。

list-4 01_adjacency\01_adjacency.cpp
#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を用意します。

list-5 02_adjacency\02_adjacency.cpp
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ではなく)橋の長さに置き換えます。

list-6 03_shortestpath\03_shortestpath.cpp
#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に到達するときの最短路長」なんです。加算の定義を「小さくない方」に差し替えれば最長路長が求まります。

次のページ
経路の道順を求める

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

επιστημη(エピステーメー)

C++に首まで浸かったプログラマ。Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"りわんくま同盟でたまにセッションスピーカやったり中国茶淹れてにわか茶...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/9421 2016/05/30 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング