• 递归1--小游戏


    递归1--小游戏

    零:本题总结

    1、回溯迷宫问题

    2、扩充边界

    一、递归基本思想

    定义:函数直接或者间接调用自身
    应用场景:原问题复杂,但是可以划分成许多性质相同的子问题,子问题容易求解
    递归写法:1、先写出问题的递推公式
                  2、递归部分的边界条件就是递推公式中的边界条件
                  3、递归部分的主体部分就是递推公式中的主体部分
    递归在内存中的实现方式:系统通过栈来实现(通过栈去讲)
    递归简单实例:http://www.cnblogs.com/Renyi-Fan/p/6914840.html

    二、递归实例题目

    题目:

    有一个w * h 个正方格子的矩形板,每个正方格子上可以有一张游戏卡片, 当然也可以没有,当下面的情况满足时,认为
    两个游戏卡片之间有一条路径相连:1、路径只包含水平或者竖直的直线段;2、路径不能穿过别的游戏卡片;3、但是允许
    路径临时的离开矩形板。判断是否存在一条满足题意的路径能连接给定的两个游戏卡片。

    输入:

    输入包括多组数据: 一个矩形板对应一组数据
    第一行包括两个整数 w和h (1 <= w, h <= 75),分别表示矩形板的宽度和长度
    下面的h行, 每行包括w个字符, 表示矩形板上的游戏卡片分布情况:
    使用 ‘X’ 表示这个地方有一个游戏卡片,使用空格 表示这个地方没有游戏卡片
    之后每行上包括4个整数:
    x1, y1, x2, y2 (1 <= x1, x2 <= w, 1 <= y1, y2 <= h)给出两个卡片在矩形板上的位置
    注意: 矩形板左上角的坐标是(1,1),输入保证这两个游戏卡片所处的位置是不相同的
    如果一行上有4个0, 表示这组测试数据的结束
    如果一行上给出w = h = 0, 那么表示所有的输入结束了

    输入样例:

    5 4
    XXXXX
    X X
    XXX X
    XXX
    2 3 5 3
    1 3 4 4
    2 3 3 4
    0 0 0 0
    0 0

    样例图片及图片说明:

    在 (1,3)和 (4,4)处的游戏卡片是可以相连的,
    而在 (2,3) 和 (3,4) 处的游戏卡是不相连的, 因为连接它们的每条路径都必须要穿过别的游戏卡片。
    本题的问题是:判断是否存在一条满足题意的路径能连接给定的两个游戏卡片。

    输出:

    对每一个矩形板, 输出一行 “Board #n:”, n是输入数据的编号
    对每一组需要测试的游戏卡片输出一行. 这一行的开头是 “Pair m: ”,
    这里m是测试卡片的编号(对每个矩形板, 编号都从1开始),
    如果可以相连, 找到连接这两个卡片的所有路径中包括线段数最少的路径, 输出 “k segments.”
    k是找到的最优路径中包括的线段的数目,
    如果不能相连, 输出 “impossible.”,每组数据之后输出一个空行。

    输出样例:

    Board #1:
    Pair 1: 4 segments.

    Pair 2: 3 segments.

    Pair 3: impossible.

    三、分析

    题目分析:
    普通迷宫问题的路径数目是经过的格子数目
    而该问题路径只包含水平或者竖直的直线段,
    所以需要记录每一步走的方向
    如果上一步走的方向和这一步走的方向相同, 递归搜索时路径数不变, 否则路径数加1

    路径只包含水平或者竖直的直线段. 路径不能穿过别的游戏卡片. 但是允许路径临时的离开矩形板
    所以在矩形板最外层增加一圈格子, 路径可以通过这些新增加的格子

    分析及变量描述:
    1. 设置迷宫为二维数组board[][], 数组的值是:
    空格: 代表这个地方没有游戏卡片
    ‘X’ : 代表这个地方有游戏卡片
    2. 在搜索过程中, 用另外一个二维数mark[][]标记格子是否已经走过了
    mark[i][j]=0 //格子(i, j)未走过
    mark[i][j]=1 //格子(i, j)已经走过
    3. int minstep, w, h;
    //全局变量
    //minstep, 记录从起点到达终点最少路径数,
    //初始化为一个很大的数
    //w, h矩形板的宽度和高度
    4. 设置搜索方向顺序是东, 南, 西, 北
    int to[4][2] = {{0,1},{1,0},{0,-1},{-1,0}};
    //now_x, now_y, 当前位置
    //x, y下一步位置
    for(i = 0; i < 4; i ++)
    {
    int x = now_x + to[i][0];
    int y = now_y + to[i][1];
    f=i; //方向, 0,1,2,3分别表示东,南,西,北 …
    }


    5. 判断新位置(x, y)是否有效
    T1: (x, y)在边界之内 (x > -1) && (x < w + 2) && (y > -1) && (y < h + 2)
    T2: 该位置没有游戏卡片并且未曾走过 ((board[y][x] == ' ') && (mark[y][x] == false))
    T3: 已经到达终点 (x == end_x) && (y == end_y) && (board[y][x] == 'X‘)
    综上, (x,y)有效的条件是 T1 && (T2 || T3)
    ((x > -1) && (x < w + 2) && (y > -1) && (y < h + 2) && (((board[y][x] == ' ') && (mark[y][x] == false)) || ((x == end_x) && (y == end_y) && (board[y][x] == 'X'))))

    四、代码

      1 /*
      2 递归基本思想:
      3 定义:函数直接或者间接调用自身
      4 应用场景:原问题复杂,但是可以划分成许多性质相同的子问题,子问题容易求解
      5 递归写法:1、先写出问题的递推公式
      6           2、递归部分的边界条件就是递推公式中的边界条件
      7           3、递归部分的主体部分就是递推公式中的主体部分
      8 递归在内存中的实现方式:系统通过栈来实现(通过栈去讲)
      9 递归简单实例:http://www.cnblogs.com/Renyi-Fan/p/6914840.html
     10 */
     11 
     12 /*
     13 这个太浪费时间了,下次不这么做了
     14 本末倒置了 
     15 题目:
     16 有一个w * h 个正方格子的矩形板,每个正方格子上可以有一张游戏卡片, 当然也可以没有,当下面的情况满足时,认为
     17 两个游戏卡片之间有一条路径相连:1、路径只包含水平或者竖直的直线段;2、路径不能穿过别的游戏卡片;3、但是允许
     18 路径临时的离开矩形板。判断是否存在一条满足题意的路径能连接给定的两个游戏卡片。
     19 
     20 输入:
     21 输入包括多组数据: 一个矩形板对应一组数据
     22 第一行包括两个整数 w和h (1 <= w, h <= 75),分别表示矩形板的宽度和长度
     23 下面的h行, 每行包括w个字符, 表示矩形板上的游戏卡片分布情况:
     24 使用 ‘X’ 表示这个地方有一个游戏卡片,使用空格 表示这个地方没有游戏卡片
     25 之后每行上包括4个整数:
     26 x1, y1, x2, y2 (1 <= x1, x2 <= w, 1 <= y1, y2 <= h)给出两个卡片在矩形板上的位置
     27 注意: 矩形板左上角的坐标是(1,1),输入保证这两个游戏卡片所处的位置是不相同的
     28 如果一行上有4个0, 表示这组测试数据的结束
     29 如果一行上给出w = h = 0, 那么表示所有的输入结束了
     30 
     31 输入样例:
     32 5 4
     33 XXXXX
     34 X   X
     35 XXX X
     36  XXX 
     37 2 3 5 3
     38 1 3 4 4
     39 2 3 3 4
     40 0 0 0 0
     41 0 0
     42 
     43 
     44 输出:
     45 对每一个矩形板, 输出一行 “Board #n:”, n是输入数据的编号
     46 对每一组需要测试的游戏卡片输出一行. 这一行的开头是 “Pair m: ”, 
     47 这里m是测试卡片的编号(对每个矩形板, 编号都从1开始),
     48 如果可以相连, 找到连接这两个卡片的所有路径中包括线段数最少的路径, 输出 “k segments.”
     49 k是找到的最优路径中包括的线段的数目,
     50 如果不能相连, 输出 “impossible.”,每组数据之后输出一个空行。
     51 
     52 输出样例:
     53 Board #1: 
     54 Pair 1: 4 segments. 
     55 
     56 Pair 2: 3 segments. 
     57 
     58 Pair 3: impossible.
     59 */
     60 
     61 /*
     62 题目分析:
     63 普通迷宫问题的路径数目是经过的格子数目
     64 而该问题路径只包含水平或者竖直的直线段,
     65 所以需要记录每一步走的方向
     66     如果上一步走的方向和这一步走的方向相同, 递归搜索时路径数不变, 否则路径数加1
     67 
     68 路径只包含水平或者竖直的直线段. 路径不能穿过别的游戏卡片. 但是允许路径临时的离开矩形板
     69 所以在矩形板最外层增加一圈格子, 路径可以通过这些新增加的格子
     70 
     71 分析及变量描述:
     72 1. 设置迷宫为二维数组board[][], 数组的值是:
     73     空格: 代表这个地方没有游戏卡片
     74    ‘X’ : 代表这个地方有游戏卡片
     75 2. 在搜索过程中, 用另外一个二维数mark[][]标记格子是否已经走过了
     76     mark[i][j]=0 //格子(i, j)未走过
     77     mark[i][j]=1 //格子(i, j)已经走过
     78 3. int minstep, w, h; 
     79 //全局变量
     80 //minstep, 记录从起点到达终点最少路径数,
     81 //初始化为一个很大的数
     82 //w, h矩形板的宽度和高度
     83 4. 设置搜索方向顺序是东, 南, 西, 北
     84 int to[4][2] = {{0,1},{1,0},{0,-1},{-1,0}};
     85 //now_x, now_y, 当前位置 
     86 //x, y下一步位置 
     87 for(i = 0; i < 4; i ++)
     88 { 
     89 int x = now_x + to[i][0]; 
     90 int y = now_y + to[i][1]; 
     91 f=i; //方向, 0,1,2,3分别表示东,南,西,北 … 
     92 }
     93 5. 判断新位置(x, y)是否有效
     94 T1: (x, y)在边界之内 (x > -1) && (x < w + 2) && (y > -1) && (y < h + 2)
     95 T2: 该位置没有游戏卡片并且未曾走过 ((board[y][x] == ' ') && (mark[y][x] == false))
     96 T3: 已经到达终点 (x == end_x) && (y == end_y) && (board[y][x] == 'X‘)
     97  综上, (x,y)有效的条件是 T1 && (T2 || T3) 
     98 ((x > -1) && (x < w + 2) && (y > -1) && (y < h + 2) && (((board[y][x] == ' ') && (mark[y][x] == false)) || ((x == end_x) && (y == end_y) && (board[y][x] == 'X'))))
     99 
    100 */
    101 
    102 /*
    103 本题伪代码:
    104     读入数据
    105     扩充边界
    106     找最小步数(核心)(递归) 
    107     输出结果
    108     ps:如果是我,我就分四个函数+来写这个题目 
    109     
    110     其中递归最小步数的伪代码为:
    111     看是不是递归边界,是的话,就返回
    112     不是递归边界,就东南西北找下一步(所以这里肯定是循环) 
    113     如果找到的下一步合理,我们就递归找下一步的下一步
    114     注意回溯,不能漏掉同层的情况 
    115  
    116  
    117 自己心得:(一定要精析,不能浮在表面) 
    118 1、这种多方向的图,我直接画出方向特别容易理解
    119 2、这种长方形不要理解为x和y,理解为宽width和高height,而且直接把表格和坐标画出来,直观 
    120 3、这里是用getchar()读入字符,并且先读上一行的换行符
    121 4、用scanf("%d %d %d %d", &begin_x, &begin_y, &end_x, &end_y)&& begin_x > 0来判断输入为0000的情况,因为正常数据x不可能为0 
    122 5、回溯的模板记好就好了:原数组,标记数组,循环,回溯 
    123 6、用if(w == 0 && h == 0)break;来判断输入为0 0的情况
    124 7、递归的边界条件是到达终点, 递归的递推公式就是上下左右走 ,只不过边界条件和递推公式都做了剪枝 
    125 8、边界条件的剪枝是当前步数大于能成功的最小步数,所以当前这种情况应该勇敢舍去
    126 9、递推公式的剪枝是下一点在边界内,下一点可以走并且没有被走过,或者下一点是终点(这个条件好像可有可无) 
    127 10、递归函数记录了关键的信息,当前点,下一点,步数和方向,因为我们要求的是步数,而方向决定步数 
    128 11、如果是我,我会把对当前四边的扩展区域写在一起,当然,扩展边界这个思路特别好 
    129 12、直接和一个二维数组相加表示东南西北,当然你之前要把这个图画出来,这个挺好的
    130 13、本题通过方向的变化来表示步数的增加,也就是线段的变化,其实挺好 
    131 
    132 */ 
    133 
    134 #include <iostream>
    135 #include <cstdio>
    136 #include <memory.h>
    137 using namespace std;
    138 
    139 #define MAXIN 75
    140 char board[MAXIN + 2][MAXIN + 2]; //定义矩形板//储存每个格子中是否有卡片//+2是扩充格子边界 
    141 
    142 int minstep, w, h, to[4][2] = {{0,1},{1,0},{0,-1},{-1,0}}; //定义方向//顺序是东, 南, 西, 北
    143 bool mark[MAXIN + 2][MAXIN + 2]; //定义标记数组//记录格子有没有被走过,回溯算法中标准的数组 
    144 
    145 
    146 void Search(int now_x, int now_y, int end_x, int end_y, int step, int f){
    147     //now_x, now_y当前位置
    148     //end_x, end_y结束位置
    149     //step已经走过的路径数目
    150     //f从上一步走到(now_x, now_y)时的方向
    151     if(step > minstep) return; //当前路径数大于minstep, 返回优化策略,其实也就是剪枝
    152     if(now_x == end_x && now_y == end_y){ //到达终点
    153         if(minstep > step) //更新最小路径数
    154             minstep = step;
    155         return;
    156     }
    157     //下面这部分程序就是一个标准的回溯 
    158     for(int i = 0; i < 4; i ++){ //枚举下一步的方向
    159         int x = now_x + to[i][0]; //得到新的位置
    160         int y = now_y + to[i][1];
    161         bool t1= (x > -1) && (x < w + 2) && (y > -1) && (y < h + 2);//(x, y)在边界之内
    162         bool t2= (board[y][x] == ' ') && (mark[y][x] == false);//该位置没有游戏卡片并且未曾走过
    163         bool t3=(x==end_x)&& (y == end_y) && (board[y][x] == 'X');//已经到达终点
    164         if (t1&& (t2 || t3)){//能够继续的条件 t1&& (t2 || t3)
    165             mark[y][x] = true; //如果新位置有效标记该位置
    166             //已经过上一步方向和当前方向相同,
    167             //则递归搜索时step不变, 否则step+1
    168             if(f == i) Search(x, y, end_x, end_y, step, i);
    169             else Search(x, y, end_x, end_y, step + 1, i);
    170             mark[y][x] = false; //回溯, 该位置未曾走过
    171         }        
    172     }
    173 }
    174 
    175 int main(){
    176     freopen("in.txt","r",stdin);
    177     int Boardnum = 0;
    178     while(scanf("%d %d", &w, &h)){ //读入数据
    179         if(w == 0 && h == 0)break;
    180         Boardnum ++;
    181         
    182         printf("Board #%d:
    ", Boardnum);
    183         int i, j;
    184         //初始化左上角相连的两条边 
    185         for (i = 0; i < MAXIN + 2; i ++) board[0][i] = board[i][0] = ' ';
    186         for(i = 1; i <= h; i ++){ //读入矩形板的布局
    187             getchar();
    188             for(j = 1; j <= w; j ++) board[i][j] = getchar();
    189         }
    190         //在矩形板最外层增加一圈格子
    191         //初始化左下角相连的两条边 
    192         for (i = 0; i <= w; i ++)
    193             board[h + 1][i + 1] = ' ';
    194         for (i = 0; i <= h; i ++)
    195             board[i + 1][w + 1] = ' ';
    196         
    197         int begin_x, begin_y, end_x, end_y, count = 0;
    198         while(scanf("%d %d %d %d", &begin_x, &begin_y, &end_x, &end_y)&& begin_x > 0){ //读入起点和终点
    199             
    200             count ++;
    201             minstep = 100000; //初始化minstep为一个很大的值
    202             memset(mark, false, sizeof(mark));
    203             //递归搜索
    204             Search(begin_x, begin_y, end_x, end_y, 0, -1);
    205             //输出结果
    206             if(minstep < 100000)
    207                 printf("Pair %d: %d segments.
    ", count, minstep);    
    208             else printf("Pair %d: impossible.
    ", count);
    209             printf("
    ");
    210         }
    211         
    212     }
    213     return 0;
    214 }

    五、自己心得

    /*
    本题伪代码:
    读入数据
    扩充边界
    找最小步数(核心)(递归)
    输出结果
    ps:如果是我,我就分四个函数+来写这个题目

    其中递归最小步数的伪代码为:
    看是不是递归边界,是的话,就返回
    不是递归边界,就东南西北找下一步(所以这里肯定是循环)
    如果找到的下一步合理,我们就递归找下一步的下一步
    注意回溯,不能漏掉同层的情况


    自己心得:(一定要精析,不能浮在表面)
    1、这种多方向的图,我直接画出方向特别容易理解
    2、这种长方形不要理解为x和y,理解为宽width和高height,而且直接把表格和坐标画出来,直观
    3、这里是用getchar()读入字符,并且先读上一行的换行符
    4、用scanf("%d %d %d %d", &begin_x, &begin_y, &end_x, &end_y)&& begin_x > 0来判断输入为0000的情况,因为正常数据x不可能为0
    5、回溯的模板记好就好了:原数组,标记数组,循环,回溯
    6、用if(w == 0 && h == 0)break;来判断输入为0 0的情况
    7、递归的边界条件是到达终点, 递归的递推公式就是上下左右走 ,只不过边界条件和递推公式都做了剪枝
    8、边界条件的剪枝是当前步数大于能成功的最小步数,所以当前这种情况应该勇敢舍去
    9、递推公式的剪枝是下一点在边界内,下一点可以走并且没有被走过,或者下一点是终点(这个条件好像可有可无)
    10、递归函数记录了关键的信息,当前点,下一点,步数和方向,因为我们要求的是步数,而方向决定步数
    11、如果是我,我会把对当前四边的扩展区域写在一起,当然,扩展边界这个思路特别好
    12、直接和一个二维数组相加表示东南西北,当然你之前要把这个图画出来,这个挺好的
    13、本题通过方向的变化来表示步数的增加,也就是线段的变化,其实挺好

    */

  • 相关阅读:
    获取窗口相对位置小工具
    关于抽奖概率的问题
    乔布斯的成功秘方:坚持思考两个问题
    清理svn生成的相关文件的小工具
    photoshop cs6 简体中文正式版下载
    .NET下office操作利器NPOI
    sql 取各组中的最大值
    c# winform 获取当前程序运行根目录
    C# Winform DataGridView使用总结 转
    c#安装数据库并自动修改Web.config类
  • 原文地址:https://www.cnblogs.com/Renyi-Fan/p/6932352.html
Copyright © 2020-2023  润新知