コンテンツ
序文
このブログは、バックトラッキングアルゴリズムの問題解決ルーチンフレームワークからのものです:: labuladongのアルゴリズムチートシート(gitee.io)、ここで研究ノートを作成し、印象を深めるためにみんなと一緒に学びたいと思います。
ビデオバージョン:[labuladong]バックトラッキングアルゴリズムのコアルーチンの詳細な説明_ビープマイル_bilibili
基本コンセプト
バックトラッキングアルゴリズムは、実際には列挙に似た検索試行プロセスです。これは主に、検索試行プロセス中に問題の解決策を見つけることです。解決条件が満たされていないことが判明すると、「バックトラック」して別の試行に戻ります。道。バックトラッキング方式は最適な検索方式であり、目標を達成するために最適な条件に従って前方に検索します。ただし、探索が特定のステップに到達し、元の選択が最適でないか、目標を達成できないことが判明した場合、ステップバックして再選択します。これは「バックトラッキングポイント」と呼ばれます。多くの複雑で大規模な問題は、「普遍的な問題解決方法」として知られているバックトラッキング方法を使用できます。
基本的なフレームワーク
1.バックトラッキングアルゴリズムは、ブルートフォース枯渇法の一種です。
2.徹底的なプロセスは、実際にはマルチフォークツリーをトラバースするプロセスです。
3.バックトラッキングアルゴリズムのコードフレームワークは、マルチツリートラバーサルコードフレームワークに似ています
バックトラッキングの特定のノードに立って、3つの質問について考える必要があります。
- パス:現在行っている選択
- 選択リスト:現時点で可能な選択
- 終了条件:つまり、決定木の最下部に到達すると、これ以上選択できなくなります。
フレーム:
result=[];
def backtrack(路径,选择列表)
if 满足结束条件:
result.add(路径);
return
for 选择 in 选择列表
做选择
backtrack(路径,选择列表)
撤销选择
上記のコードの核となる部分は、forループで選択を行い、再帰して、選択を元に戻すことです。おそらく、上記の3つの質問についてまだ混乱しているかもしれませんが、よく理解できていません。問題は、慌てる必要はありません。ここで言及し、印象を残してください。次に、完全な順列とNクイーンの2つの古典的なトピックを取り上げ、それらを詳細に分析します。
例:完全な配列
この質問を受けたとき、カプセルについてどう思いますか?まず、{1,2,3}の例を見てみましょう。通常、数値を1に固定し、2桁目は2、最後の桁は3、2桁目は3、最後の桁は3になります。 2を取る。そして最後の2つを徹底的に列挙します...
これは紛らわしいように聞こえるので、理解しやすいようにバックトラックツリーを描画しましょう
このバックトラッキングツリーによると、ルートから順番にトラバースされます。これは実際にはすべての順列です。このバックトラッキングツリーを決定木と呼ぶこともできます。私たちがこれを言う理由は、私たちが次にどこに行くかを決めるために各ノードに立っているからです。私たちが1に立っている場合、写真を見てください。
私たちは今2つか3つを選ぶことができます、なぜ私たちは以前に歩いたことがあり、完全な配置は繰り返しを許さないので、1つのカプセルを選ぶことができないのですか?
1はパスで、移動したパスを記録します。2と3は選択リストで、現在選択できるパスを示します。終了条件は、トラバーサルツリーの最下部のリーフノードが空で、選択リストが空の場合です。
私たちが定義する関数は、 backtrack
実際にはポインターのようなものであり、このツリー内を歩き、各ノードの属性を正しく維持します。ツリーの最下部に移動するたびに、その「パス」は完全に配置されます。
実際、これはツリートラバーサルの問題であり、マルチフォークツリーのトラバーサルフレームワークは次のようになります。
void traverse(TreeNode root) {
for (TreeNode child : root.childern) {
// 前序遍历需要的操作
traverse(child);
// 后序遍历需要的操作
}
}
いわゆるプレオーダートラバーサルとポストオーダートラバーサルは、2つの異なる時点でのみ行われるものです。プレオーダートラバーサルは、特定のノードに入る前の時点でのトラバーサルであり、ポストオーダートラバーサルは、その後の時点でのトラバーサルです。特定のノードに入る。トラバース。
今言ったことを思い出してください。「パス」と「選択」は各ノードの属性です。関数がツリーを上に移動するときにノードの属性を適切に維持するには、次の2つの特別な時点で何かを行う必要があります。
バックトラックのコアフレームワークを理解しましたか?
for 选择 in 选择列表:
# 做选择
将该选择从选择列表移除
路径.add(选择)
backtrack(路径, 选择列表)
# 撤销选择
路径.remove(选择)
将该选择再加入选择列表
コード:
List<List<Integer>> res = new LinkedList<>();
/* 主函数,输入一组不重复的数字,返回它们的全排列 */
List<List<Integer>> permute(int[] nums) {
// 记录「路径」
LinkedList<Integer> track = new LinkedList<>();
// 「路径」中的元素会被标记为 true,避免重复使用
boolean[] used = new boolean[nums.length];
backtrack(nums, track, used);
return res;
}
// 路径:记录在 track 中
// 选择列表:nums 中不存在于 track 的那些元素(used[i] 为 false)
// 结束条件:nums 中的元素全都在 track 中出现
void backtrack(int[] nums, LinkedList<Integer> track, boolean[] used) {
// 触发结束条件
if (track.size() == nums.length) {
res.add(new LinkedList(track));
return;
}
for (int i = 0; i < nums.length; i++) {
// 排除不合法的选择
if (used[i]) {
// nums[i] 已经在 track 中,跳过
continue;
}
// 做选择
track.add(nums[i]);
used[i] = true;
// 进入下一层决策树
backtrack(nums, track, used);
// 取消选择
track.removeLast();
used[i] = false;
}
}
ブルーブリッジカップのはしごでよく見られるのは、次のコードです
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
int ants ;
int a[10] = { 0,1,2,3,4,5,6,7,8,9 };
void f(int k)
{
if (k == 10)
{
//这里一般还会有个判断条件
return;
}
//从k位开始的每个字符,都尝试放在K这个位置
for (int i = k; i < 10; i++)
{
{int temp = a[i]; a[i] = a[k]; a[k] = temp; }//把后面的每个数字都换到k位
f(k + 1);
{int temp = a[i]; a[i] = a[k]; a[k] = temp; }//回溯
}
}
int main()
{
f(0);
cout << ants << endl;
return 0;
}
C ++を学んだ友人は、next_permutationを使用して直接実装できることを知っています。
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
int ants;
int a[10] = { 0,1,2,3,4,5,6,7,8,9 };
int main()
{
do {
ants++;
} while (next_permutation(a, a + 10));
//f(0);
cout << ants << endl;
return 0;
}
もちろん、この質問は、接頭辞の合計、反復、およびその他の方法で行うこともできます。興味のあるパートナーは、下に行って試してみることができます。これまで、バックトラッキングアルゴリズムのフレームワークを紹介してきました。次の記事では、N個のクイーンを解きます。