【数据结构与算法(十)】——回溯法

BFS???

回溯法

用回溯法解决的问题的所有选项可以形象地用树状结构表示。在某一步有n个可能的选项,那么该步骤可以看成是树状结构的一个节点,每个选项看成树中节点连接线,经过这些连接线到达该节点的n个子节点。树的叶节点对应着终结状态。如果在叶节点的状态满足题目的约束条件,那么代表我们找到了一个可行的解决方案。如果叶节点的状态不满足约束条件,那么只好回溯到它的上一个节点再尝试其他选项。如果上一个系欸但所有可能的选项都已经试过了,并且不能满足到达约束条件的终结状态,则再次回溯到上一个节点。如果所有节点的所有选项都已经尝试过仍然不能到达满足约束条件的终结状态,则该问题无解。

题目

矩阵中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3*4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用下划线标出)。但矩阵中不包含字符串“abdb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子
这里写图片描述

思路:

以上矩阵从b开始的选项组成的树状结构
这里写图片描述
首先在矩阵中任选一个格子作为路径的起点。假设矩阵中某个格子的字符为ch,并且这个格子将对应于路径上的第i个字符。如果路径上的第i个字符不是ch,那么这个格子不可能处在路径上的第i个位置。如果路径上的第i+1个字符。除矩阵边界上的格子之外,其他格子都有相邻4个的格子。重复这个过程,直到路径上的所有字符都在矩阵中找到相应的位置。
回溯法具有递归特性,路径可以被看成一个栈。当在矩阵中定位了路径中前n个字符的位置之后,在与第n个字符对应的格子的周围都没有找到第n+1个字符,这时候只好在路径上回到第n-1个字符,重新定位第n个字符。
由于路径不能重复进入矩阵的格子,所以还需要定义和字符矩阵大小一样的布尔值矩阵,用来标识路径是否已经进入了每个格子。

#include<string>
//使用引用作为函数的返回值,函数执行完,返回的是引用,可以引向的是很大的数据内存区,可以更有效解决
bool hasPathCore(char* matrix, int rows, int cols, int row, int col, const char* str, int& pathLength, bool* isVisited)
{
    //说明已经到达路径的出口了
    if (str[pathLength] == '\0')
        return true;

    bool hasPath = false;
    //当前访问的字符串就是符合路径的字符串
    if (row >= 0 && row < rows&&col >= 0 && col < cols
        &&matrix[row*cols + col] == str[pathLength]
        && !isVisited[row*cols + col]) {
        ++pathLength;//访问目标字符串的下一个字符
        isVisited[row*cols + col] = true;
        //访问上下左右四个元素
        hasPath = hasPathCore(matrix, rows, cols, row, col - 1, str, pathLength, isVisited)
            || hasPathCore(matrix, rows, cols, row, col + 1, str, pathLength, isVisited)
            || hasPathCore(matrix, rows, cols, row + 1, col, str, pathLength, isVisited)
            || hasPathCore(matrix, rows, cols, row - 1, col, str, pathLength, isVisited);
        //这一个元素之后的路径都没有合适的,回到当前元素的上一个元素(看树,父节点)
        if (!hasPath) {
            --pathLength;
            isVisited[row*cols + col] = false;
        }
    }
    return hasPath;
}

bool hasPath(char* matrix, int rows, int cols, char* str)
{
    if (matrix == nullptr || rows < 1 || cols < 1 || str == nullptr)
        return false;

    bool* visited = new bool[rows*cols];
    /*extern void *memset(void *buffer, int c, int count)        
    buffer:为指针或是数组,
    c:是赋给buffer的值,
    count:是buffer的长度.
    作用:
    这个函数在socket中多用于清空数组.如:原型是memset(buffer, 0, sizeof(buffer))
    用来对一段内存空间全部设置为某个字符,一般用在对定义的字符串进行初始化为‘ ’或‘/0’;*/
    memset(visited, 0, rows*cols);
    int pathLength = 0; 
    //为什么是双重循环?因为是二维数组,要遍历整个数组中所有的元素
    //为什么要遍历所有元素?因为如果从某一个元素为起始点找不到符合条件的路径就要重新找起点,之后就要另选起点了
    for (int row = 0; row < rows; row++)
        for (int col = 0; col < cols; col++)
            if (hasPathCore(matrix, rows, cols, row, col, str, pathLength, visited))
                return true;
    //为什么要delete?因为visited是一个指针,我们一开始有用new给他分配空间,之后这个空间不用了就需要释放掉,不然会造成内存泄露?
    delete[] visited;
    return false;
}

C++指针与引用的区别:引用是一个别名(编译器中的引用是一个const指针,但引用是没有被分配内存的),访问一个对象和访问该对象的引用,实际上是访问同一块内存区域;指针指向一个地址【具体参考博客:https://blog.csdn.net/ljheee/article/details/52190087
引用和指针的区别和联系(笔试热点)【来自以上链接的文章】
1. 引用只能在定义时初始化一次,之后不能改变指向其它变量(从一而终);指针变量的值可变。
2. 引用必须指向有效的变量,指针可以为空。
3. sizeof指针对象和引用对象的意义不一样。sizeof引用得到的是所指向的变量的大小,而sizeof指针是对象地址的大小。
4. 指针和引用自增(++)自减(–)意义不一样。
5. 相对而言,引用比指针更安全。

★不同点:
1. 指针是一个实体,而引用仅是个别名;
2. 引用使用时无需解引用(*),指针需要解引用;
3. 引用只能在定义时被初始化一次,之后不可变;指针可变;
4. 引用没有 const,指针有 const;const修饰的指针不可变;
5. 引用不能为空,指针可以为空;
6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
7. 指针和引用的自增(++)运算意义不一样;
8.从内存分配上看:程序为指针变量分配内存区域,而引用不需要分配内存区域。

★相同点:两者都是地址的概念,指针指向一块儿内存,其内容为所指内存的地址;引用是某块儿内存的别名。
这里写图片描述

机器人的运动范围

地上有一个m行n列的方格。一个机器人从坐标(0,0)的给子开始移动,它每次可以向左、右、上、下移动一格,但不能进入行坐标和列坐标之和大于k的格子。例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7=18.但它不能进入方格(35,38),因为3+5+3+8=19.请问该机器人能够到达多少个格子。

思路:

1、抽象建模:简单化。
2、将机器人运动的方格看成是rows*cols矩阵,所以思路和上面那道题差不多,也还是得判断是不是被走过了,不然会重复计算同一个表格,而且还得判断是否和小于K。⭐因为记录了是否被走过,所以在一条路走的时候,遇到四周都是被访问过的,拿这条路就结束了【对于递归这个东西,总是一会看的很清楚,一会又迷迷糊糊的】

//得到坐标的和
int getMaxtrixSum(int n, int m)
{
    int sum = 0;
    while (n > 0) {
        sum += sum % 10;
        sum /= 10;
    }
    while (m > 0) {
        sum += sum % 10;
        sum /= 10;
    }
    return sum;
}

//检查机器人是否可以进入该坐标(row,col)
bool check(int maxK, int rows, int cols, int row, int col, bool* isVisited)
{
    if (row >= 0 && col >= 0 && row < rows&&col < cols
        &&getMaxtrixSum(row, col)
        && !isVisited[row*cols + col])
        return true;
    return false;
}

int moveCountCore(int maxK, int rows, int cols, int row, int col, bool* isVisited)
{
    int count = 0;
    if (check(maxK, rows, cols, row, col, isVisited)) {
        isVisited[row*cols + col] = true;
        //这个count只是当前节点以及以后的总和,没有前面的,前面的要等递归回去之后加上
        count = 1 + moveCountCore(maxK, rows, cols, row - 1, col, isVisited)
            + moveCountCore(maxK, rows, cols, row + 1, col, isVisited)
            + moveCountCore(maxK, rows, cols, row, col - 1, isVisited)
            + moveCountCore(maxK, rows, cols, row, col + 1, isVisited);
    }
    return count;
}

int moveCount(int maxK, int rows, int cols)
{
    if (maxK < 0 || rows <= 0 || cols <= 0)
        return 0;
    bool *isVisited = new bool[rows*cols];
    for (int i = 0; i < rows*cols; i++)
        isVisited[i] = false;
    int count = moveCountCore(maxK, rows, cols, 0, 0, isVisited);
    //还是要记得释放内存空间!!
    delete[] isVisited;
    return count;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值