生成从n选m的所有排列算法_L2算法基础第15课 深搜

L2-算法基础-第15课 深搜

搜索

搜索, 其实就是在一堆东西中找到自己想要的那个.

在知识爆炸的现在, 难的不是获取知识, 而是找到自己需要的知识.

1998年8月底的一天,两位斯坦福大学毕业生佩奇和布什当着Sun联合创始人安迪·贝克托 斯海姆(Andy Bechtolsheim)的面点击了几下鼠标,就获得了后者10万美元的投资。他们当时向贝克托斯海姆演示了一项新型互联网搜索技术。后来他们创立了 google, 是最大的搜索引擎.

定义

搜索算法是利用计算机的高性能来有目的的穷举一个问题的部分或所有的可能情况,从而求出问题的解的一种方法。搜索过程实际上是根据初始条件和扩展规则构造一棵解答树并寻找符合目标状态的节点的过程。

在算法竞赛中,枚举、深度优先搜索(DFS)、广度优先搜索(BFS)是最常使用的搜索方法.

用好搜索算法, 基本上可以拿到提高组省一.

输出全排列

P1706 全排列问题

  • 「题目描述」

输出自然数 1 到 n 所有不重复的排列,即 n 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。

  • 「输入格式」

一个整数 n。

  • 「输出格式」

由 1∼n 组成的所有不重复的数字序列,每行一个序列。

每个数字保留 5 个场宽。

  • 「输入输出样例」

    • 输入 #1复制

3

  • 输出 #1复制
    1    2    3
    1    3    2
    2    1    3
    2    3    1
    3    1    2
    3    2    1

说明/提示

1≤n≤9

  • 「分析」

全排列的方案数是

3个数, 有3个位置, 第一个位置有3种选择, 第二个位置有2种选择, 最后一个位置有1种选择.

设3个位置分别是 i j k, 每个位置一开始有3个选择, 前面得选中后, 后面的位置选择的数就会减少.

暴力枚举的程序:

for(int i = 1; i <= 3; i++)
    for(int j = 1; j <= 3; j++)
        for(int k = 1; k <= 3; k++) {
            if(_) //程序填空
                printf("%d %d %d\n", i, j, k);
        }

暴力枚举的过程其实就是3个位置可选数的枚举. 可以看成是给每个位置寻找一个合适的数.

n个数时, 我们可以枚举n个位置, 每个位置选择不同的数. 怎么知道哪个数被选了, 哪些数没被选呢? 可以开一个vis[]数组, 记录选/没选的情况.

每个位置的选择方法是一样的, 写成一个函数:

// ans存放选择的数  vis记录是否被选过了
int ans[N];
int vis[N];
void select(int pos) // 给pos位置选个数吧{
    for(int i = 1; i <= n; i++){
        if (vis[i] == 0) {
            ans[pos] = i;
            vis[pos] = 1;
            break; // 选中 i  这个数, 退出.
        }
    }
}
for(int pos = 1; pos <=n ; pos++) {
    //给每个位置选个数
    select(pos);
}

上述代码可以查找出一种方案. 可是其他方案怎么办? 怎么记录哪种方案选过, 哪种没选呢?

以前学过的for循环实现的暴力枚举就不太容易实现上述功能了(当然可以模拟栈, 或者二维数组).

这时就得用到递归 + 回溯了.

dfs(int now)表示查找第now个位置可以放哪个数字(从小到大选取)。

ans[i]表示第i个位置选取的数字。

vis[i]表示数字i是否被选取,值为1表示选取, 0为未选取

void dfs(int pos){
 int i;
 if(pos == n + 1) { // 找到了n个数, 输出.  递归出口
  for(i = 1; i <= n; i++) {
   printf("%5d", ans[i]);
  }
  printf("\n");
  return;
 }

 for(i = 1; i <= n; i++) {
  if(vis[i]) // 如果被选过了, 找下一个
   continue;
  ans[pos] = i; //选中 i
  vis[i] = 1;   // 记录选中
  dfs(pos + 1); // 继续下一个位置的选择
  vis[i] = 0;   //回溯, 下一个排列
 }
}

深度优先搜索(Depth-First-Search -- dfs)

深搜定义

深度优先搜索(Depth-First-Search)它的思想是从一个顶点 V0 开始,沿着一条路一直走到底,如果发现不能到达目标解,那就返回到上一个节点,然后从另一条路开始走到底,这种尽量往深处走的概念即是深度优先的概念。

dfs框架

void dfs()//参数的个数根据实际情况确定{
    if(到达终点状态)
    {
        //根据题意添加
        return;
    }
    if(越界或不合法状态)
        return;
    if(特殊状态) // 剪枝
        return;
    for(扩展方式)
    {
        if(扩展方式所达到状态合法)
        {
            修改操作;
            标记;
            dfs();
            (还原标记);
            //是否还原标记根据题意
        }

    }
}

数的拆分

P2404 自然数的拆分问题

  • 「题目描述」

任何一个大于1的自然数n,总可以拆分成若干个小于n的自然数之和。现在给你一个自然数n,要求你求出n的拆分成一些数字的和。每个拆分后的序列中的数字从小到大排序。然后你需要输出这些序列,其中字典序小的序列需要优先输出。

  • 「输入格式」

输入:待拆分的自然数n。

  • 「输出格式」

输出:若干数的加法式子。

  • 「输入输出样例」

    7

    • 输出 #1复制
    • 输入 #1复制
    1+1+1+1+1+1+1
    1+1+1+1+1+2
    1+1+1+1+3
    1+1+1+2+2
    1+1+1+4
    1+1+2+3
    1+1+5
    1+2+2+2
    1+2+4
    1+3+3
    1+6
    2+2+3
    2+5
    3+4
  • 「分析」

和前面讲的全排列很像, 很典型的一道深搜,一路搜到底得到一种方案,本次方案排列完毕后,回溯搜索下一方案.

如何寻找下一个数?

那么怎么保存方案呢?

需要恢复现场吗?

和全排列有什么不一样呢?

参考代码
#include 
using namespace std;

int n, a[200];

void dfs(int ai, int k){
 if(k == 0) {
  if(ai == 2)
   return;
  for(int i = 1; i    if(i == ai - 1)
    cout <endl;
   else
    cout <"+";
  return;
 }

 for(int i = a[ai-1]; i <= k; i++) {
  a[ai] = i;
  dfs(ai+1, k - i);
 }
}

int main(){
 cin >> n;
 a[0] = 1;
 dfs(1, n);
 return 0;
}

迷宫/寻路问题

模板
void dfs(int x,int y){
    if (x,y都与目标点相同)
    {
        得到一个解;
        return;
    }


    for (int i = 1; i <= 四个方向; i++)
        if (满足进一步搜索条件)
        {
            为进一步搜索所需要的状态打上标记;
            dfs(to_x, to_y);
                恢复到打标记前的状态;//也就是回溯一步
        }
}

例题

P1605 迷宫

  • 「题目背景」

给定一个N*M方格的迷宫,迷宫里有T处障碍,障碍处不可通过。给定起点坐标和终点坐标,问: 每个方格最多经过1次,有多少种从起点坐标到终点坐标的方案。在迷宫中移动有上下左右四种方式,每次只能移动一个方格。数据保证起点上没有障碍。

  • 「题目描述」

  • 「输入格式」

第一行N、M和T,N为行,M为列,T为障碍总数。第二行起点坐标SX,SY,终点坐标FX,FY。接下来T行,每行为障碍点的坐标。

  • 「输出格式」

给定起点坐标和终点坐标,问每个方格最多经过1次,从起点坐标到终点坐标的方案总数。

  • 「输入输出样例」

    • 输入 #1复制
    2 2 1
1 1 2 2
1 2
  • 输出 #1复制
    1
  • 「说明/提示」

【数据规模】

1≤N,M≤5

  • 「分析」

直接套用模板吧

#include
using namespace std;

int n,m,coun=0,t;
int sx,sy,fx,fy;
int mp[6][6];
int vis[6][6]={0};//全为没走过
int dx[4]={0, 0,1,-1};
int dy[4]={1, -1,0,0};
int b,c;//障碍坐标

void dfs(int x,int y){
 if(x==fx&&y==fy)//如果到达
 {
  coun++;
  return ;//方案+1
 }
 else{
  for(int i=0;i<=3;i++)
  {
   if(mp[x+dx[i]][y+dy[i]]==1&&vis[x+dx[i]][y+dy[i]]==0)//如果没有障碍且没有走过
   {
    vis[x][y]=1;//标记为已走过
    dfs(x+dx[i],y+dy[i]);//继续搜索
    vis[x][y]=0;//回溯一步
   }
  }
 }
}
int main(){
 cin>>n>>m>>t;
 cin>>sx>>sy>>fx>>fy;
 vis[sx][sy]=1;
 for(int i=1;i<=n;i++)
 {
  for(int j=1;j<=m;j++)
  {
   mp[i][j]=1;//全标记为空地
  }
 }
 for(int i=1;i<=t;i++)
 {
  cin>>b>>c;
  mp[b][c]=0;//标记障碍
 }
 dfs(sx,sy);//开始搜索
 printf("%d",coun);
 return 0;
}
思考

如何输出所有可行的解 ?

能解决的问题规模有多大 ?

题单

  • P1219 [USACO1.5]八皇后 Checker Challenge
  • P2392 kkksc03考前临时抱佛脚
  • P1135 奇怪的电梯
  • P1036 选数
  • P2036 [COCI2008-2009#2] PERKET
  • P1605 迷宫
  • P1019 单词接龙
  • P1101 单词方阵
  • P2404 自然数的拆分问题
  • P1596 [USACO10OCT]Lake Counting S

云帆优培老师联系方式:

云帆老师

微信:

7fb22adc5c6c0a34230bfb9b80b496a9.png

云帆优培介绍

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值