バックトラックは、古典的な人工知能の基盤であり、この文は、「古典」として理解することができ、「伝統的な。」さて、人工知能の分野では、非常に人気のトピックがあり、それは機械学習です。
Nクイーン問題:ここでは、伝統的な人工知能の問題を導入する必要があります。このように、それは典型的な再帰的なバックトラックの問題です。
例:LeetCode質問51:Nクイーン
ポータル:英語ウェブサイト:51 N-クイーンズ、中国のウェブサイト:51. Nクイーン。
nは研究クイーン問題はどのようにあるn個置か女王nは × nはボード上の、そして女王はお互いにお互いを攻撃することはできません。
画像を解決する方法は、8つのクイーンの問題を示しています。
整数所与N、すべての異なる返し、n個の女王の問題に対する解決策を。
各溶液は、明示的な含有Nクイーン問題片は、プログラムの実施例を配置
'Q'
し、'.'
スペース、クイーンを表します。例:
输入: 4 输出: [ [".Q..", // 解法 1 "...Q", "Q...", "..Q."], ["..Q.", // 解法 2 "Q...", "...Q", ".Q.."] ] 解释: 4 皇后问题存在两个不同的解法。
分析:それは見えますが、それは人工知能の質問ですが、私たちのコードの実装は、暴力的な解決策として解釈することができ、私たちは「再帰的なバックトラック」モード暴力的なソリューションを使用し、あなたがすぐに生成された暴力の進路を決定することができます;結果条件は、すべての暴力ソリューションアウト後不適格除去され
、我々のカラム1に、行1は、その後明らかに、要素が配置されている、行2、列1:例えば最初の2例と要素の配置は、我々は要素は、その後、ダウントラバース継続置くことができます2行目の最初の3除外されていました。
上記の分析から、我々は「再帰」、「戻る」と「暴力的な解決策」、「深さ優先探索」表裏一体を見ることができます。
実際には、この道の問題は、多くの問題の配置のように、私たちは先に説明しました。私たちは、この方法は、我々は場所のようなものを持って実際には各層の始まりではないと思われるが、ゲームのルールは、それぞれの層我々は戻って、私たちが記録されますそれぞれの層の前にいくつかの可能な状態を排除するので、後に、状態が回復しなければなりません。
問題解決のアイデアに、我々は、ラインの位置によって、クイーンまたはラインがとても長いサイクルの外層として、配置を考えるために使用されます。
Java実装:
public class Solution {
private boolean[] col; // 记录在列上第几列已经放置了元素
private boolean[] dia1; // 记录在主对角线上哪些位置已经放置了元素
private boolean[] dia2; // 记录在副对角线上哪些位置已经放置了元素
private List<List<String>> res = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
col = new boolean[n];
dia1 = new boolean[2 * n - 1]; // 可以用归纳法得到对角线上的元素个数
dia2 = new boolean[2 * n - 1]; // 可以用归纳法得到对角线上的元素个数
putQueue(n, 0, new ArrayList<Integer>());
return res;
}
/**
* 尝试在一个 n 皇后的问题中,摆放第 index 行的皇后的位置
*
* @param n
* @param index
* @param row
*/
private void putQueue(int n, int index, List<Integer> row) {
if (index == n) {
res.add(generateBoard(n, row));
return;
}
// i 表示第几列,循环的过程就是在尝试给每一行的每一列放置皇后,看看在列上能不能放,看看在对角线上能不能放
// 其实 n 皇后问题和使用回溯解决排列问题的思路是一致的:暴力遍历,使用额外数组记录状态,一层层减少,递归到底以后回溯,回溯的过程中,一层一层地恢复状态
for (int i = 0; i < n; i++) {
if (!col[i] && !dia1[index + i] && !dia2[index - i + n - 1]) {
row.add(i);
col[i] = true;
dia1[index + i] = true;
dia2[index - i + n - 1] = true;
putQueue(n, index + 1, row);
col[i] = false;
dia1[index + i] = false;
dia2[index - i + n - 1] = false;
row.remove(row.size() - 1);
}
}
}
private List<String> generateBoard(int n, List<Integer> row) {
List<String> res = new ArrayList<>();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
sb.append(".");
}
StringBuilder cur = null;
for (int i = 0; i < n; i++) {
cur = new StringBuilder(sb);
int queueLoc = row.get(i);
cur.replace(queueLoc, queueLoc + 1, "Q");
res.add(cur.toString());
}
return res;
}
// 测试用例
public static void main(String[] args) {
Solution solution = new Solution();
List<List<String>> solveNQueens = solution.solveNQueens(8);
for (int i = 0; i < solveNQueens.size(); i++) {
System.out.println("第 " + (i + 1) + " 种解法:");
List<String> sloveOne = solveNQueens.get(i);
printList(sloveOne);
System.out.println("********");
}
}
private static void printList(List<String> sloveOne) {
for (int i = 0; i < sloveOne.size(); i++) {
System.out.println(sloveOne.get(i));
}
}
}
知識サプリメント:Nクイーン問題は、多くの最適化のアイデアを持っている、あなたは、検索処理を高速化することができます。(時間のあるので、我々は注目します)
練習
演習1:LeetCode質問52:N-クイーンII
ポータル:英語ウェブサイト:52. N-クイーンII、中国のウェブサイト:52 N女王II。
nは研究クイーン問題はどのようにあるn個置か女王nは × nはボード上の、そして女王はお互いにお互いを攻撃することはできません。
画像を解決する方法は、8つのクイーンの問題を示しています。
整数所与N、戻りN個の女王の異なる解の数。
例:
输入: 4 输出: 2 解释: 4 皇后问题存在如下两个不同的解法。 [ [".Q..", // 解法 1 "...Q", "Q...", "..Q."], ["..Q.", // 解法 2 "Q...", "...Q", ".Q.."] ]
Javaコード:
import java.util.Stack;
public class Solution {
private boolean[] marked;
private int count;
public int totalNQueens(int n) {
if (n == 0 || n == 1) {
return n;
}
int[] board = new int[n];
for (int i = 0; i < n; i++) {
board[i] = i;
}
permuta(board);
return count;
}
// 生成一个 [0,1,...,n-1] 的全排列
private void permuta(int[] board) {
int len = board.length;
marked = new boolean[len];
Stack<Integer> pre = new Stack<>();
findPermutation(board, 0, len, pre);
}
private void findPermutation(int[] board, int usedCount, int len, Stack<Integer> pre) {
if (usedCount == len) {
// 判断是否是符合要求的棋盘布局
if (noDanger(pre, len)) {
count++;
}
return;
}
for (int i = 0; i < len; i++) {
if (!marked[i]) {
marked[i] = true;
pre.push(board[i]);
findPermutation(board, usedCount + 1, len, pre);
marked[i] = false;
pre.pop();
}
}
}
private boolean noDanger(Stack<Integer> pre, int len) {
int[] board = new int[len];
for (int i = 0; i < len; i++) {
board[i] = pre.get(i);
}
for (int i = 0; i < len - 1; i++) {
for (int j = i + 1; j < len; j++) {
// 得到所有不同的 i j 的组合,是一个组合问题,按顺序来就行
// System.out.println(i + "\t" + j);
if (i - j == board[i] - board[j]) {
return false;
}
if (i - j == -(board[i] - board[j])) {
return false;
}
}
}
// 走到这里表示通过检验
// System.out.println(Arrays.toString(board));
return true;
}
public static void main(String[] args) {
Solution solution = new Solution();
int totalNQueens = solution.totalNQueens(8);
System.out.println(totalNQueens);
}
}
演習2:LeetCodeの第37回タイトル:数独を解きます
ポータル:37.単独で停止します。
分析:これはN-クイーン問題の問題よりもさらに低温である、人工知能の典型的な問題を自動的に解決することに加え、一般的には人工知能の章を開始再帰バックトラック、効果的な剪定は、問題を検索します。
満たされた空間を通って数独の問題を解決するためのプログラムを書きます。
数独は、解決策になると、次のルールに:
- 数字は
1-9
各列に一度だけ現れます。- 数字は
1-9
各列に一度だけ表示されます。- デジタル
1-9
各太い実線で区切られた3x3
子宮内で一度だけ表示されます。ブランクグリッド
'.'
表現。
数独。
答えは赤でマークされています。
注意:
- 与えられた数独シーケンスは数字のみ含まれている
1-9
との文字が'.'
。- あなたは、与えられた数独は、一つだけのソリューションを持っていることを仮定してもよいです。
- 数独は常に与えられます
9x9
フォームを。
Pythonコード:
import time
import sys
# 虽然成功了,但是已经超时!!!
# 37. 解数独
# 编写一个程序,通过已填充的空格来解决数独问题。
#
# 一个数独的解法需遵循如下规则:
#
# 数字 1-9 在每一行只能出现一次。
# 数字 1-9 在每一列只能出现一次。
# 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
# 空白格用 '.' 表示。
class Solution:
def __print(self, board):
result = ''
# i 表示行,是纵坐标,要特别注意
for i in range(9):
# j 表示列,是横坐标,要特别注意
for j in range(9):
if board[i][j] == '.':
result += ' '
else:
result += board[i][j]
result += ' '
result += '\n'
return result
def check(self, board, x, y):
# x 表示横坐标,y 表示纵坐标
num = board[x][y]
# 水平方向上,已经出现的数
h_nums = [board[x][col_index] for col_index in range(9) if col_index != y and board[x][col_index] != '.']
# 判断
if num in h_nums:
return False
# print('h_nums', h_nums)
# 垂直方向方向上,已经出现的数
v_nums = [board[row_index][y] for row_index in range(9) if row_index != x and board[row_index][y] != '.']
# 判断
if num in v_nums:
return False
# print('v_nums', v_nums)
# 重点理解下面这个变量的定义:所在小正方形左上角的横坐标
x_left = (x // 3) * 3
# 重点理解下面这个变量的定义:所在小正方形左上角的纵坐标
y_up = (y // 3) * 3
block_nums = []
for row in range(3):
for col in range(3):
if not ((x_left + row) == x and (y_up + col) == y):
if board[x_left + row][y_up + col] != 0:
block_nums.append(board[x_left + row][y_up + col])
# print('block_nums', block_nums)
if num in block_nums:
return False
# 以上 3 个条件都判断完以后,才能把 val 放在坐标 (x,y) 处
return True
def next(self, board):
# i 表示每一行
for i in range(9):
# j 表示每一列
for j in range(9):
if board[i][j] == '.':
return i, j
# 表示没有下一个元素了,数独任务完成
return False
def __accept(self, board):
# 如果没有了,就表示填完,返回 True
if self.next(board) is False:
return True
# 否则就表示数独任务没有完成
return False
def __solve(self, board):
# time.sleep(0.1)
# print(board)
# sys.stdout.flush()
# 先写递归终止条件
if self.__accept(board):
return True
x, y = self.next(board)
for i in range(1, 10):
board[x][y] = str(i)
if self.check(board, x, y) and self.__solve(board):
return True
board[x][y] = '.'
return False
def solveSudoku(self, board):
"""
:type board: List[List[str]]
:rtype: void Do not return anything, modify board in-place instead.
"""
self.__solve(board)
if __name__ == '__main__':
board = [['5', '3', '.', '.', '7', '.', '.', '.', '.'],
['6', '.', '.', '1', '9', '5', '.', '.', '.'],
['.', '9', '8', '.', '.', '.', '.', '6', '.'],
['8', '.', '.', '.', '6', '.', '.', '.', '3'],
['4', '.', '.', '8', '.', '3', '.', '.', '1'],
['7', '.', '.', '.', '2', '.', '.', '.', '6'],
['.', '6', '.', '.', '.', '.', '2', '8', '.'],
['.', '.', '.', '4', '1', '9', '.', '.', '5'],
['.', '.', '.', '.', '8', '.', '.', '7', '9']]
solution = Solution()
solution.solveSudoku(board)
for row in board:
print(row)
この時点の内容のこの部分は、動的プログラミングの我々の研究の次の部分。
(この節の終わり)