深度优先搜索 - DFS(暴搜)

DFS思路应用-穷举求解问题

在无路可走时,我们往往会选择搜索算法,因为我们期望利用计算机的高性能来有目的的穷举一个问题的部分甚至所有可能情况,从而在这些情况中寻找符合题目要求的答案。这也是“爆搜”之名的  由来

我们约定,对于问题的介入状态,叫初始状态,要求的状态叫目标状态
这里的搜索就是对实时产生的状态进行分析检测,直到得到一个目标状态或符合要求的最佳状态为止。对于实时产生新的状态的过程叫扩展(由一个状态,应用规则,产生新状态的过程)

搜索的要点:

  1. 选定初始状态,在某些问题中可能是从多个合法状态分别入手搜索;

  2. 遍历自初始状态或当前状态所产生的合法状态,产生新的状态并进入递归;

  3. 检查新状态是否为目标状态,是则返回,否则继续遍历,重复2-3步骤

对状态的处理:DFS时,用一个数组存放产生的所有状态

  1. 把初始状态放入数组中,设为当前状态;
  2. 扩展当前的状态,从合法状态中旬寻找一个新的状态放入数组中,同时把新产生的状态设为当前状态;
  3. 判断当前状态是否和前面的状态重复,如果重复则回到上一个状态,产生它的另一状态;
  4. 判断当前状态是否为目标状态,如果是目标目标状态,则找到一个解答,根据实际问题需求,选择继续寻找答案或是直接返回。
  5. 如果数组为空,说明对于该问题无解。


 

目录

842. 排列数字

843. n-皇后问题

P1036 [NOIP2002 普及组] 选数

题1与题3的结合练习:

        P1157 组合的输出


842. 排列数字

给定一个整数 n,将数字 1∼n 排成一排,将会有很多种排列方法。

现在,请你按照字典序将所有的排列方法输出。

输入格式

共一行,包含一个整数 n。

输出格式

按字典序输出所有排列方案,每个方案占一行。

数据范围

1≤n≤7

输入样例:

3

输出样例:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

 先画出递归搜索树

思路: 

  • 用 path 数组保存排列,当排列的长度为 n 时,是一种方案,输出。
  • 用 st 数组表示数字是否用过。当 st[i] 为 1 时:i 已经被用过,st[i] 为 0 时,i 没有被用过。
  • dfs(i) 表示的含义是:在 path[i] 处填写数字,然后递归的在下一个位置填写数字。
  • 回溯:第 i 个位置填写某个数字的所有情况都遍历后, 第 i 个位置填写下一个数字。
#include<iostream>
using namespace std;
const int N=10;
int n;
int path[N];
bool st[N];

void dfs(int u)
{
    if(u==n)
    {
        for(int i=0;i<n;i++) cout<<path[i]<<" ";
        puts("");
        return ;
    }
    for(int i=1;i<=n;i++)
        if(!st[i])
        {
            path[u]=i;
            st[i]=true;
            dfs(u+1);
            st[i]=false;
        }
    
}
int main()
{
    cin>>n;
    dfs(0);
    return 0;
}

843. n-皇后问题

n−皇后问题是指将 n 个皇后放在 n×n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。

现在给定整数 n,请你输出所有的满足条件的棋子摆法。

输入格式

共一行,包含整数 n。

输出格式

每个解决方案占 n 行,每行输出一个长度为 n 的字符串,用来表示完整的棋盘状态。

其中 . 表示某一个位置的方格状态为空,Q 表示某一个位置的方格上摆着皇后。

每个方案输出完成后,输出一个空行。

注意:行末不能有多余空格。

输出方案的顺序任意,只要不重复且没有遗漏即可。

数据范围

1≤n≤9

输入样例:

4

输出样例:

.Q..
...Q
Q...
..Q.

..Q.
Q...
...Q
.Q..

方法1、(DFS按行枚举) 时间复杂度O(n*n!)

代码分析

对角线 dg[u+i],反对角线udg[n−u+i]中的下标 u+i 和 n−u+i 表示的是截距

下面分析中的(x,y)相当于上面的(u,i)
反对角线 y=x+b 截距 b=y−x,因为我们要把 b 当做数组下标来用,显然 b 不能是负的,所以我们加上 +n (实际上+n+4,+2n都行),来保证是结果是正的,即 y - x + n
而对角线 y=−x+b, 截距是 b=y+x,这里截距一定是正的,所以不需要加偏移量

核心目的:找一些合法的下标来表示dg或udg是否被标记过,所以如果你愿意,你取 udg[n+n−u+i] 也可以,只要所有(u,i)对可以映射过去就行

总结:因为斜线斜率相同,所以唯一区分就是截距,截距相同则是同一条斜线
 

#include <iostream>
using namespace std;
const int N = 20; 

// bool数组用来判断搜索的下一个位置是否可行
// col列,dg对角线,udg反对角线
// g[N][N]用来存路径

int n;
char g[N][N];
bool col[N], dg[N], udg[N];

void dfs(int u) {
    // u == n 表示已经搜了n行,故输出这条路径
    if (u == n) {
        for (int i = 0; i < n; i ++ ) puts(g[i]);   // 等价于cout << g[i] << endl;
        puts("");  // 换行
        return;
    }

    //对n个位置按行搜索
    for (int i = 0; i < n; i ++ )
        // 剪枝(对于不满足要求的点,不再继续往下搜索)  
        // udg[n - u + i],+n是为了保证下标非负
        if (!col[i] && !dg[u + i] && !udg[n - u + i]) {
            g[u][i] = 'Q';
            col[i] = dg[u + i] = udg[n - u + i] = true;
            dfs(u + 1);
            col[i] = dg[u + i] = udg[n - u + i] = false; // 恢复现场 这步很关键
            g[u][i] = '.';

        }
}

int main() {
    cin >> n;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            g[i][j] = '.';

    dfs(0);

    return 0;
}   


方法2、(DFS按每个元素枚举)时间复杂度O(2^(n^2))

#include <iostream>
using namespace std;
const int N = 20;

int n;
char g[N][N];
bool row[N], col[N], dg[N], udg[N]; // 因为是一个个搜索,所以加了row

// s表示已经放上去的皇后个数
void dfs(int x, int y, int s)
{
    // 因为从0开始,y==n时超出边界,处理超出边界的情况
    if (y == n) y = 0, x ++ ;

    if (x == n) { // x==n说明已经枚举完n^2个位置了
        if (s == n) { // s==n说明成功放上去了n个皇后
            for (int i = 0; i < n; i ++ ) puts(g[i]);
            puts("");
        }
        return;
    }

    // 分支1:放皇后
    if (!row[x] && !col[y] && !dg[x + y] && !udg[x - y + n]) {
        g[x][y] = 'Q';
        row[x] = col[y] = dg[x + y] = udg[x - y + n] = true;
        dfs(x, y + 1, s + 1);
        row[x] = col[y] = dg[x + y] = udg[x - y + n] = false;
        g[x][y] = '.';
    }

    // 分支2:不放皇后
    dfs(x, y + 1, s);
}


int main() {
    cin >> n;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            g[i][j] = '.';

    dfs(0, 0, 0);

    return 0;
}

P1036 [NOIP2002 普及组] 选数

 

输入输出样例

输入

4 3
3 7 12 19

输出

1

思路 

不降原则
因为是求种类数 ,所以需要去重,有点麻烦,所以就按照从小到大的顺序全排列

例如,在1 2 3 4,四个数字中选三个数字
1 2 3
1 2 4
1 3 4
因为123跟213,231,312…虽然顺序不同,但是本体是求和,他们的和都相等,是重复的结果

所以,从大到小输出,避免重复

具体实现,看下列代码,dfs函数的参数ks,就代表当前选择的数字在全部数字中的位置是第ks个,
for循环也是从第ks个开始,省去了循环的时间,而且也在避免重复
之后dfs递归i+1,自动选择比它大的数字进行排序
 

#include<iostream>
#include<cmath>
using namespace std;
const int N=21;

int n,sum,k,tot;
int a[N];

int zs(int n)
{
	if(n<2) return 0;
	for(int i=2;i<=sqrt(n);i++)
		if(n%i==0) return 0;
	return 1;
}

void dfs(int m,int sum,int ks)//开始坐标防止重复
							  //如出现1+2+3和1+3+2 
{
	if(m==k)
	{
		if(zs(sum))
		{
			tot++;
		}
		return ;
	}
	
	for(int i=ks;i<n;i++)
	{
		dfs(m+1,sum+a[i],i+1);	//i+1,只需要升序加和就能防止重复 
	}
	return ;
}

int main()
{
	cin>>n>>k;
	for(int i=0;i<n;i++) cin>>a[i];
	dfs(0,0,0);	//层数,加和,开始坐标 
	cout<<tot;
	return 0;
}

题1与题3的结合练习:

P1157 组合的输出

输入 #1

5 3 

输出 #1

  1  2  3
  1  2  4
  1  2  5
  1  3  4
  1  3  5
  1  4  5
  2  3  4
  2  3  5
  2  4  5
  3  4  5

思路:画树+不降原则

#include<bits/stdc++.h>
using namespace std;

int n,k;
int a[22];
bool st[22];
void dfs(int m,int ks)
{
	if(m==k)
	{
		for(int i=0;i<k;i++)
		{
			printf("%3d",a[i]);
		}
		puts("");
	}
	
	for(int i=ks;i<=n;i++)
	{
		if(!st[i])
		{
			a[m]=i;
			st[i]=true;
			dfs(m+1,i+1);
			st[i]=false;
		}
	}
}

int main()
{
	cin>>n>>k;
	dfs(0,1);
	return 0;
} 

感觉搜索终于入门了......

  • 18
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NO.-LL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值