dfs总结

#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <stdbool.h>
/*递归
 * dfs深度搜索:一条路走到底在往上
 * 数独游戏
 *
你一定听说过数独游戏。
如下图所示,玩家需要根据9x9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行,每一列每一个同色九宫内的数字均含1-9,不重复。
数独的答案都是唯一的, 所以,多个解也称为无解。
本图的數字据说是芬兰数学家花了3个月的时间设计出来的较难的题目。但对会使用计算机编程的你来说,恐怕易如反掌了。
本题的要求就是输入数独题目,程序输出数独的唯一解。我们保证所有已知数据的格式都是合法的,并且题目有唯一的解。
格式要求,输入9行,每行9个数字,0代表未知,其它数字为已知。
输出9行,每行9个数字表示数独的解。
输入:
005300000
800000020
070010500
400005300
010070006
003200080
060500009
004000030
000009700

 输出
145327698
839654127
672918543
496185372
218473956
753296481
367542819
984761235
521839764
*/
bool check(char table[][10],int x,int y,int k)
{
    //检查同行同列
    for(int i=0;i<9;i++) {
        if (table[x][i] == (char) ('0' + k))  //不能重复 table[i]的行列
            return false;
        if (table[i][y] == (char)('0' +  k))
            return false;
    }
    //检查小九宫格3*3
      // (x/3)*3是小九宫点的起点范围为0-3
    for(int i=(x/3)*3;i<(x/3+1)*3;i++)  //行的起点 x/3*3
    {
        for(int j=(y/3)*3;j<(y/3+1)*3;j++)   //列
        {
            if(table[i][j]==(char)('0'+k))   //有与要放入的i相同的数返回false
                return false;
        }
    }
    return true;
}


//核心代码dfs
void dfs(char table[][10],int x,int y) {
    if(x==9)  //结束
    {
      for(int i=0;i<9;i++) //打印
            printf("%s\n",table[i]);
        exit(0);  //唯一解退出系统;
    }

    if (table[x][y] == '0')  //有0的地方填入数字
    {
        //选1-9的合法数字填到x,y这个位置
        for(int i=1;i<10;i++)
        {
           if(check(table,x,y,i))//在x这个行以及j这个列和小九宫格里面是否有i了

           {
               table[x][y]=(char)('0'+i);//是真,就可以填i这个位置,然后转移到下一个状态转为字符
               dfs(table,x+(y+1)/9,(y+1)%9);  //行要不要动取决于y有没有到最后一列,y上限8,y=8时x+1下一行,只有等于8,y之后归0
               table[x][y]='0';  //平行状态 回溯
           }
        }
    }
    else
    {
        //继续找下一个需要处理的位置
        dfs(table,x+(y+1)/9,(y+1)%9);
    }
}

int main()
{
    char table[10][10];
    for(int i=0;i<9;i++)
          scanf("%s",table[i]);
    dfs(table,0,0);
}

/*
 * 部分和
 * 给定整数序列a1, a2,... an,判断是否可以从中选出若干数,使它们的和恰好为k。
    1<=n<=20
   -10^8s<=k<=10^8
   -10^8<=k<=10^8
样例:
输入
n=4
a={1,2,4,7}
k=13
输出:
Yes (13=2+4+7)


*/
int n;
int *a;
int arr[1000];   // ints数组

int size = 0;  //当前的arr的长度,size是当前的arr多用到的数据的长度

int len = 0;// 总长度n

int kk; //记录初值13

void dfs(int a[], int k, int cur) {   //k在变小cur在变大,cur是下标表示可选范围
    if (k == 0)
    {  //k为0说明13最后减成了0已经全部找到所有和的子集
        printf("yes (%d=", kk);  //kk=13,因为K在变,所以要用kk记录一下初始的位置
        for (int i = 0; i < size; i++) { //遍历arr里面的数
            printf("%d", arr[i]);//2 4 7
            if (i != size - 1) {  //只要当前遍历的不是最后一次循环就打印一个加号
                printf("%c", '+');
            }
        }
        printf(")");
        return;
    }

    if (k < 0 || cur == len) {  //下标和数组长度一样了,这边的数组是int不能用strlen,len=n
        return;     //找不到这条路不通
    }

    dfs(a, k, cur + 1); //思维导图的左边,你不要cur这个元素两种状态的转移

    arr[size++] = a[cur];  //把要存的cur下标的数放入arr数组里面,size++继续
    //dfs先遍历左分支,就是cur不要的,等左分支全部遍历到底之后才会遍历右分支。
    // 右分支是要cur的,当左边遍历完时,程序回到了arr[size++]=a[cur]开始遍历右分支
    //我是用k减去要的值,直到k=0减完结束,要的cur对应的下标存入arr,最后打印arr里的数
    dfs(a, k - a[cur], cur + 1);  //你要cur这个元素,k是13部分的和减去下标为cur的这个数,凑的数变小,k-a[cur]右边,要!
    size--;   //走不通就回溯,size变小了,for循环遍历不到了,这和删除也差不多意思了 ,就是回溯
}

int main() {
    int k = 13;
    scanf("%d", &n);
    a=(int *)malloc(sizeof(int)*n);
    for(int i=0;i<n;i++)
        scanf("%d",&a[i]);
    len = n;
    kk = k;  //存初值13
    dfs(a, k, 0);  //cur一开始为0
    return 0;
}


/*
 * dfs之水洼题目要背诵!
有一个大小为NxM的园子,兩后积起了水。八连通的积水被认为是连接在一起的。请求出
园子里总共有多少水洼? (八连通指的是 下图中相对W的*的部分)
(就是W是积水,以w为中心的八个方向找,分别八个方向都没w为止)
 ***
 *w*
 ***
I
限制条件
N, M<=100
样例:
输入
N=10, M=12
园子如下图('W'表示积水,'. '表示没有积水)

W........WW.
.WWW.....WWW
....WW...WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.
 输出3

水洼的思路就是找到W把W消成. 并且不用回溯,因为已经全部变成点了

*/
//#define M 12
//#define N 10
int N,M;
void dfs(char a[][12],int i,int j)
{
    a[i][j] = '.';  //把w变成点
    for (int k = -1; k < 2; k++)
    {//-1,0,1 行 向左,不动,向右三种情况
        for (int L = -1; L < 2; L++)
        {//-1向上,0不动,1向下
            if (k == 0 && L== 0) continue;  //横纵坐标不变没变的
            if (i + k >= 0 && i + k <= N - 1 && j + L >= 0 && j + L <= M - 1)
            {//i+k是行数,j+L是列数
                if (a[i + k][j + L] == 'W')
                    dfs(a, i + k, j + L);  //新的坐标
            }
        }
    }
}

int main()
{

    scanf("%d%d",&N,&M);
    char a[10][12];
    for(int i=0;i<N-1;i++)
    {
        scanf("%s",a[i]); //一行一行相当于字符串一行一行的输入
    }
    //找有w的点
    int count=0;
    for(int i=0;i<N;i++)
    {
        for(int j=0;j<M;j++)
        {
            if(a[i][j]=='W')
            {
                dfs(a,i,j);  //找一个起点开始清除一个水洼
                count++;
            }
        }
    }
    printf("\n水洼数=%d\n",count);
}


/*复习:回溯和剪枝
 * 全排列的交换、数独、部分和等用到了回溯
 * dfs之n皇后问题
 *请设计一种算法,解决著名的n皇后问题。这里的n皇后问题指在一个n*n的棋盘上放置n个棋子,
 *使得每行每列和每条对角线上都只有一个棋子, 求其摆放的方法数。
  给定一个int n,请返回方法数,保证n小于等于15
   3*3放不了 4*4两种
   主对角线x-y相同,副对角线x+y相同
*/

int n;
int count=0; //计数
int rec[20];  //做一下记录 下标i代表行,值rec[i]代表存的每一列

bool check(int rec[],int x,int y)  //x是行,y是列
{
    for(int i=1;i<x;i++)  //i从第一行到相应的行
    {
        //rec[i]+i下标加值是主对角元素,i-rec[i]副对角线元素,x是行号,y是列号
      if(rec[i]==y||rec[i]+i==x+y||i-rec[i]==x-y)  //下标i代表行,值rec[i]代表存的每一列。同一列和主副对角线有没有
          //(1,1) (1,2)
          //(2,1)  (2,2)...
          //(3,1)    (3,2).....发现主对角线上x-y相同,副对角线相加x+y相同
          return false;
    }
    return true;
}
void dfs(int row)
 {
    if(row==n+1) //找到一种,行已经结束出去了
    {
        count++;
        return ;
    }

     for(int col=1;col<=n;col++)   //你如果定义下标是从1开始的,那么你最好全局的数组定义都从1开始因为上面那个rec[i]下标从1开始的
     {
         if(check(rec,row,col))//检查这一列能不能放
         {
             rec[row]=col;  //假设在row这一行皇后放在col这列
             dfs(row+1);
             rec[row]=0; //回溯,清0
         }
     }
 }

 int main()
 {
    scanf("%d",&n);
    dfs(1); //从第一行开始
    printf("%d皇后一共有:%d个解\n",n,count);
 }

/*
 * dfs困难的串
 * 如果个字符串包含两个相邻的重复子串则称它是容易的串,其也串称为困难的串
 * 例如BB,ABCDACABCAB(CAB重复),ABCDABCD(ABCD重复)都是容易的串
 * 而D,DC ABDAB,CBABCBA都是困难的串
 * 输入正整数n和L输出由前L个字符(26个大写字母)组成的.字典序第n小的困难的串(按照字典序每次必须从A开始)
 *例如,当L=3时(由ABC组成),前7个困难的串分别为A、AB、ABA、ABAC、ABACA、ABACAB、ABACABA。  //字典序要求递增
 * 如果n=4的话就输出ABAC
 *  //下列代码可以用于输出字符串,可做参考
 **/

void printStr(char* str) {
    while (*str) {
        printf("%c", *str);
        str++;
      }
    printf("\n");
    }

char prefix[1000];   //用于存放已经是困难的串的前缀

int cnt = 0;

//(j,j+count+1) 和(j+count+1,index)
char* substr(int start, int end) {  //功能是复制子字符串,要求从指定位置开始,并具有指定位置结束。
    int len = end - start +1;  //多个'\0'
    int i = 0;
    char* str = (char*)malloc(len * sizeof(char));
    for (int j = start; j < end; j++) {
        str[i++] = prefix[j];  //把前缀串复制到str里面
    }
    str[i] = '\0';  //最后一位放入'\0'
    return str;
}

char* strcat(char* str, char ch) {  //用于字符串连接,把i连接到前缀串中   //x2,i
    char *p = str;
    while (*p != '\0') {
        p++;
    }
    *p = ch;
     p++;
     *p = '\0';  //最后一位放'\0'
    return str;

}

bool equals(char* str1, char* str2) { //判断两个串是否相等x1==x2
    char* p1 = str1;
    char* p2 = str2;
    while (*p1 != '\0' && *p2 != '\0') {
        if (*p1 != *p2) {  //相等不符合条件
            return false;
        }
        p1++;
        p2++;
    }
    if (*p1 != '\0' || *p2 != '\0') return false;
    return true;
}

bool isHardString(char i, int index) {   //判断是否是困难的串
    int count = 0;
    for (int j = index - 1; j >= 0; j -= 2) {//每次给前面的加入字符分配一个字符 自己也要增加一个长度 所以每次往后退两个位置。
        char* x1 = substr(j, j + count + 1);
        char* x2 = substr(j + count + 1, index);
        x2 = strcat(x2, i);  //把i连接到x2中
        if (equals(x1, x2)) {  //如果相等不符合条件
            return false;
        }
        count++;
    }
    return true;
}

void dfs(int n, int l, int index) {
    for (char i = 'A'; i < 'A' + l; ++i) {
        if (isHardString(i, index)) {
            prefix[index] = i;//如果前缀都是困难的串把i放入前缀数组
            cnt++;
            if (cnt == n) {
                for (int j = 0; j <= index; j++) {
                    printf("%c", prefix[j]);  ///输出困难的串
                }
                printf("\n");
                exit(0);
            }
            dfs(n, l, index + 1);
        }
    }
}

int main() {
    int N = 4;
    int L = 3;
    int index = 0;
    dfs(N, L, index);//N表示第几个,L表示困难的串,index表示下标
    return 0;
}
/*
 * 方格填数
如下的10个格子
+--+--+--+
|  |  |  |
+--+--+--+--+
|  |  |  |  |
+--+--+--+--+
|  |  |  |
+--+--+--+
(如果显示有问题,也可以参看【图1.jpg】)
填入0~9的数字。要求:连续的两个数字不能相邻。
(左右、上下、对角都算相邻)
 //8个方向8个分支
一共有多少种可能的填数方案?请填写表示方案数目的整数。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。

 */
int res[3][4];  //用于存放数字
int flag[10];  //用于标记
int sum=0;

int check(int row ,int col,int k)
{
    for(int i=-1;i<2;i++)
    {
      for(int j=-1;j<2;j++)
      {
          if(i==0&&j==0)
              continue;
          if((row+i)>=0 && ( row+i)<3 && (col+j)>=0 && (col+j)<4)
          {
              if(abs(res[row+i][col+j]-k)==1)
                  return 0;
          }
      }
    }
    return 1;
}
void dfs(int row,int col)
{
    if(row==2&&col==3)  //结束k
    {
       sum++;  //实现了一次加一
       return ;
    }

    for(int i=0;i<=9;i++)//0-9的数字进行填空格
    {
        if(check(row,col,i)&&!flag[i])  //检查,标记数组不为0说明这个数字没用过
        {
            flag[i]=1; //使用过的数字标记1
            res[row][col]=i;
            dfs(row+(col+1)/4,(col+1)%4);
            //同数独
            //行要不要动取决于col有没有到最后一列col上限3,y=3时row+1下一行,只有等于3,col之后归0
            res[row][col]=1000;
            flag[i]=0;
        }
    }
}
int main()
{
    int i,j;
    for(i=0;i<3;i++)
    {
        for(j=0;j<4;j++)
        {
            res[i][j]=1000;  //随机存放的数
        }
    }
    for( i=1;i<10;i++)
        flag[i]=0;   //标记数组
    dfs(0,1);   //从第0行第1列开始
    printf("%d\n",sum);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值