バックトラック方式は実際には非常に一般的です。たとえば、9 * 9の数独ゲームでは、ピア、同じ列、同じブロック内の重複する番号の原則に基づいて、未完成のフォームを補完します。空白を埋めるとき、特定の不確かなグリッドに遭遇することがよくあります。これらのグリッドには常に複数の選択肢があります。現時点では、最初にオプションを置き換えて推論を続ける必要があります。同じ列の同じ列の原則が繰り返されない場合。矛盾がある場合は、以前に置き換えたオプションが間違っているため、それを排除し、他の可能なオプションに置き換えて試行を続けることができます。これはバックトラックプロセスです。
アルゴリズムの概念
バックトラック方式は、最適な検索方式であり、ヒューリスティック方式とも呼ばれます。主なプロセスは、特定の制約または条件に従って検索プロセスを徐々に実行し、必要な目標を達成することです。制約が満たされない場合、または目標に到達できない場合は、返されます。特定のステップでの再選択は、バックトラックのプロセスです。
遡及的方法の中核は、制約、行動、目標の3つの側面にあります。たとえば、上記の数独ゲームでは、「同じ列と同じブロックで繰り返さない」の原則がバックトラッキング方式の制約です。「次の空白ボックスに1〜9の数字を入力できます」が検索の動作である「fill 「完全なフォーム」は私たちが達成したいものであり、グリッドに記入するたびに検索プロセスになります。
バックトラッキングの本質は再帰です。再帰を使用できるのは、バックトラッキング方式の検索の各ステップが前向きで、同じ検索原理に従うためです。同時に、目標に到達するか、目標を達成できない場合、再帰プロセスの終了に関する制約が完了します。したがって、問題を追跡可能な問題と判断する限り、トレースバックアルゴリズムの実装をすばやく記述できます。
アルゴリズム適用式
回溯法 = 行为(逐个xxxxx) + 约束(xxx应该xxxx) + 目标(最终xxxx)
回溯代码 = 约束检查函数 + 目标截止的行为递归函数
クラシックアプリケーションシナリオ
クイーンズエイトの質問
問題シーン
8クイーンの問題は、バックトラッキング方式の古典的なシナリオです。問題は次のように説明されています。8クイーンを8 * 8のチェス盤に配置して、お互いを攻撃しないようにします。つまり、2つのクイーンを同じ行に同じ行に表示することはできません。同じ対角線上には何種類の振り子がありますか?(92)
解決策
前述したように、バックトラックの中心は制約、検索方向、および目標にあります。
8クイーンの問題では、各行と列にクイーンが1つしかなく、8クイーンが8行のチェス盤に配置されるため、各行にクイーンが1つしかないため、各クイーンを決定するだけで済みます。どの列に配置され、すべてのクイーンが配置されると、配置を取得できます。
これから、次の情報を得ることができます。8つのクイーンの問題の場合、制約は「各行、列、およびスラッシュにクイーンが1つしかない」、動作は「次の行のクイーンの位置を決定する」、目標は「」です。 8個の配置を完了し、目標が達成されるたびに達成数を数えます。
コード作成のアイデア
コードの記述を完了する方法を段階的に説明します。
A.制約チェック機能チェック
制約部分には通常、問題に必要なさまざまなキー値が含まれているため、最初に、記述する制約部分を選択します。これにより、必要な変数をできるだけ早く決定できます。Eight Queens問題では、制約は
- 女王は一人だけ
- 同じ列に女王は1人だけです
- 同じスラッシュを持つ最大1つのクイーン
私たちの振る舞いは、クイーンの位置を行ごとに決定するため、各行にクイーンが1つだけ存在する必要があります。制約関数で必要な最初の2つのパラメーターは、行番号int line
と列番号int row
です。このとき、2番目の制約を確認するために、現在の列を決定する必要があります前のすべての行に重複するクイーンが存在するかどうかではなく、各行にクイーンint board[8]
の列番号を保存する必要があります。すでに列番号データを持っているので、3番目の制約をチェックするのは当然非常に簡単です。
inline bool check(int line, int row, int board[]){ // 约束
for(int i = 0; i < line; ++i){
int existRow = board[i];
if(existRow == row){ // 列是否重复
return false;
}
else if(line-i == abs(row-existRow)){ // 斜线是否重复
return false;
}
}
return true;
}
B.行動再帰関数placeQueen
パラメータ設定では、まず、8クイーンの問題の動作は、クイーンの位置を行ごとに決定することであるため、現在検索されている行番号を格納するパラメータが必要です。次に、包括的な制約関数もboard[8]
列情報を格納する必要があります。最後に、タイトル要件は、解の数を計算するnum
ことです。また、結果の数を数えるために1つ必要です。
再帰関数として、placeQueenには、すべてのクイーンの訪問を完了するための目標であるカットオフ設定が必要です。つまり、現在の行番号はチェス盤の行番号よりも大きいです。
if(line > 7){ // 目标
num++;
cout << "right!" << endl;
return ;
}
動作の主なリンクは、この行に配置できる位置を決定することです。そのため、現在の行の8つの位置の制約がチェックされ、それらが満たされている場合は、配置して次の行に入ることができます。
注:配置が許可されている場合board[8]
、対応する値を変更する必要があります。操作が完了したら、偏差を防ぐために値を変更する必要があります。
for(int i = 0; i < 8; ++i){
if(check(line, i, board)){
board[line] = i;
placeQueen(line+1, board, num); // 行为
board[line] = 8;
}
}
コード
#include <iostream>
#include <cmath>
using namespace std;
inline bool check(int line, int row, int board[]){ // 约束
for(int i = 0; i < line; ++i){
int existRow = board[i];
if(existRow == row){
return false;
}
else if(line-i == abs(row-existRow)){
return false;
}
}
return true;
}
void placeQueen(int line, int board[], int& num){
if(line > 7){ // 目标
num++;
return ;
}
for(int i = 0; i < 8; ++i){
if(check(line, i, board)){
board[line] = i;
placeQueen(line+1, board, num); // 行为
board[line] = 8;
}
}
}
int main(){
int num = 0;
int board[8];
for(int i = 0; i < 8; ++i){
board[i] = 0;
}
placeQueen(0, board, num);
cout << num << endl;
return 0;
}
数独問題
問題シーン
大きな9 9テーブルは、9 3 3グリッドで構成されています。3 3テーブルごとに、1〜9の9つの異なる数字を同時に入力する必要があります。これにより、9 9テーブルの各行と各列に重複する番号がなく、正方形の一部がわかります。フォームに記入します。
解決策
同じように、私たちはまだ数独問題の3つの側面を分析する必要があります。数独を埋めるとき、私たちは通常、消去法を使用して決定されたグリッドを埋めます。不確かなグリッドは可能なオプションを順番に試します。これは可能ですが、81個のセルに順番に入力してください。所定の値がある場合は、次のセルの処理を続行し、特定の値の競合を見つけたら、最も近いバックトラックポイントに戻ります。3つの要素の意味は上記で説明されており、ここでさらに説明します。
- 制約:同じ列、ピア、同じブロックの9つのグリッドは、1〜9の9つの数字で構成されます。
- 動作:各行の各グリッドに値を割り当てます。行が完了したら、次の行の最初の行から開始します。グリッドに既にデータがある場合は、スキップすることを選択します
- 目標:右下のグリッドにデータが入力されています
コード
ここに直接コードがあります
#include <iostream>
using namespace std;
bool check(int line, int row, int value, int board[][9]){
for(int i = 0; i < 9; ++i){
if(board[line][i] == value){
return false;
}
if(board[i][row] == value){
return false;
}
}
line /= 3;
row /= 3;
for(int i = line*3; i < line*3+3; ++i){
for(int j = row*3; j < row*3+3; ++j){
if (board[i][j] == value){
return false;
}
}
}
return true;
}
void printSudoku(int board[][9]){
for(int i = 0; i < 9; ++i){
for(int j = 0; j < 9; ++j){
cout << board[i][j] << " ";
}
cout << endl;
}
cout << "success" << endl;
}
void setNum(int line, int row, int board[][9]){
if(line>8){
printSudoku(board);
return;
}
bool succ = false;
if(board[line][row] == 0){
for(int i = 1; i < 10; ++i){
if(check(line, row, i, board)){
board[line][row] = i;
if(row < 8){
setNum(line, row+1, board);
}
else{
setNum(line+1, 0, board);
}
board[line][row] = 0;
}
}
}
else{
if(row < 8){
setNum(line, row+1, board);
}
else{
setNum(line+1, 0, board);
}
}
}
int main(){
int board[9][9] = {
{3, 0, 8, 0, 0, 0, 6, 0, 0},
{0, 0, 0, 0, 7, 4, 3, 0, 8},
{7, 1, 0, 6, 0, 0, 0, 2, 0},
{0, 6, 7, 5, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 4, 0, 2, 6, 9},
{9, 0, 0, 0, 8, 0, 0, 1, 0},
{0, 5, 0, 3, 2, 0, 0, 4, 0},
{0, 7, 0, 0, 0, 5, 1, 0, 0},
{0, 0, 0, 4, 0, 1, 5, 3, 0}
};
setNum(0, 0, board);
return 0;
}