前言
回溯算法篇 我想带领你把握回溯算法,这是上篇。
说要把握它,那至少得知道它是什么吧,所以我先讲回溯算法是什么。知道它是什么东西后,一个很自然的问题是它有什么用?他能解决什么类型的问题,也就是我们在什么场景下使用这类算法。这就是我论述的思路,好,开始~
论述
什么是回溯算法呢?了解回溯算法的本质之前,我们先看一个题,请跟着我的节奏一起思考。
题目是:
给定一个数n,求1-n范围中(包含1、n)所有的两个数的组合情况。
想到怎么做了吗?你肯定想到用两层for解决它,暴力枚举,这不就很容易就解决了吗?
// c语言版本
void print_combinations(int n)
{
for (int i = 1; i <= n; i++)
{
for (int j = i; j <= n; j++)
{
printf("%d %d\n", i, j);
}
}
}
不错,这是一种解决方案,而且对于这个问题,是最优解。但是面对的问题不是这样的呢?让情况更加复杂
给定两个数n,k。求1-n范围中(包含1、n)所有的k个数的组合情况。
你又该怎么办?你会说不就是更多嘛,k=3时,我嵌套3层,k=5时我就嵌套5层,你拿我没办法。但当你真正写代码的时候,你才会发现问题。因为这个代码结构根本就不是确定的!我无法嵌套!
我该怎么办?回溯算法来救你了,它说把我(for循环)放到递归中,让我在递归中完成它,告诉我递归几次就行。
回溯算法 这种方法给不确定层数for循环结构,创造了一种动态层数for循环结构,而这个层数正是由递归的深入所完成的。
上述问题就是leetcode上第77题https://leetcode.cn/problems/combinations/,以下是针对以上问题的回溯算法模块
void backtracking(int n, int k,int startIndex)
{
if(pathTop == k)
{
int* temp = (int*)malloc(sizeof(int) * k);
int i;
for(i = 0; i < k; i++)
{
temp[i] = path[i];
}
ans[ansTop++] = temp;
return ;
}
int j;
for(j = startIndex; j <=n ;j++)
{
//将当前结点放入path数组
path[pathTop++] = j;
//进行递归
backtracking(n, k, j + 1);
//进行回溯,将数组最上层结点弹出
pathTop--;
}
}
大家能看到,里面有两个for循环,从上到下第一个是一个赋值的操作,真正起作用的是下面的for循环,里面还有递归调用自己的逻辑。先不用看懂,先看个大概有个印象。其实这个模块就是在做这样一件事:通过一些条件来控制for循环的层数,来暴力枚举所有情况,将所有的组合收集起来。没错,回溯算法的本质就是暴力枚举,只不过是在递归中枚举罢了,而这也正是它的优点,可以根据情况动态的变化for循环的层数。
你现在应该知道回溯算法是什么了。第二个问题,什么时候用呢?到这了也可以自然回答上:如果当你想要暴力去枚举所有情况,而且循环的层数又是动态不确定的时候,你应该想到回溯算法,连接起、唤醒起你回溯算法相关的记忆。当然暴力枚举这种方式并不是好的方法,时间复杂度会很高,再加上是递归调用,时间更长了。所以应该不要首选就是回溯,面对具体问题去想想有没有其他可能。
知道了回溯算法是什么,以及什么时候想到去用这算法,接下来也就是重要的怎么去写出回溯算法了。这个一时半会还说不完,我们下回再聊
总结
回溯算法是什么?回溯算法其实就是通过暴力枚举来解决问题的一种方式。
什么时候用?当你想暴力枚举所有情况,并且循环层数还是动态变化的时候,你就选回溯。
~~~~~~
以上就是我本篇想讲的所有内容了,如果这篇文章对你有价值的话,还请点个赞,你的支持对我非常重要!
我是阿航,一位胆大包天、梦想成为大牛的学生~
我们下篇文章再聊