搜索算法 暴力 与 回溯

一.暴搜相关概念

暴搜算法(穷举算法)实际上是一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就返回,尝试别的路径。

暴搜就是通过不断的搜索将所有情况枚举出来,依次判断,求得最终的答案,因此,暴搜的时间复杂度通常很大,相应的数据范围较小。

暴搜本质还是运用了递归的思想,重复调用自身函数求得答案。

 二.相关例题

     需要将所有情况考虑的题目,都可以使用暴搜来写(但是耗时,经常TLE超时)

1.子集问题

求出元素为m个的子集合,直接暴搜即可

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
int n,m;
int a[1001],b[1001];
void dfs(int x,int sum) // x 为当前数组a下标 sum为子集数组b元素个数
{
    b[sum]=a[x]; // 加入子集合
    if(sum==m) // 如果 子集合元素个数为 m 则直接输出数组b
    {
        for(int i=1;i<=m;i++) printf("%d ",b[i]);
        printf("\n");
        return ; // 一定要返回,不然死循环
    }
    for(int i=x+1;i<=n;i++) 
    {
        dfs(i,sum+1);     // 不断向枚举多种情况 将数组a的第i个元素加入到子集合数组b中
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n-m+1;i++) // 从1考虑到n-m+1  因为n-m+1 ~ n 为m个元素,如果仍然往后搜索,一定没有答案
        dfs(i,1); // 以数组中第i个元素 为子集中的第一个元素
    system("pause");
    return 0;
}

 2.迷宫问题(八皇后问题)

 思路:①先通过暴搜的思想把所有 n 个棋子的位置放好,再考虑这种放置方法是否可行

            ②判断放置方法是否可行:任意两个棋子不出现在同一条直线上

                                 a.在同一行(列):只需要将所有棋子对应的行列记录下来,出现重复的方法即可直接舍去

                                b.在同一斜线上: 分为左斜线和右斜线

                               斜线1:当两个棋子的行列之差相等时,即在同一条斜线1上 

                                斜线2:当两个棋子的行列之和相等时,即在同一条斜线2上                                 

 这里图示说明:

 

//这里采用了暴力枚举的方法,统计合理个数,没有使用回溯
//也可以使用回溯法写
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
int n;
int ans;
int flag1[101],flag2[101];
int x1[101],y1[101];
void dfs(int x,int y,int sum)
{
    x1[sum]=x; y1[sum]=y; //第i个皇后的横坐标为x1[i] 纵坐标为 y1[i]
    if(sum==n) // 当放入了 n 个皇后
    {
        memset(flag1,0,sizeof(flag1)); //重置数组
        memset(flag2,0,sizeof(flag2));
        for(int i=1;i<=n;i++) 
        {
            if(!flag1[x1[i]]) flag1[x1[i]]=1; //判断是否在同一行
            else return ;
            if(!flag2[y1[i]]) flag2[y1[i]]=1; // 判断是否在同一列
            else return ;
        }
        for(int i=1;i<=n;i++) // 二维数组判断任意两个是否在同一条斜线上
        for(int j=i+1;j<=n;j++)
        {
            if(x1[i]-y1[i]==x1[j]-y1[j]) return ; //斜线1
            if(x1[i]+y1[i]==x1[j]+y1[j]) return ; //斜线2
        }
        //这里二重循环明显时间复杂度增加,下文对其进行优化
        ans++;
        return ;
    }
    for(int j=1;j<=n;j++)
    {
        dfs(x+1,j,sum+1); //暴力枚举 考虑下一行放皇后的位置
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) 
        dfs(1,i,1); // 放第一个皇后 于 第1行 第i列
    printf("%d",ans);
    system("pause");
    return 0;
}

三.回溯相关概念

回溯是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

与暴搜的区别:在完成某个枚举时返回过程中,将在上一次枚举中修改过的变量返回到原来的状态

四.相关例题

1.全排列问题

题目描述

        输入一个数n,输出从 1 到 n 的所有排列情况(按字典序排列)

 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
int n,m;
int a[1001],b[1001];
int num[1001];
void dfs(int x,int sum)
{
    num[sum]=x;
    if(sum==n)
    {
        for(int i=1;i<=n;i++) printf("%d ",num[i]);
        printf("\n");
        return ;
    }
    for(int i=1;i<=n;i++)
    {
        if(b[i]==1) continue;
        b[i]=1;
        dfs(i,sum+1);
        b[i]=0; // 回溯过程
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        b[i]=1;
        dfs(i,1);
        b[i]=0; // 回溯
    } 
    system("pause");
    return 0;
}

2.迷宫问题(八皇后问题—回溯)

洛谷 八皇后问题 P1219

 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
int n;
int ans;
int f[101];
int flag1[101],flag2[101],flag3[101];
void print()
{
    if(ans<=3)
    {
         for(int i=1;i<=n;i++)
         printf("%d ",f[i]);    
         printf("\n");
    }
    return ;
}
void dfs(int x)
{
    if(x>n)
    {
        ans++;
        print();
        return ;
    }
    for(int i=1;i<=n;i++)
    {   // check函数判断是否能在 x,i 处放置皇后
        // flag1 判断列  flag2 判断斜线2  flag3 判断斜线1
        if(!flag1[i] && !(flag2[x+i]) && !(flag3[x-i+n])) //优化上文两重循环
        {
        // 成功放入
            f[x]=i;
            flag1[i]=1;
            flag2[x+i]=1;
            flag3[x-i+n]=1;
            dfs(x+1);
            flag1[i]=0;
            flag2[i+x]=0;
            flag3[x-i+n]=0;
        }
    }
}
int main()
{
    scanf("%d",&n);
    dfs(1);
    printf("%d",ans);
    system("pause");
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值