素人がプログラミングを勉強していたブログ

プログラミング、セキュリティ、英語、Webなどのブログ since 2008

連絡先: twitter: @javascripter にどうぞ。

幅優先探索

昔流行ったやつ。幅優先探索で迷路の経路を出す時は、ノードをリストにして逆算していくか、マップを0で初期化して、通るたびに0, 1, 2…というように徐々に増やしていって、ゴールについたら一つずつ減っていくような道を探す、という方法が使える。
それぞれ辺のコストが異なる場合はキューをPriority Queueにしてコストの合計が最小であるところから探索すれば良い。で、これを発展させるとダイクストラ法になって、更に、探索順序を改良すると、AStarになる。このへんは、一度書いてみると理解が深まるのでオススメ。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>迷路探索</title>
    <style>
      textarea {
        font-family: monospace;
      }
    </style>
  </head>
  <body>
    <p>ref. <a href="http://okajima.air-nifty.com/b/2010/01/post-abc6.html">人生を書き換える者すらいた。: 人材獲得作戦・4 試験問題ほか</a></p>
    <textarea rows="20" cols="30">
**************************
*S* *                    *
* * *  *  *************  *
* *   *    ************  *
*    *                   *
************** ***********
*                        *
** ***********************
*      *              G  *
*  *      *********** *  *
*    *        ******* *  *
*       *                *
**************************
    </textarea>
    <button>search</button>
  </body>
</html>
<script>

var button = document.querySelector("button");
button.addEventListener("click", onclick, false);

function onclick() {
  var textarea = document.querySelector("textarea");
  if (textarea.value.indexOf("$") >= 0) {
    textarea.value = textarea.value.replace(/\$/g, " ");
  } else {
    textarea.value = search(textarea.value);
  }
}

function search(map) {
  // マップは、文字の二次元配列とする
  var cols = map.split("\n").map(function (s) {
    return s.split("");
  });
  var queue = [];
  var v;

  // 最初はキューにスタート地点のみを入れておく
  queue.push(startNode(cols));

  // 訪問済み地点を記録するためのマップ
  var visited = {};

  find: {
    while (queue.length != 0) {
      v = queue.shift();
      if (cols[v.y][v.x] == "G") {
        // ゴールについたので抜ける、ゴール地点はv
        break find;
      }
      s = [v.x, v.y].join(",");
      if (!(s in visited)) { // 訪問済みでなければ、次の手をキューに入れる
        visited[s] = true;
        queue.push.apply(queue, build(cols, v));
      }
    }

    // ゴールに到達できなかった場合(ここは通らない)
    return;

  }

  // ゴール地点から逆算し経路を算出する
  for (v = v.p; v.p; v = v.p) {
    cols[v.y][v.x] = "$";
  }

  return cols.map(function (s) {
    return s.join("");
  }).join("\n")

}

function startNode(cols) {
  var x, y;
  // スタート地点の探索
  for (y = 0; y < cols.length; y++) {
    for (x = 0; x < cols[y].length; x++) {
      if (cols[y][x] == "S") {
        // キューの要素は、現在の地点を値とし一手前にリンクするリスト
        return {
          x: x,
          y: y,
          p: null
        };
      }
    }
  }
  // スタート地点が見つからなかった場合(ここは通らない)
  return null;
}

// 次の手
function build(cols, v) {
  // 現地点から次の手までの差分
  var ds = [
    {dx: -1, dy: 0},
    {dx: 1, dy: 0},
    {dx: 0, dy: -1},
    {dx: 0, dy: 1},
  ];
  return ds.map(function (d) {
    return {
      x: v.x + d.dx,
      y: v.y + d.dy,
      p: v
    };
  }).filter(function (v) {
    // Gも次の手に含まなねばならないので== " "ではない
    return cols[v.y][v.x] != "*";
  });
}

</script>