LoginSignup
6
5

More than 5 years have passed since last update.

N-Queens問題を解いてみた

Posted at

N-Queens問題をscalaでやってみた

N-Queens問題とは

チェスボード上に、クイーンを互いにチェックされないように置いていきます。クイーンは縦、横、斜めに相手がいる時にチェックできます。
したがって、8×8のチェスボード上では、各列に1つずつ、最大8つのクイーンを置くことができそうです。
これをn×nのチェスボードに拡張して、n個のクイーンの配置を考えます。

listとfor-comprehensionを使って解いてみる

まず、1個目からk-1個目までのクイーンが配置されていると仮定します。各行についても列と同様に、一つずつクイーンが置かれるので、1行目からk-1行目まで順番にクイーンが置かれているとします。これは、1からk−1のクイーンが置かれている列番号のリストと、そのリストを一つの要素とした、並べ方の組み合わせのリスト、すなわちリストのリストで表されます。

ここに、k番目のクイーンを、チェックされないように置くことを考えます。1からnまでの列番号に対して、それがチェックされない位置なら、列番号リストのk番目に加えます。これをすべての組み合わせに対して総当たりで行います。ここに、for-comprehensionの強さが出るわけですね。

  def queens(n: Int): List[List[Int]] = {
    def placeQueens(k: Int): List[List[Int]] = 
      if (k == 0) List(List())
      else for { queen <- placeQueens(k - 1)
                 column <- List.range(1, n + 1)
                 if isSafe(column, queen, 1) } yield column :: queen
    placeQueens(n)
  }

※ここまでのコードは http://www.scala-lang.org/docu/files/ScalaByExample.pdf
の第10章 For-Comprehensionsに載っています。この続き、isSafeを作るのが課題として提示されています。

チェックされない安全地帯を求める

さて、※に書いたように、ここからisSafeを表現するのが課題です。isSafeは、引数のカラムが、列番号リストに対して安全かどうかをBooleanで返します。3番目の引数deltaは、行番号kと対象のクイーンの行番号(1からk-1)の差で、初期値を1としています。k−(k-1)。

縦方向の条件から、k番目の列番号は、i番目(0<i<k)の列番号と違わなくてはなりません。また、斜め方向の条件を考えると、i番目の列番号±deltaは安全ではありません。
以上より、isSafeは次のように書けます。

 def isSafe(col: Int, queen: List[Int], delta: Int): Boolean = { queen match{
      case Nil => true
      case head :: tail => 
        if (col == head || col == head - delta || col == head + delta ) { false

        }else { isSafe(col, tail, delta + 1) } 
    }
  }

結果の表示

これで、N-Queens問題の解がList[List]の形で求められるようになりました。
しかし、リスト形式だとわかりにくいので、チェスボードを描画してみます。

描画は以下の関数で行います。

 def describe(row: Int, col: Int): String = col match{
    case `row` => describe(row, col - 1) + "_q_|"
    case 0 => "|"
    case _ => describe(row, col - 1) + "___|"
  }

queens関数で求められた解を受け取りつつ、描画してみると、クイーンの配置が平面図でわかります。

 def schema(n: Int) = {
    val solution = queens(n)
    var count = 1
    for (eachboad <- solution) {
      for (line <- List.range(1, n + 1)) print(" ___")
      print("\n")
      for (eachrow <- eachboad) {
        println(describe(eachrow, n))
      }
      println(eachboad + "\n--------No." + count + "----------\n")
      count += 1
    }

  }

8×8のチェスボードで計算したところ、92通りの配置方法があることがわかりました。
意外と多いですね。
16×16でやってみると、さすがに計算に時間がかかってなかなか結果が帰ってこなかったので中断しました。
計算効率を上げる方法があれば教えていただきたいです。


全文

object Queen {
  def schema(n: Int) = {
    val solution = queens(n)
    var count = 1
    for (eachboad <- solution) {
      for (line <- List.range(1, n + 1)) print(" ___")
      print("\n")
      for (eachrow <- eachboad) {
        println(describe(eachrow, n))
      }
      println(eachboad + "\n--------No." + count + "----------\n")
      count += 1
    }

  }

  def queens(n: Int): List[List[Int]] = {
    def placeQueens(k: Int): List[List[Int]] = 
      if (k == 0) List(List())
      else for { queen <- placeQueens(k - 1)
                 column <- List.range(1, n + 1)
                 if isSafe(column, queen, 1) } yield column :: queen
    placeQueens(n)
  }

  def isSafe(col: Int, queen: List[Int], delta: Int): Boolean = { queen match{
      case Nil => true
      case head :: tail => 
        if (col == head || col == head - delta || col == head + delta ) { false

        }else { isSafe(col, tail, delta + 1) } 
    }
  }

  def describe(row: Int, col: Int): String = col match{
    case `row` => describe(row, col - 1) + "_q_|"
    case 0 => "|"
    case _ => describe(row, col - 1) + "___|"
  }

}
6
5
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5