<<The algorithm is beautiful>>——(5)——Core framework of backtracking algorithm (1)

content

foreword

basic concept

basic framework 

Example: full array


foreword

This blog is from the backtracking algorithm problem-solving routine framework:: labuladong's algorithm cheat sheet (gitee.io) , I want to make a study note here and learn with everyone to deepen the impression.

Video version: [labuladong] Detailed explanation of the core routine of backtracking algorithm _ beep mile _bilibili

basic concept

The backtracking algorithm is actually a search attempt process similar to enumeration, which is mainly to find the solution of the problem during the search attempt process. The backtracking method is an optimal search method , which searches forward according to the optimal conditions to achieve the goal. However, when the exploration reaches a certain step, and it is found that the original choice is not optimal or fails to achieve the goal, it will take a step back and re-selection. called "backtracking points". Many complex and large-scale problems can use the backtracking method, which is known as the "universal problem-solving method" 

basic framework 

1. The backtracking algorithm is a kind of brute force exhaustion method

2. The exhaustive process is actually the process of traversing a multi-fork tree .

3. The code framework of the backtracking algorithm is similar to the multi-tree traversal code framework

Standing at a certain node of the backtracking, we need to think about three questions:

  • Path: the choice we currently make
  • Choice list : the choices we can make at the moment
  • End condition : that is, reaching the bottom of the decision tree, no more choices can be made

frame:

result=[]; 

def backtrack(路径,选择列表)

     if 满足结束条件: 

        result.add(路径);

        return 

    for 选择 in 选择列表 

        做选择

        backtrack(路径,选择列表)

        撤销选择
 

The core part of the above code is to make a selection in the for loop, then recurse, and then undo the selection. Maybe I am still confused about the above three questions, I don't understand it very well, it doesn't matter, don't panic, just mention it here , just leave some impressions. Next, I will do two classic topics of full permutation and N queen with you, and analyze them in depth.

Example: full array

How do we think about the capsule when we get this question? First, let's take an example of {1,2,3}. We usually fix a number 1, then the second digit can take 2, the last digit can take 3, or the second digit can take 3, and the last digit can take 2. And then exhaustively enumerate the last two...

This sounds confusing, so let's draw a backtracking tree to help us understand

 According to this backtracking tree, it is traversed from the root in turn, which is actually all permutations. We might as well call this backtracking tree a decision tree. The reason why we say this is that we stand at each node to decide where to go next. Looking at the picture, if we stand at 1.

 We can choose 2 or 3 now, why can't we choose 1 capsule, because we have already walked before, and the full arrangement does not allow repetition.

1 is the path, recording the path we have traveled . 2 and 3 are the selection list , indicating the path we can currently choose . The end condition is when the bottom leaf node of the traversal tree is empty and the selection list is empty .

 The function we define is  backtrack actually like a pointer. It walks in this tree and maintains the attributes of each node correctly. Whenever we go to the bottom of the tree, its "path" is a full arrangement.

In fact, it is a tree traversal problem, and the traversal framework of multi-fork tree is like this:

void traverse(TreeNode root) {
    for (TreeNode child : root.childern) {
        // 前序遍历需要的操作
        traverse(child);
        // 后序遍历需要的操作
    }
}

 The so-called pre-order traversal and post-order traversal are only things done at two different time points. Pre-order traversal is traversal at the time point before entering a certain node, and post-order traversal is at the time point after entering a certain node. traverse.

 Recalling what we just said, "path" and "selection" are attributes of each node. To properly maintain the attributes of a node when a function travels up the tree, it needs to do something at these two special time points:

 Do you now understand the core framework of backtracking?

for 选择 in 选择列表:
    # 做选择
    将该选择从选择列表移除
    路径.add(选择)
    backtrack(路径, 选择列表)
    # 撤销选择
    路径.remove(选择)
    将该选择再加入选择列表

Code: 

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;
    }
}

 What we see more often in the Blue Bridge Cup ladder is the following code

 


#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;
}

Friends who have studied C++ know that they can be implemented directly with 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;
}

Of course, this question can also be done by prefix sum, iteration and other methods. Interested partners can go down and try it. So far, the framework of our backtracking algorithm has been introduced. In the next article, we will solve the N queens.

 

Guess you like

Origin blog.csdn.net/m0_58367586/article/details/123867947