DFS

DFS复盘

0. 写在前面

先把DFS的精髓记录一下,后续就想着如何应用就好.....

DFS搜索

在搜索算法 中,该词常常指利用递归函数方便地实现暴力枚举的算法,与图论中的 DFS 算法有一定相似之处,但并不完全相同。(摘自wiki

​ 例如正整数n=6,将其分解为6=1+2+3。

​ 满足条件:后面的数>前面的数

​ 要求:输出所有分解方案。

咱刚学C语言那会儿,正常思路基本是for循环暴力枚举...当然...仅限于题目要求我们的项数很少的情况下,不然时间复杂度就是跟幂函数一样,指数级增加了...

for (int i = 1; i <= n; ++i)
  for (int j = i; j <= n; ++j)
    for (int k = j; k <= n; ++k)
      if (i + j + k == n) printf("%d = %d + %d + %d\n", n, i, j, k);

但其实我们学过递归以后,更优的方法是可以逐项递归搜索枚举出来...

参数说明:

参数n是记录还剩下多少数。

参数i记录项数,要求是小于总项数m

参数a是记录本次搜索开始的数(即这一项从何数开始for循环)

IMG_20220221_195016.jpg

(画的些许潦草,将就看看....)

int m, rec[103];  // rec 用于记录方案
void dfs(int n, int i, int a) {
  if (n == 0) {
    for (int j = 1; j <= i - 1; ++j) printf("%d ", rec[j]);
    printf("\n");
  }
  if (i <= m) {
    for (int j = a; j <= n; ++j) {
      rec[i] = j;
      dfs(n - j, i + 1, j);  // 请仔细思考该行含义。
    }
  }
}
// 主函数
// n为待拆分数,m为拆分的项数
scanf("%d%d", &n, &m);
dfs(n, 1, 1);

dfs(int n,int i,int a)函数的要点:

  1. 题目说要求多少项,那需要一个项数i吧,我们搜索就是一项一项的往下搜索的。

  2. 那我们判断有没有结束,总得有一个剩下的数n吧,如果N-sum(arr)=0的话,就说明可以分解相加了。

  3. 题目说要后面的数比前面大,那我们for循环的起始位置得从上一层搜索中前一位的数j拿出来吧,这就是参数a。

最后用record数组保存在搜索过程中的数字。


1. n的全排列问题

输入

3

输出

​ 1 2 3
​ 1 3 2
​ 2 1 3
​ 2 3 1
​ 3 1 2
​ 3 2 1

给出一套dfs框架

//  深度优先搜索算法框架2
// int Search(int k)
//  {
//    if  (到目的地) 输出解;
//    else
//     for (i=1;i<=算符种数;i++)
//      if  (满足条件)
//        {
//         保存结果;
//                      Search(k+1)
//                              恢复:保存结果之前的状态{回溯一步}
//        }
//  }

来看状态(搜索)树

dfs(1)时

image.png

dfs(2)时

image.png

源码如下

#include<stdio.h>

int n,ans[15],vis[15];

void dfs(int index)
{
    if(index>n)
    {
        for(int i=1;i<=n;i++) printf("%5d",ans[i]);
        printf("\n");
    }
    else
    {
        for(int i=1;i<=n;i++)
        {
            if(vis[i]==0)
            {
                vis[i]=1;
                ans[index]=i;
                dfs(index+1);
                vis[i]=0;
            }
        }
    }
}

int main()
{
    scanf("%d",&n);
    dfs(1);
    return 0;
}

2. 女王占地盘的决策(地盘要求每行每列=1,每斜线<=1)

在6*6地图中,女王占地盘的一种情况如下:

image.png

首先,我们想想,如果我们要用深度优先搜索的话,从哪开始往下挖呢??根据前面6=1+2+3的一项一项的启发,再加上此题目的要求是输出每一行的列数,那我们是不是可以一列一列(line)地搜索哪一行(row)满足条件可以占领呢? 参数也只需要给一个列数line即可,后续递归dfs(line+1)就可以做下一列搜索了,搜索完的时候line>n表示结束, 然后根据题目要求顺带输出。

先复盘一下整个题目的思路:

女王想要占领地盘的话,就让臣民一行行(row++)往后找,如果满足条件,就占领,改变相应参数!

满足条件就往下一列继续找dfs(line+1), 即考虑占领下一个地盘,不行的话就让臣民们回到一开始的状态(回溯),让臣民们继续在下一行(dfs)看看满不满足占领的条件。

这是dfs搜索的基本思路。

dfs最外面框架先写好。

void dfs(int line)
{
  if(line>n)
  {
	print();
    return ;
  }
  for((int row=1;i<=n;i++))
  {
     //.....
  }
}

那么省略的内容就是:

  1. 臣民判断咱占领的条件是啥?

如果可以占领:

  1. 怎么表示我们已经占领了?

  2. 怎么往下一层找?(dfs(line+1))

  3. 如果找不到了或者找到了,怎么回到一开始的那一层继续往后一行(row)找??(回溯)

最初判断能否占领的想法是用二维数组,不过这个条件有点类似于数独,需要好多个for循环,然后对角线又很麻烦,所以让本菜鸟放弃了挣扎。

这边比较好的方法是申请四个一维数组,分别表示列str_line[]、行str_row[],左对角线left_slope[],右对角right_slope[]

这边还有一个难点就是判断对角线的时候:

n=5时

比如(1,3)左对角线,有(2,2),(3,1) 满足如下条件

line+row=const1;

比如(1,3)右对角线,有(2,4),(3,5) 满足如下条件

line-row=const2;

这里注意const2可能为负数,而数组right_slope[]下标不能为负数。解决方法是在原有基础上+n, 后续判断也同样+n,即

right_slope[line-row+n]=1

数组的值初始化为0,表示满足条件,可以占领,表示臣民占领成功以后就分别设置为非0, 这边因为要输出具体的列,所以str_line=满足条件的行数i.

for(int row=1;row<=n;row++)
{
    if(!str_row[row]&&!left_slope[line+row]&&!right_slope[line-row+n])
    //因为臣民是一列列(line++)搜索的,那他找的那一列一定是都没有被占领过的,所以这里判断和回溯的时候少写了这种情形
    {
        str_line[line]=row;	//表示第line列不可访问,且返回值为其列数
        str_row[row]=1;		//表示第row行不可访问
        left_slope[row+line]=1;
        right_slope[line-row+n]=1;
        dfs(line+1);
        str_row[row]=0;
        left_slope[row+line]=0;
        right_slope[line-row+n]=0;  //回溯
    }
}

最后组合在一起加个输入输出,让臣民从first line开始找,那就是源码了,女王的臣民也终于可以按照她的指示开工了嘻嘻。

#include<stdio.h>

int n;
int str_line[30],str_row[30],left_slope[30],right_slope[30];
int ans;

void print()
{
    if(ans<=2)
    {
        for(int i=1;i<=n;i++) printf("%d ",str_line[i]);
        printf("\n");
    }
    ans++;
}

void dfs(int line)
{
	if(line>n)
	{
		print();
		return ;
	}

	for(int row=1;row<=n;row++)
	{
        if(!str_row[row]&&!left_slope[line+row]&&!right_slope[line-row+n])
        {
            str_line[line]=row;	//表示第line列不可访问,且令值为列数row方便输出
            str_row[row]=1;		//表示第row行不可访问
            left_slope[row+line]=1;
            right_slope[line-row+n]=1;
            dfs(line+1);		//往下一层找
            str_row[row]=0;
            left_slope[row+line]=0;
            right_slope[line-row+n]=0;  //回溯
        }
    }
}

int main()
{
    scanf("%d",&n);
    dfs(1);
    printf("%d\n",ans);
    return 0;
}

附:框架

 深度优先搜索算法框架2
int Search(int k)
 {
   if  (到目的地) 输出解;
   else
    for (i=1;i<=算符种数;i++)
     if  (满足条件)
       {
        保存结果;
                     Search(k+1)
                             恢复:保存结果之前的状态{回溯一步}
       }
 }

posted @ 2022-02-22 23:28  yuezi2048  阅读(6)  评论(0编辑  收藏  举报