基本的な考え方:
バックトラッキング アルゴリズムの基本的な考え方は、1 つの道から進み、できる場合は進み、できない場合は戻り、別の道を試すというものです。8 クイーン問題は典型的なバックトラッキング アルゴリズムです。最初のステップではクイーンを順番に配置し、次に 2 番目のステップで 2 番目のクイーンを配置するための要件を満たします。要件を満たす位置がない場合は、その位置が最初のクイーンを変更し、2 番目のクイーンを再度配置する必要があります。条件を満たす位置が見つかるまで 2 つのクイーンの位置を変更します。深さ優先探索とジャンプ探索を備えたアルゴリズムです。
率直に言うと、バックトラッキング アルゴリズムは網羅的な手法ですが、枯渇の過程で枝刈り機能を使用して不要な検索をスキップし、最終状態に到達できない子ノードをスキップして、状態空間ツリーの数を削減します。ノードの生成
次に、バックトラッキングアルゴリズム、状態空間ツリーとは何か、状態空間ツリーを生成する方法、問題に応じて満たされていない状態を見つける方法、枝刈り関数のロジックを設定する方法についてさらに混乱します。
バックトラッキングの 2 つの方法の疑似コード:
1. 再帰的なバックトラッキング:
void Backtrack(int t){
if(t > n) Output(x);//Output 记录或者输出可行解
else{
for( int i = f(n,t); i <= g(n,t); ++i){
//f(n,t)和g(n,t)表示在当前结点未搜索过的子树的起始编号和终止编号
x[t] = h(i);
if(constraint(t) && Bound(t)) Backtrack(t+1);//constraint和bound分别是约束函数和界限函数
}
}
}
递归实现回溯非常容易理解,但是执行效率没有迭代高,根据需要,开辟大量的内存空间,来进行递归。
2. 反復的なバックトラッキング:
void IterativeBacktrack(void){
int t = 1;
while(t > 0){
if(f(n,t) < g(n,t)){
for(int i = f(n,t); i <= g(n,t); ++i){
//这个for 是遍历各个值的意思,实际中写成for循环会有逻辑错误
x[t] = h(i);
if(constraint(t) && bound(t)){
if(solution(t)) Output(x);//solution 判断是否已经得到问题的解
else t++;
}
else t--;
}
}
}
}
//迭代回溯相比较递归比较难理解,它的迭代过程就是解决问题的逻辑过程,不断进行循环,根据问题的规模,有时候时间复杂度也是不容乐观的。
バックトラッキング アルゴリズムの原理をよりよく理解するために、例として n クイーン問題を解決してみましょう。
例: 問題は、互いに攻撃できないように、n 個のクイーンを n × n グリッドのチェス上に配置することです。つまり、2 つのクイーンが同じ行、列、または斜めに配置できないようにする方法は何通りありますか。
この問題を単純な網羅法で解くとn^n個の検索結果が得られますが、網羅する場合は最初の列に全てのクイーンを配置し、クイーン同士が攻撃するかどうかを確認します。そうであれば、H 列のクイーンを移動して次の計画を確認します。最後まで移動した後、G 列のクイーンまで「キャリー」して 1 マス移動し、H 列のクイーンは n 行すべてを再試行します。この方法は非常に非効率であることは間違いありません。n が大きすぎる場合は、そこで、バックトラッキング アルゴリズムを見てみましょう。
アルゴリズムについて話す前に、状態空間ツリーとは何なのかを説明しましょう。
ここでは、状態空間ツリーとは何かを説明するために 4 つのクイーン問題を使用します。4
×4 の空のチェス盤をルートとして取り、まずチェス盤の最初の行と最初の列 (1, 1) にクイーン 1 を配置します。クイーン2を観察する クイーン2の場合、1列目と2列目には配置できないので、3列目を試してください クイーン2を(2,3)に配置すると、クイーン3は配置できないことがわかります後戻りすると、クイーン 2 を 4 列目に配置し、クイーン 3 を (3, 2) に配置できることがわかりますが、この時点で再び問題が発生し、クイーン 4 を防ぐことができません。そして行き止まりに入ったら、バックトラックを続け、クイーン 1 を配置すべき位置に戻り、クイーン 1 を 2 番目の列に配置する、ということを、可能な最後の配置ソリューションが見つかるまで続けます。これは、アルゴリズムの検索状態空間ツリーです。
ただし、上記は一部にすぎず、クイーン 1 が 2 列目に到達するまでのみです。他のメソッドも検索状態を持つことができますが、一般的な考え方は同じです。
検索プロセスは次のとおりです (例として 4 つのクイーンを取り上げます)。
クイーン問題を解くことでさらに興味深いのは、状態空間ツリーの各ノードが 2 次元空間であることです。コードを実装すると、次の各ステップが実行されます。明確な検索が可能コンピュータストレージのロジックに準拠。
これまでのところ、不満の状態は、各クイーンの位置で、同じ列の同じ対角線が不満であることがわかりました。検索プロセスでこの状態に遭遇したら、段階的に検索するときに、枝刈り機能(この質問ではif判定)を使用して、ジャンプ遡行検索を実行します。
まとめると、バックトラッキングを実行するときにバックトラッキングがどのようなロジックであるかは大まかに理解できましたが、バックトラッキングの難しい点は、スペース ツリーがサブセット ツリーなのかソート ツリーなのかを理解するために問題を十分に理解する必要があることです。コードを実装するときに検索プロセスをシミュレートする方法、使用するデータ構造、再帰または反復実装プロセスを使用するかどうか。(一般的な難しい問題の場合は、最初に再帰的な疑似コードを作成します。これは一般に思いつきやすく、次にその疑似コードに基づいて修正します) これが一般的な解決プロセスです。一言で言えば、バックトラックの考え方は非常にシンプルで理解しやすいですが、問題を解決するためにバックトラックする場合、コードの実装にはまだ一定の距離があり、多くの問題に直面し、より練習する必要があります。
コード:
ここでは反復的なバックトラッキングのみ
#include<stdio.h>
#include<iostream>
#include<cmath>
using namespace std;
//用一维数组存储,解决行冲突,数组下标为行数,数组值为列数
int x[10]={
0};
int count=0;
bool judge(int k)//判断第k行某个皇后是否发生冲突 ,发生冲突就跳过
{
int i=1;
while(i<k) //循环i到k-1之前的皇后;
{
if(x[i]==x[k]||abs(x[i]-x[k])==abs(i-k))//存在列冲突或者存在对角线冲突(实际上就是一种剪枝)
return false;
i++;
}
return true;
}
void queen(int n)
{
int i,k=1; //k为当前行号,从第一行开始
x[1]=0;//x[k]为第k行皇后所放的列号
while(k>0)
{
x[k]++; //首先从第一列开始判断
while(x[k]<=n&&!judge(k))//推导k行到底选择哪一列,如果当前该列不行则下一列,直到成立即可;
x[k]++;
if(x[k]<=n)
{
if(k==n)//输出所有解
{
for(i=1;i<=n;i++)
printf("第%d行的皇后:在第%d列 ",i,x[i]);
count++;
printf("-------这是其中第%d个解",count);
printf("\n");
}
else//判断下一行
{
k++; x[k]=0;
}
}
else k--;//没找到,回溯
}
return ;
}
int main()
{
int n;
cout<<"请输入你的n皇后: ";
cin>>n;//n最大值不超过10
queen(n);
return 0;
}