万能搜索算法

《啊哈!算法》学习笔记

深度优先搜索

        例一:全排列

        输入一个数n,输出全排列1~n的全排列。在上一个博客中讲解了枚举算法解这道题:

暴力枚举算法-优快云博客

        现在我们使用另一种解法。首先我们将问题具象化假如有依次写着1、2、3的3张扑克牌和编号1、2、3的3个盒子;现在需要将这3张扑克牌放在3个盒子里,每个盒子只能放一个扑克牌。

        我们定义一种规则,每个盒子放的扑克牌按1、2、3的顺序来,比如放编号为1的盒子时,手里有1、2、3 三张扑克牌,我们按照规则放入’1‘,到达编号为2的盒子剩2、3;我们放入’2‘、到达第3个盒子。我们只剩一个放入’3‘。这时就组成了第一种排列“1 2 3”。

        我们取出三号盒子的扑克牌,此时手里只有一个已经放入过的’3‘,只能退到二号盒子,取出扑克牌,此时我们手里有“2 3”,按照规则,已经放入过’2‘, 这次就放入’3‘,前进一步三号盒子放入’2‘,组成排列’1 3 2‘。

        按照这个规则依次得到“2 1 3”、“2 3 1”、“3 1 2”、“3 2 1”。

        我们总结一下代码要实现的条件:

  • 按照扑克牌编号大小依次放入盒子,每个盒子只能放一个。
  • 已经放过的扑克牌不能二次放置。
  • 放置顺序按照盒子编号大小依次放入。
  • 当最后一个盒子放入后,排列次数+1。
  • 后退到上一个盒子,要将本盒子使用的扑克牌回收。

代码如下: 

#include<stdio.h>
int n=0,sum=0;
int a[10]={0},book[10]={0};

void  arrange(int step) //放入编号为step的盒子
{
    int i = 0;
    if (step == n+1) //满足条件表示所有的盒子都已经放入扑克牌,存入到了数组a[]中
    {
        for ( i = 1; i <= n; i++) //打印排列
        {
            printf("%d ",a[i]);

        }
        sum++; //总排列次数加一
        printf("  sum=%d \n",sum);
        
        return ;
    }

    for ( i = 1; i <= n; i++) //放入编号为i的扑克牌
    {
        if (book[i] == 0) //扑克牌未被使用
        {
            a[step]=i;  // 将编号为i的扑克牌放入编号为step的盒子里
            book[i]=1;  // 标记 i编号的扑克牌已经被使用;
            arrange(step+1); //再下一个盒子进行同样的操作
            book[i] = 0; // 这一步非常重要,大家可以尝试去掉会造成什么结果,已经返回一种排列,该盒子中的扑克牌被收回;
        }
    }
    
    return ;
}


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

         可以看到,本代码通过函数递归来实现,如果代码看不懂,建议先去了解函数递归,栈、队列、链表-优快云博客我在本片博客中介绍了递归函数。

         本套代码包含了深度优先模型的基本模型

void dfs(int step)
{
    判断边界
    尝试每种可能 for(i=1;i<=n;i++)
    {
        继续下一步 dfs(step+1);
    }
    返回
}

        例二:九个方框奥数

         将数字1~9分别填入9个方框中,每个数字只能使用一次使等式成立,例如173+286=459,其中286+173=459与173+286=459是同一种组合。了【

        与例一,相比实现步骤一致,从3张扑克牌三个盒子变成九张扑克牌,九个盒子,再加一个约束条件,即a[1]*100+a[2]*10+a[3]+a[4]*100+a[5]*10+a[6]=a[7]*100+a[8]*10+a[9]。

        我们按照深度优先搜索的模型来实现这道题。代码如下(含注释)

#include<stdio.h>
// 存储扑克牌的盒子
int a[10]={0};
// 判断扑克牌是否被使用
int book[10]={0};
// 记录总数
int total = 0;

void dfs(int num)
{
    if(num==10){
        // 边界
        if(a[1]*100+a[2]*10+a[3]+a[4]*100+a[5]*10+a[6]==a[7]*100+a[8]*10+a[9])
        {
            // 满足条件
            total++;
            printf("%d%d%d+%d%d%d=%d%d%d\n",a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9]);
            return ;
        }
    }
    // 尝试每种可能
    for (int i = 1; i <= 9; i++)
    {
        // 判断扑克牌是否被使用
        if(book[i]!=1)
        {
            a[num]=i;
            // 标记扑克牌使用
            book[i]=1;
            // 进入递归
            dfs(num+1);
            // 扑克牌被放回
            book[i]=0;
        }
    }
    return ;
} 


int main()
{
    int num = 1;
    dfs(1);
    printf("%d",total/2);
    return 0;
}

        例三:解救小哈

        有一天,小哈一个人去迷宫玩,但是方向感很不好的小哈很快就迷路了。小哼得知后便立即去解救无助的小哈。小哼清楚了迷宫的地图,现在小哼要以最快的速度去解救小哈。问题就此开始了....

        迷宫由n行m列的单元格组成(n和m都小于等于50),每个单元格要么是空地,要么是障碍物。你的认为是帮助小哼找到一条从迷宫的起点通往小哈所在位置的最短路径。注意障碍物是不能走的,当然小哼也不能走到迷宫之外。

        首先我们的目标是找到最短路径,即走最少的点。然后找一下条件为:

  • 只能走空地不能走障碍物。
  • 已经走过的路径不能再走。
  • 每次走一步。
  • 到达小哈所在的位置即结束。

实现步骤:

  • 先用二维数组表示坐标。
  • 由于我们需要搜索所有路径,所以每一步都要尝试上下左右各个方向,我们按照顺时针的方向来尝试(右、下、左、上)定义数组存储。
  • 我们每次找到小哈便记录所用的步数,最后选择最少的步数step。

输入:

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

输出:

#include<stdio.h>
int map[50][50]={0};//地图
int book[50][50]={0};//标记已经走过的位置
int next[4][2]={
    {0,1},//右
    {1,0},//下
    {0,-1},//左
    {-1,0},//上
};
int p=0,q=0; //小哈的位置
int min=99999999;
int x=0,y=0;                //地图坐标

void dfs(int i,int j,int step)
{
    int i_flag=0,j_flag=0;
    if (i==p && j==q)//判断小哈是否到达位置
    {
        if (step<min)//更新最小值
        {
            min=step;
        }
        return ;
    }
    for (int k=0 ; k < 4; k++)//枚举四种走法
    {
        i_flag=i+next[k][0];
        j_flag=j+next[k][1];
        if (i_flag<0 || i_flag>x || j_flag<0 || j_flag>y) //判断是否为边界
            continue;
        
        if (book[i_flag][j_flag]!=1&&map[i_flag][j_flag]!=1)//判断该点是否为障碍物或者已经在路径中
        {
            book[i_flag][j_flag]=1;
            dfs(i_flag,j_flag,step+1);
            book[i_flag][j_flag]=0;
        }
    }     
    return ; 
}

int main()
{
    int start_x,start_y;        //开始位置
    scanf("%d %d",&x,&y);       // 导入地图大小
    for (int i = 0; i < x; i++) // 导入空地和障碍物位置
        for (int j = 0; j < y; j++)
            scanf("%d",&map[i][j]);

    scanf("%d %d %d %d",&start_x,&start_y,&p,&q);// 导入初始位置和小哈的位置
    dfs(start_x,start_y,0);
    printf("%d",min);
 return 0;   
}

        

广度优先搜索

        广度优先搜索也称宽度优先搜索,顾名思义,为发散的搜索。

        例一:解救小哈

        还是解救小哈这道题,我们用广度优先搜索,从初始位置向外扩散,直到到达小哈的位置,每次扩散step+1,即每次扩散到达的位置所需的步数是一样的。

        我们用一个队列来模拟整个过程 

struct note

{

    int x;//横坐标

    int y;//纵坐标

    int f;//父亲在队列中的编号,本体不要求输出路径,可以不需要

    int s;//步数

};

#include<stdio.h>
#include<stdbool.h>
struct note
{
    int x;//横坐标
    int y;//纵坐标
    int f;//父亲在队列中的编号,本体不要求输出路径,可以不需要
    int s;//步数
};

int main()
{
    struct note que[2501];
    int map[50][50]={0},book[50][50]={0};//导入地图
    int next[4][2]={
    {0,1},//右
    {1,0},//下
    {0,-1},//左
    {-1,0},//上
    };
    int head,tail;
    int x,y,start_x,start_y,p,q;
    scanf("%d %d",&x,&y);       // 导入地图大小
    for (int i = 0; i < x; i++) // 导入空地和障碍物位置
        for (int j = 0; j < y; j++)
            scanf("%d",&map[i][j]);
    scanf("%d %d %d %d",&start_x,&start_y,&p,&q);// 导入初始位置和小哈的位置
    // 队列初始化
    head = 1; 
    tail = 1;
    // 往队列插入迷宫入口坐标
    que[tail].x = start_x;
    que[tail].y = start_y;
    que[tail].f = 0;
    que[tail].s = 0;
    tail++;
    book[start_x][start_y]=1;

    int i_flag,j_flag;
    bool flag=false;
    while (head<tail)
    {
        for (int k=0 ; k < 4; k++)//枚举四种走法
        {
            i_flag=que[head].x+next[k][0];
            j_flag=que[head].y+next[k][1];
            if (i_flag<0 || i_flag>x || j_flag<0 || j_flag>y) //判断是否为边界
                continue;
            
            if (book[i_flag][j_flag]!=1&&map[i_flag][j_flag]!=1)//判断该点是否为障碍物或者已经在路径中
            {
                //宽搜每个点入队一次,不需要将book数组还原
                book[i_flag][j_flag]=1;
                que[tail].x=i_flag;
                que[tail].y=j_flag;
                que[tail].f=head;
                que[tail].s=que[head].s+1;//步数是父亲的步数加1
                tail++;
            }
             if (i_flag==p && j_flag==q)//判断小哈是否到达位置
            {
                flag=true;
                break;
            }
        }  
        if (flag==true)
        {
            break;
        }
        head++;
        //当一个点扩展介绍后,head++才能对后面的点再进行扩展
    }
    // 打印队列中末尾最后一个点(目标点)的步数
    // 注意tail是指向队列队尾的下一个位置,所以这需要-1
    printf("%d",que[tail-1].s);
    return 0;
}

         例二:再解炸弹人

        我们再上一个博客中留下的问题:再解炸弹人暴力枚举算法-优快云博客

       

        按照枚举的算法,将炸弹放置到(1,11)处,最多可以消灭11个敌人,但小人其实是无法走到(1,11)的。正确的答案是是将炸弹放置到(7,11)处,消灭10个敌人。我们可以使用广度优先搜索或者深度优先搜索来 枚举所有小人可以出现的点,然后再这些可达到的点上来分别统计可以消灭的敌人数。

        广度优先搜索解法

        首先我们来用广度优先搜索来解决。

                其中有很多函数和代码都可以直接用枚举的解法和上一道解救小哈的cv过来,代码如下:

#include<stdio.h>
struct  note//队列
{
    int x;
    int y;
};
char map[20][20];

int getnum(int i,int j) //统计(i,j)杀死敌人个数
{
    int sum=0,x=i,y=j;//sum用来计数(可以消灭的敌人数),所以需要初始化为0
    //分为上下左右四个方向来统计,用x,y来复制i和j。
    
    //向上统计
    while (map[x][y]!='#')
    {
        if (map[x][y]=='G')
        {
            sum++;
        }
        x--;//向上统计
        
    }



    //向下统计
    x=i,y=j;
    while (map[x][y]!='#')
    {
        if (map[x][y]=='G')
        {
            sum++;
        }
        x++;//向下统计
        
    }

    //向左统计
    x=i,y=j;
    while (map[x][y]!='#')
    {
        if (map[x][y]=='G')
        {
            sum++;
        }
        y--;//向左统计
        
    }

    //向右统计
    x=i,y=j;
    while (map[x][y]!='#')
    {
        if (map[x][y]=='G')
        {
            sum++;
        }
        y++;//向右统计
        
    }
    return sum;
}

int main(){
    struct note que[401];//假设地图大小不能超过20*20,因此队列扩展不会超过400个。
    int head=0,tail=0;//队列头尾
    int book[20][20]={0};//定义一个标记数组
    int n=0,m=0,startx=0,starty=0,sum=0;
    int max=0,mx=0,my=0;
    int tx=0,ty=0;

    int next[4][2]={
    {0,1},//右
    {1,0},//下
    {0,-1},//左
    {-1,0},//上
    };

    //读入n和m,表示多少行多少列。
    //读入初始位置
    scanf("%d %d %d %d",&n,&m,&startx,&starty);

    for (int i = 0; i < n; i++)
    {
        scanf("%s",map[i]);
    }

    //队列初始化
    head = 1;
    tail = 1;

    //往队列插入小人的起始坐标
    que[tail].x=startx;
    que[tail].y=starty;
    tail++;
    book[startx][starty] = 1;
    max = getnum(startx,starty);
    mx = startx;
    my = starty;
    
    //当循环不为空的时候循环
    while (head<tail)
    {
        //枚举四个方向
        for (int k = 0; k < 4; k++)
        {
            //尝试走的下一个坐标
            tx=que[head].x+next[k][0];
            ty=que[head].y+next[k][1];

            //判断是否越界
            if (tx<0 || tx>n-1 || ty<0 || ty>m-1)
            {
                continue;
            }

            //判断是否为平地
            if (map[tx][ty]=='.'&&book[tx][ty]==0)
            {
                //标记该点已经走过,每个点只走一次
                book[tx][ty]=1;

                //插入新扩展的点到队列中
                que[tail].x=tx;
                que[tail].y=ty;
                tail++;

                //统计新进扩展点杀死敌人数
                sum=getnum(tx,ty);

                //更新max的值
                if (sum>max)
                {
                    max=sum;
                    mx=tx;
                    my=ty;
                }
            }
            
        }
        head++;//对后面的点进行扩展
        
    }
    //输出结果
    printf("mx= %d my=%d sum = %d",mx,my,max);
    return 0;
}

输入:

13 13 3 3
#############
#GG.GGG#GGG.#
###.#G#G#G#G#
#.......#..G#
#G#.###.#G#G#
#GG.GGG.#.GG#
#G#.#G#.#.#.#
##G...G.....#
#G#.#G###.#G#
#...G#GGG.GG#
#G#.#G#G#.#G#
#GG.GGG#G.GG#
#############  

输出: 

  mx= 7 my=11 sum = 10

深度优先搜索解法

        从小人所在点向右走。每到统计该点消灭的敌人数,并从该点继续尝试往下走,直到无路可走的时候返回,再尝试走其它方向,直到将所有可以走到的点都访问一遍,程序结束,代码如下:

#include<stdio.h>
char map[20][20]={"\0"};//地图
int book[20][20]={0};//标记已经走过的路
int max=0,mx=0,my=0;//最大值坐标以及消灭的敌人数
int n=0,m=0;//地图大小
int next[4][2]={ //上下左右四个方向
{0,1},//右
{1,0},//下
{0,-1},//左
{-1,0},//上
};

int getnum(int i,int j) //统计(i,j)杀死敌人个数
{
    int sum=0,x=i,y=j;//sum用来计数(可以消灭的敌人数),所以需要初始化为0
    //分为上下左右四个方向来统计,用x,y来复制i和j。
    
    //向上统计
    while (map[x][y]!='#')
    {
        if (map[x][y]=='G')
        {
            sum++;
        }
        x--;//向上统计
        
    }



    //向下统计
    x=i,y=j;
    while (map[x][y]!='#')
    {
        if (map[x][y]=='G')
        {
            sum++;
        }
        x++;//向下统计
        
    }

    //向左统计
    x=i,y=j;
    while (map[x][y]!='#')
    {
        if (map[x][y]=='G')
        {
            sum++;
        }
        y--;//向左统计
        
    }

    //向右统计
    x=i,y=j;
    while (map[x][y]!='#')
    {
        if (map[x][y]=='G')
        {
            sum++;
        }
        y++;//向右统计
        
    }
    return sum;
}

void dfs(int i,int j)
{
    int tx=0,ty=0,sum=0;

    //统计新进扩展点杀死敌人数
    sum=getnum(i,j);
    //更新max的值
    if (sum>max)
    {
        max=sum;
        mx=i;
        my=j;
    }


    for (int k = 0; k < 4; k++)
    {
        tx=i+next[k][0];
        ty=j+next[k][1];
        //判断是否越界
        if (tx<0 || tx>n-1 || ty<0 || ty>m-1)
        {
            continue;
        }

        //判断是否为平地
        if (map[tx][ty]=='.'&&book[tx][ty]==0)
        {
            //标记该点已经走过                                                            
            book[tx][ty]=1;
            dfs(tx,ty);
            //注意本题不需要将已经走过的点归零,因为我们的目的是将所有的点遍历就行。
        }
    }
    return ;
}

int main()
{
    int startx=0,starty=0;
    //输入 n行n列以及初始位置
    scanf("%d %d %d %d",&n,&m,&startx,&starty);
    //输入地图数据
    for (int i = 0; i < n; i++)
    {
        scanf("%s",map[i]);
    }
    //初始化
    max=getnum(startx,starty);
    mx=startx;
    my=starty;
    // 进行搜索
    dfs(startx,starty);
    //输出结果
    printf("mx= %d my=%d sum = %d",mx,my,max);
    return 0;
}

输入输出结果同上。

练习题

宝岛探险 

        小哼通过秘密方法得到一张不完整的钓鱼岛航拍图,钓鱼岛由一个主岛和一些附属岛屿组成,小哼决定去钓鱼岛探险。下面这个10*10的二维矩阵就是钓鱼岛的航拍图。图中数字表示海拔,0表示海洋,1~9都表示陆地。小哼的飞机降落在(6,8)处,现在需要计算出小哼降落所在岛的面积(既有多少个格子)。注意此处我们把与小哼降落点上下左右想链接的陆地视为同一岛屿。

        此题可以用广度优先搜素,也可以用深度优先搜索。

        广度优先搜索解法

        从(6,8)开始广度优先搜索。每次需要向上下左右四个方向扩展,当扩展出的点大于0时就加入队列,直到队列扩展完毕。所有被加入点的总数就是小岛的面积,还是通过队列实现,相信大家已经能总结出一套模板了,我们假设地图的大小不超过50*50.代码实现如下:

#include<stdio.h>
struct node
{
    int x;
    int y;
};

int map[20][20]={0};//地图
int book[20][20]={0};//标记
int next[4][2]={
{0,1},//右
{1,0},//下
{0,-1},//左
{-1,0},//上
};

int main()
{
    
    struct node que[250];
    //导入地图大小和降落位置
    int n=0,m=0,startx=0,starty=0;
    scanf("%d %d %d %d",&n,&m,&startx,&starty);
    // printf("**");
    int head=1,tail=1;
    int tx=0,ty=0;
    int sum=0;//像素个数
    //导入地图
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            scanf("%d",&map[i][j]);
            // printf("## ");
        }
        printf("\n");
    }
    
    que[tail].x=startx;
    que[tail].y=starty;
    book[startx][starty]=1;
    sum++;
    tail++;
    while (head<tail)
    {
        for (int k = 0; k < 4; k++)
        {
            //尝试走的下一个坐标
            tx=que[head].x+next[k][0];
            ty=que[head].y+next[k][1];

            //判断是否越界
            if (tx<1 || tx>n || ty<1 || ty>m)
            {
                continue;
            }

            //判断是否为平地
            if (map[tx][ty]>0&&book[tx][ty]==0)
            {
                //标记该点已经走过,每个点只走一次
                book[tx][ty]=1;

                //插入新扩展的点到队列中
                que[tail].x=tx;
                que[tail].y=ty;
                tail++;
                sum++;
            }
        } 
        head++;//对后面的点进行扩展  
    }
    
    
    printf("%d",sum);
    return 0;
}

样例输入

10 10 6 8
1 2 1 0 0 0 0 0 2 3
3 0 2 0 1 2 1 0 1 2
4 0 1 0 1 2 3 2 0 1
3 2 0 0 0 1 2 4 0 0
0 0 0 0 0 0 1 5 3 0
0 1 2 1 0 1 5 4 3 0
0 1 2 3 1 3 6 2 1 0
0 0 3 4 8 9 7 5 0 0
0 0 0 3 7 8 6 0 1 2
0 0 0 0 0 0 0 0 1 0
 

 样例输出

38

从(6.8)开始搜索,可以扩展的点如下图(阴影部分)。

 

深度优先搜索解法

        废话不多说,直接看代码:

#include<stdio.h>
int map[20][20]={0};//地图
int book[20][20]={0};//标记已经走过的路
int max=0,mx=0,my=0;//最大值坐标以及消灭的敌人数
int n=0,m=0;//地图大小
int next[4][2]={ //上下左右四个方向
{0,1},//右
{1,0},//下
{0,-1},//左
{-1,0},//上
};
int sum = 0;

void dfs(int i,int j)
{
    int tx=0,ty=0;


    for (int k = 0; k < 4; k++)
    {
        tx=i+next[k][0];
        ty=j+next[k][1];
        //判断是否越界
        if (tx<1 || tx>n || ty<1 || ty>m)
        {
            continue;
        }

        //判断是否为平地
        if (map[tx][ty]>0&&book[tx][ty]==0)
        {
            //标记该点已经走过                                                            
            book[tx][ty]=1;
            sum++;
            dfs(tx,ty);
            //注意本题不需要将已经走过的点归零,因为我们的目的是将所有的点遍历就行。
        }
    }
    return ;
}

int main()
{
    int startx=0,starty=0;
    //输入 n行n列以及初始位置
    scanf("%d %d %d %d",&n,&m,&startx,&starty);
    //输入地图数据
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            scanf("%d",&map[i][j]);
        }
        
    }
    book[startx][starty]=1;
    sum++;
    // 进行搜索
    dfs(startx,starty);
    //输出结果
    printf("%d\n",sum);
    return 0;
}

         上面的方法又叫着色法,以某个点为源点对其相邻的点进行着色。

        只需在dfs()函数建一个参数color就行了,color表示所需染的颜色。注意下面代码颜色的部分

void dfs(int i,int j,int color)

{

    int tx=0,ty=0;

    map[i][j]=color;

    for (int k = 0; k < 4; k++)

    {

        tx=i+next[k][0];

        ty=j+next[k][1];

        //判断是否越界

        if (tx<1 || tx>n-1 || ty<1 || ty>m-1)

        {

            continue;

        }

        //判断是否为平地

        if (map[tx][ty]>0&&book[tx][ty]==0)

        {

            //标记该点已经走过                                                            

            book[tx][ty]=1;

            sum++;

            dfs(tx,ty,color);

            //注意本题不需要将已经走过的点归零,因为我们的目的是将所有的点遍历就行。

        }

    }

    return ;

}

       统计小岛个数(着色法)

        如果想知道这个地图中有多少个小岛,只需要将每个大于0的点都搜素一遍即可。对每个点尝试染色。

#include<stdio.h>
int map[20][20]={0};//地图
int book[20][20]={0};//标记已经走过的路
int max=0,mx=0,my=0;//最大值坐标以及消灭的敌人数
int n=0,m=0;//地图大小
int next[4][2]={ //上下左右四个方向
{0,1},//右
{1,0},//下
{0,-1},//左
{-1,0},//上
};
int sum = 0;

void dfs(int i,int j,int color)
{
    int tx=0,ty=0;
    map[i][j]=color;

    for (int k = 0; k < 4; k++)
    {
        tx=i+next[k][0];
        ty=j+next[k][1];
        //判断是否越界
        if (tx<1 || tx>n || ty<1 || ty>m)
        {
            continue;
        }

        //判断是否为平地
        if (map[tx][ty]>0&&book[tx][ty]==0)
        {
            //标记该点已经走过                                                            
            book[tx][ty]=1;
            dfs(tx,ty,color);
            //注意本题不需要将已经走过的点归零,因为我们的目的是将所有的点遍历就行。
        }
    }
    return ;
}

int main()
{
    int startx=0,starty=0,num=0;
    //输入 n行n列以及初始位置
    scanf("%d %d %d %d",&n,&m,&startx,&starty); 
    //输入地图数据
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            scanf("%d",&map[i][j]);
        }
        
    }
    // 进行搜索 
    
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            if (map[i][j]>0)
            {
                num--;
                book[i][j]=1;
                printf("i=%d j=%d\n",i,j);
                dfs(i,j,num);
            }
            
        }
        
    }
    //输出结果
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            printf("%3d ",map[i][j]);
        }
        printf("\n");
    }
    printf("%d\n",-num);
    return 0;
}

        其实这就是求一个图中独立子图的个数。这个算法就是Floodfill漫水补充法(也称种子填充法)。Floodfill在计算机图形学中有着广泛的运用,比如图像分割、物体识别等等。另外我们熟知的Windows下“画图”软件的油漆桶工具(可以出题)就是基于这个算法的。当你需要给某个密闭区域涂色或更改颜色时,程序自动选中与种子点(鼠标左键单击的位置)周边颜色相同的区域,紧接着将该区域替换成指定的颜色。Photoshop的魔术棒选择工具也可以基于这个算法实现。具体的算法是:查找种子点周边的点,将与种子点颜色相近的点(可以设置一个阈值)入队作为新种子,并对新入队的种子也进行同样的扩展操作,这样就选取了和最初种子相近的颜色区域。

水管工游戏

        最近小哼又迷上了一个叫做水管工的游戏。游戏的大致规则是这样的。一块矩形土地被分为N*M的单位正方形,现在这块土地已经埋设有一些水管,水管将从坐标为(1,1)的矩形土地的左上角的左部边缘,延伸到坐标为(N,M)的矩形土地右下角右部边缘。水管只有两种,如下图所示。

        每种管道将占据一个单位正方形土地。你现在可以旋转这些管道,使其构成一个管道系统,即创造一条从(1,1)到(N,M)的连通管道,标有树木的方格表示这里没有管道。如下图表示一个5*4的土地中(2,4)处有一个树木。

        

         我们可以旋转管道,使之构成一个连通的管道系统。

        如果通过旋转管道可以使之构成一个连通的管道系统,就输出铺设的路径,否则输出impossible。

        输入数据如下:

5 4
5 3 5 3
1 5 3 0
2 3 5 1
6 1 1 5
1 5 5 4

        输出:

(1,1) (1,2) (2,2) (3,2) (3,3) (3,4) (4,4) (5,4) 

         输入的第一行为两个整数N和M(都不超过10),接下来的N行,每行有M个整数,表示地图中的每一小格,其中0表示树木,1-6分别表示管道的六种摆放方式如下图:

        我们可以将这六中摆放方式,分成两种,前一个为直管,或者为弯管;

        当为直管,再分4种情况,分别对应接口为左、上、右、下,为左或右对应情况5,为上或下对应6;

    //当前水管是自管的情况
    if (map[i][j]>=5 && map[i][j]<=6)
    {
        if (front==1)//进水口在左边的情况
        {
            dfs(i,j+1,1);//5
        } 
        else if (front==2)//进水口在上边的情况
        {
            dfs(i+1,j,2);//6
        }
        else if (front==3)//进水口在右边的情况
        {
            dfs(i,j-1,3);//5
        }
        else if (front==4)//进水口在下边的情况
        {
            dfs(i-1,j,4);//6
        }
    }

        当为弯管时,也分4种情况,分别对应接口为左、上、右、下,为左对应3、4号,为上对应1、4号,为右对应1,2,为下对应2、3号。

        //当前水管是弯管的情况
    if (map[i][j]>=1 && map[i][j]<=4)
    {
        if (front==1)//进水口在左边的情况
        {
            dfs(i+1,j,2);//3号状态
            dfs(i-1,j,4);//4号状态
        } 
        else if (front==2)//进水口在上边的情况
        {
            dfs(i,j+1,1);//1号状态
            dfs(i,j-1,3);//4号状态
        }
        else if (front==3)//进水口在右边的情况
        {
            dfs(i-1,j-1,4);//1号状态
            dfs(i+1,j+1,2);//2号状态
        }
        else if (front==4)//进水口在下边的情况
        {
            dfs(i,j+1,1);//2号状态
            dfs(i,j-1,3);//3号状态
        }
    }

 我们用栈来输出路径。

struct note

{

    int x;

    int y;

}s[100];

front的4个方向左、上、右、下代表为1、2、3、4,注意是进水口不是出水口

完整代码如下:

#include<stdio.h>
int map[20][20]={0};//地图
int book[20][20]={0};//标记已经走过的路
int n=0,m=0,flag=0;
struct note
{
    int x;
    int y;
}s[100];
int top=0;

//不用判断下一个会不会接不上,约束条件只有有无被使用
void dfs(int i,int j,int front)//front表示进水口方向
{
    if (i==n && j==m+1)
    {
        flag=1;//找到铺设方案
        for (int i = 1; i <= top; i++)
        {
            printf("(%d,%d) ",s[i].x,s[i].y);
        }
        
        return ;
    }
    
    if (i<1 || i>n || j<1 || j>m)//越界
        return ;
    if (book[i][j]==1)//被使用过
        return ;
    book[i][j]=1;//标记被使用
    
    //将当前尝试入栈
    top++;
    s[top].x=i;
    s[top].y=j;

    //当前水管是自管的情况
    if (map[i][j]>=5 && map[i][j]<=6)
    {
        if (front==1)//进水口在左边的情况
        {
            dfs(i,j+1,1);//5
        } 
        else if (front==2)//进水口在上边的情况
        {
            dfs(i+1,j,2);//6
        }
        else if (front==3)//进水口在右边的情况
        {
            dfs(i,j-1,3);//5
        }
        else if (front==4)//进水口在下边的情况
        {
            dfs(i-1,j,4);//6
        }
    }

        //当前水管是弯管的情况
    if (map[i][j]>=1 && map[i][j]<=4)
    {
        if (front==1)//进水口在左边的情况
        {
            dfs(i+1,j,2);//3号状态
            dfs(i-1,j,4);//4号状态
        } 
        else if (front==2)//进水口在上边的情况
        {
            dfs(i,j+1,1);//1号状态
            dfs(i,j-1,3);//4号状态
        }
        else if (front==3)//进水口在右边的情况
        {
            dfs(i-1,j-1,4);//1号状态
            dfs(i+1,j+1,2);//2号状态
        }
        else if (front==4)//进水口在下边的情况
        {
            dfs(i,j+1,1);//2号状态
            dfs(i,j-1,3);//3号状态
        }
    }
    book[i][j]=0;
    top--;//将当前尝试出栈
    return ;
}

int main()
{
    //输入 n行n列
    scanf("%d %d",&n,&m);
    //输入地图数据
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            scanf("%d",&map[i][j]);
        }
        
    }
    dfs(1,1,1);
    //输出结果
    if (flag==0)
    {
        printf("impossible\n");
    }
    
    // printf("yes!\n");
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值