深度优先搜索(暴力搜算)初步感悟

本文详细阐述了深度优先搜索(DFS)算法,包括其递归思想和与二叉树前序遍历的关系,以及在解决存在性问题和最优解问题时的应用,如接龙数列和最长子数列。文章还强调了回溯和标记的重要性,以及暴力搜索在特定问题中的局限性。
摘要由CSDN通过智能技术生成

深度优先算法,其中依旧嵌套了递归的思想。算法的底层思路等价于二叉树前序遍历的逻辑。

深度优先,广度次要。优先对最深层次的所有元素进行检查,若所有元素均不符合,再回溯到倒数第二层次,对该层次的要素进行检查,然后立马再次深入到最深层次。当第二层次均不符合题意后,再回溯到倒数第三层次.......以此类推。

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

  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值