深度优先算法,其中依旧嵌套了递归的思想。算法的底层思路等价于二叉树前序遍历的逻辑。
深度优先,广度次要。优先对最深层次的所有元素进行检查,若所有元素均不符合,再回溯到倒数第二层次,对该层次的要素进行检查,然后立马再次深入到最深层次。当第二层次均不符合题意后,再回溯到倒数第三层次.......以此类推。
回溯:DFS常用名词,意指退回到准备检查该层次时的状态)
易错点:回溯时,一定要记得擦除回溯前的状态
DFS题型总结:
1.题目要求是否存在一种排列方式符合题意(有解)(存在性问题)
以蓝桥杯飞机降落题目为例
一般做法:通常使用for循环+递归的模版。递归是为了进去更深层次,循环是为了进行广度检查。
需要注意的是,利用暴搜解这种题型时,一定是元素可以任意排列组合的题目。
像蓝桥杯2023年第十四届省赛真题-接龙数列,这种题目,在进行暴力搜索时一定要注意,题目所给数列中的数是不能任意排列的。
使用DFS时,通常使用for循环+递归的模版。递归是为了进去更深层次,循环是为了进行广度检查。
求解过程中,一般需要将这些元素进行标记,为了防止在排列时出现了同一个元素的重复排列。
标记:通常采用定义一个全局变量性的数组int mark[],注意索引与数组的大小一定要与原数组(即储存所有飞机的数组)对应索引、数组大小保持一致,1表示被标记(即原数组中对应的飞机已被拿出去排列了),0表示未标记,说明原数组中对应的飞机可被拿出去排列。
缺点:如果不做剪枝优化的话,当测试用例比较大时,DFS的事件复杂度很容易超时。
2. 最优解问题的暴搜(容易超时,只能拿部分分)
以蓝桥杯接龙排序(最长子数列问题)举例
算法思路:定义一个值用于记住当前的最长子数列,然后枚举所有符合题意的子数列,再对当前的最长子数列进行迭代。迭代完毕的值即是所求的最长子数列
代码骨架依旧是for循环+递归模版。
为了求出最长子数列,一般会定义一个全局变量数组ans,用以接收搜索出的符合题意的子数列,然后迭代出最长子数列。
求最长子数列的长度:仍要采取迭代法。定义一个全局变量A,记录当前ans所接收的数列长度。一个计数器cout,记录当前正在排列的子数列的长度
递归过程中,每确定好一个排列数,就++cout,当一个子数列确定好时,就将A与cout进行比较,谁大就取谁的值放入A中。
子数列排列好的标志:
1.在这个栈帧中,发现该栈帧接收的前一个排列数的在数列中的索引已经大于该数列的最大索引。此时说明该子数列结束。
return的作用:去接着寻找其他有序子数列
if (index >= N - 1) {
//到此时说明,找到一次合法的排列顺序,进行一次判定
A = cout > A ? cout : A;
cout = 0;
return;
}
2.程序在遍历了整个for循环后,仍没有找到符合题意的排列数,说明,该子数列的有序排列到此结束。因为:母数列中剩下的元素已无法和该子数列再构成有序排列。
return的作用:去接着寻找其他有序子数列
for(...){
.......
}
A = cout > A ? cout : A;
--cout;
return;
由此可见:DFS十分符合一句俗语“不撞南墙不回头” ,回头动作即为上述的两种return。
这里的南墙即为上述两种情况:
1.递归到nums数组最后一个元素时,仍然符合题意,再递归进去,发现index>N-1,回头。
2.在某一次递归的栈中,for循环检查所有剩余nums元素均不符合题意,于是for循环结束,子数列排列结束,回头。
以下是接龙序列完整代码。
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
typedef long long ll;
//写一个函数用于求一个数的最高位的数字
int getMaxNumber(ll x) {
int tar = -1;
while (x) {
tar = x % 10;
x /= 10;
}
return tar;
}
//写一个函数用于求一个数的最高位的数字
int getMinNumber(ll x) {
int tar = -1;
tar = x % 10;
return tar;
}
//储存排列好的有序列
ll ans[11000] = { 0 };
//记住当前的最长子数列
int cout = 0;
int A = -1;
int end = -1;
//index是上一个排列数在nums中的索引
void DFS(int N, ll* nums, int index) {
if (index >= N - 1) {
//到此时说明,找到一次合法的排列顺序,进行一次判定
A = cout > A ? cout : A;
cout = 0;
return;
}
for (int i = index + 1 ; i < N; i++) {
//此处非法
if (getMaxNumber(nums[i]) != getMinNumber(ans[end])
&& index >= 0
) {
continue;
}
//此处合法
++cout;
ans[++end] = nums[i];
DFS(N, nums,i);
//程序到这进行循环回溯,去搜索其他的可行解
}
//程序进到这里时,排列结束
A = cout > A ? cout : A;
--cout;
return;
}
int main() {
int N = 0;
scanf("%d",&N);
ll* nums = (ll*)malloc(sizeof(ll) * N);
for (int i = 0; i < N;++i) {
scanf("%lld", &nums[i]);
}
//将所有的数的最高位与最低位储存起来
int* Maxnums = (int*)malloc(sizeof(int) * N);
int* Minnums = (int*)malloc(sizeof(int) * N);
for (int i = 0; i < N; ++i) {
Maxnums[i] = getMaxNumber(nums[i]);
Minnums[i] = getMinNumber(nums[i]);
}
//开始暴力搜索
DFS(N, nums,-1);
printf("%d", N-A);
return 0;
}