• <迷宫问题及最短路径问题(使用DFS与回溯法求解)>——《算法》


    目录

    一、迷宫问题: 

    1.迷宫问题链接:(牛客网)迷宫问题

    1.1 问题描述:

    1.2 问题分析:

    1.3 功能函数实现:

    1.4 栈的模拟实现:

    1.5测试用例及演示:

    1.6 完整源码:

    二、迷宫最短路径问题: 

    1. 迷宫最短路径问题链接:(牛客网)地下迷宫

    1.1 问题描述:

    1.2 问题分析:

    1.3 功能函数实现:

    1.4 栈的模拟实现:

    1.5测试用例及演示:

    1.6 完整源码:

    三、总结:

    后记:●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!

                                                                           ——By 作者:新晓·故知


    一、迷宫问题: 

    1.迷宫问题链接:(牛客网)迷宫问题

    1.1 问题描述:

     

    1.2 问题分析:

    以上迷宫OJ题曾经是百度某一年的其中一个笔试题,迷宫问题本质就是一个图的遍历问题,从起点开始不断四个方向探索,直到走到出口,走的过程中我们借助栈记录走过路径的坐标,栈记录坐标有两方面的作用,一方面是记录走过的路径,一方面方便走到死路时进行回溯找其他的通路。这里涉及到四路递归。(上、下、左、右四个方向的递归)
    我们在这里借助深度优先遍历(DFS)与回溯法进行分析、求解。
    根据题目要求,分析后确定实现方式,模块化实现各部分功能,然后依据“高内聚,低耦合”的方式进行组织!

     

    1.3 功能函数实现:

    (1)首先定义迷宫坐标位置的结构体:

     

    (2)写出调用逻辑(主函数):

     

     

    (3)判断路径坐标进行可以进行遍历:

     

     

    (4)判断该坐标是否为有效可遍历的坐标

     (5) 输出栈里面的坐标路径:

    由于栈的“FIFO”性质,又题目要求按照从最开始打印,则需要进行顺序调整。

     (6)打印遍历的路径坐标:

     1.4 栈的模拟实现:

    (这里借助栈的“FIFO”性质,借用栈存储递归遍历的数据存储,不符合的坐标就出栈,再进行回溯遍历)

     1.5测试用例及演示:

    示例1:

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

    示例2:

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

     测试打印:

     1.6 完整源码:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. //定义位置结构体
    7. typedef struct Postion
    8. {
    9. int row;
    10. int col;
    11. }PT;
    12. /
    13. //栈的实现
    14. typedef PT STDataType;
    15. typedef struct Stack
    16. {
    17. STDataType* a; //通过数组实现栈的结构
    18. int top;
    19. int capacity;
    20. }ST;
    21. //初始化
    22. void StackInit(ST* ps);
    23. //释放内存、销毁空间
    24. void StackDestory(ST* ps);
    25. // 入栈
    26. void StackPush(ST* ps, STDataType x);
    27. // 出栈
    28. void StackPop(ST* ps);
    29. //取栈顶数据
    30. STDataType StackTop(ST* ps);
    31. //栈的大小
    32. int StackSize(ST* ps);
    33. //判空
    34. bool StackEmpty(ST* ps);
    35. //初始化
    36. void StackInit(ST* ps)
    37. {
    38. assert(ps);
    39. ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
    40. if (ps->a == NULL)
    41. {
    42. printf("malloc fail!\n");
    43. exit(-1);
    44. }
    45. ps->capacity = 4;
    46. ps->top = 0; //这使得top最终指向的是栈顶的后一个位置。若top=-1,则最终指向的是栈顶。
    47. }
    48. //释放内存、销毁空间
    49. void StackDestory(ST* ps)
    50. {
    51. assert(ps);
    52. free(ps->a);
    53. ps->a = NULL;
    54. ps->top = ps->capacity = 0;
    55. }
    56. // 入栈
    57. void StackPush(ST* ps, STDataType x)
    58. {
    59. assert(ps);
    60. // 满了->增容
    61. if (ps->top == ps->capacity)
    62. {
    63. STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
    64. if (tmp == NULL)
    65. {
    66. printf("realloc fail!\n");
    67. exit(-1);
    68. }
    69. else
    70. {
    71. ps->a = tmp;
    72. ps->capacity *= 2;
    73. }
    74. }
    75. ps->a[ps->top] = x;
    76. ps->top++;
    77. }
    78. // 出栈
    79. void StackPop(ST* ps)
    80. {
    81. assert(ps);
    82. // 栈空了,再调用Pop,就会直接中止程序报错
    83. assert(ps->top > 0);
    84. //ps->a[ps->top - 1] = 0; //置为0只考虑了int型等,若为char、double等就不适用了。
    85. ps->top--;
    86. }
    87. //取栈顶数据
    88. STDataType StackTop(ST* ps)
    89. {
    90. assert(ps);
    91. // 栈空了,再调用Top,就会直接中止程序报错
    92. assert(ps->top > 0);
    93. return ps->a[ps->top - 1];
    94. }
    95. //求栈大小
    96. int StackSize(ST* ps)
    97. {
    98. assert(ps);
    99. return ps->top;
    100. }
    101. //判空
    102. bool StackEmpty(ST* ps)
    103. {
    104. assert(ps);
    105. return ps->top == 0;
    106. }
    107. //maze实现
    108. ST path; //定义全局变量
    109. //打印
    110. void PrintMaze(int** maze, int N, int M)
    111. {
    112. for (int i = 0; i < N; ++i)
    113. {
    114. for (int j = 0; j < M; ++j)
    115. {
    116. printf("%d ", maze[i][j]);
    117. }
    118. printf("\n");
    119. }
    120. printf("\n");
    121. }
    122. // 输出栈里面的坐标路径
    123. //要求从最开始的坐标先输出,而栈是后进先出,需要调整输出
    124. void PirntPath(ST* ps)
    125. {
    126. // path数据倒到rPath
    127. ST rPath;
    128. StackInit(&rPath);
    129. while (!StackEmpty(&path))
    130. {
    131. StackPush(&rPath, StackTop(&path));
    132. StackPop(&path);
    133. }
    134. while (!StackEmpty(&rPath))
    135. {
    136. PT top = StackTop(&rPath);
    137. printf("(%d,%d)\n", top.row, top.col);
    138. StackPop(&rPath);
    139. }
    140. StackDestory(&rPath);
    141. }
    142. //判断该坐标是否为有效可遍历的坐标
    143. bool IsPass(int** maze, int N, int M, PT pos)
    144. {
    145. if (pos.row >= 0 && pos.row < N
    146. && pos.col >= 0 && pos.col < M
    147. && maze[pos.row][pos.col] == 0)
    148. {
    149. return true;
    150. }
    151. else
    152. {
    153. return false;
    154. }
    155. }
    156. //判断路径坐标进行可以进行遍历
    157. bool GetMazePath(int** maze, int N, int M, PT cur)
    158. {
    159. StackPush(&path, cur);
    160. // 如果走到出口
    161. if (cur.row == N - 1 && cur.col == M - 1)
    162. return true;
    163. // 探测cur位置得上、下、左、右四个方向
    164. PT next;
    165. maze[cur.row][cur.col] = 2; //已经遍历过的(包括当前)位置,标记为2
    166. //向上遍历
    167. next = cur;
    168. next.row -= 1;
    169. if (IsPass(maze, N, M, next))
    170. {
    171. if (GetMazePath(maze, N, M, next))
    172. return true;
    173. }
    174. //向下遍历
    175. next = cur;
    176. next.row += 1;
    177. if (IsPass(maze, N, M, next))
    178. {
    179. if (GetMazePath(maze, N, M, next))
    180. return true;
    181. }
    182. //向左遍历
    183. next = cur;
    184. next.col -= 1;
    185. if (IsPass(maze, N, M, next))
    186. {
    187. if (GetMazePath(maze, N, M, next))
    188. return true;
    189. }
    190. //向右遍历
    191. next = cur;
    192. next.col += 1;
    193. if (IsPass(maze, N, M, next))
    194. {
    195. if (GetMazePath(maze, N, M, next))
    196. return true;
    197. }
    198. StackPop(&path); //不通,这个坐标不能使用,就出栈
    199. return false; //四个方向都不通,就返回false,递归会回溯,探索其他通路
    200. }
    201. int main()
    202. {
    203. int N = 0, M = 0;
    204. while (scanf("%d%d", &N, &M) != EOF)
    205. {
    206. // int a[n][m]; // vs2013 不支持
    207. // 动态开辟二维数组
    208. int** maze = (int**)malloc(sizeof(int*) * N);
    209. for (int i = 0; i < N; ++i)
    210. {
    211. maze[i] = (int*)malloc(sizeof(int) * M);
    212. }
    213. // 二维数组的输入
    214. for (int i = 0; i < N; ++i)
    215. {
    216. for (int j = 0; j < M; ++j)
    217. {
    218. scanf("%d", &maze[i][j]);
    219. }
    220. }
    221. StackInit(&path);
    222. // PrintMaze(maze, N, M);
    223. PT entry = { 0, 0 }; //入口
    224. if (GetMazePath(maze, N, M, entry))
    225. {
    226. //printf("找到通路\n");
    227. PirntPath(&path);
    228. }
    229. else
    230. {
    231. printf("没有找到通路\n");
    232. }
    233. StackDestory(&path);
    234. for (int i = 0; i < N; ++i)
    235. {
    236. free(maze[i]);
    237. }
    238. free(maze);
    239. maze = NULL;
    240. }
    241. return 0;
    242. }

    二、迷宫最短路径问题: 

    1. 迷宫最短路径问题链接:(牛客网)地下迷宫

    1.1 问题描述:

     1.2 问题分析:

    本题在曾经是美团某一年的笔试题,本题在上一个题的基础上计入了体力值的概念,但是本题还有一个隐藏条件,就是要找出迷宫的最短路径,如下图的两个测试用例,需要找出最短路径,才能通过全部测试用例。本题依旧借助深度优先遍历(DFS)和回溯法解决。这里涉及到四路递归,(上、下、左、右四个方向的递归)。
    分析问题,确定求解方法
    根据题目可知:相比于“迷宫问题”,这题在上题的基础上进行了“变种”,即引入了体力值”和最短路径而且路径也从“只有一条”变为了“多条路径”,因此,要进行分析,将各个功能模块封装后,依据“高内聚,低耦合”的设计原则进行组织。

    1.3 功能函数实现:

    (1)定义迷宫坐标的结构体:

    (2)写出调用逻辑(主函数):

    (3)获取有效遍历的有效路径坐标: 

    (4)解决浅拷贝的两次释放空间会报错问题:

     

    (5)判断该坐标是否为有效可遍历的坐标:

     

    (6)输出栈里存储的路径坐标:

    由于栈的“FIFO”性质,又题目要求按照从最开始打印,则需要进行顺序调整。

     

    (7)打印遍历的有效路径坐标:

     

     1.4 栈的模拟实现:

    (这里借助栈的“FIFO”性质,借用栈存储递归遍历的数据存储,不符合的坐标就出栈,再进行回溯遍历)

     1.5测试用例及演示:

    示例1:

    1.6 完整源码:

    1. //2.迷宫最短路径问题
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. //定义迷宫坐标的结构体
    8. typedef struct Postion
    9. {
    10. int row;
    11. int col;
    12. }PT;
    13. /
    14. //栈的实现
    15. typedef PT STDataType;
    16. typedef struct Stack
    17. {
    18. STDataType* a; //通过数组实现栈的结构
    19. int top;
    20. int capacity;
    21. }ST;
    22. //初始化
    23. void StackInit(ST* ps);
    24. //释放内存、销毁空间
    25. void StackDestory(ST* ps);
    26. // 入栈
    27. void StackPush(ST* ps, STDataType x);
    28. // 出栈
    29. void StackPop(ST* ps);
    30. //取栈顶数据
    31. STDataType StackTop(ST* ps);
    32. //栈的大小
    33. int StackSize(ST* ps);
    34. //判空
    35. bool StackEmpty(ST* ps);
    36. //初始化
    37. void StackInit(ST* ps)
    38. {
    39. assert(ps);
    40. ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
    41. if (ps->a == NULL)
    42. {
    43. printf("malloc fail!\n");
    44. exit(-1);
    45. }
    46. ps->capacity = 4;
    47. ps->top = 0; //这使得top最终指向的是栈顶的后一个位置。若top=-1,则最终指向的是栈顶。
    48. }
    49. //释放内存、销毁空间
    50. void StackDestory(ST* ps)
    51. {
    52. assert(ps);
    53. free(ps->a);
    54. ps->a = NULL;
    55. ps->top = ps->capacity = 0;
    56. }
    57. // 入栈
    58. void StackPush(ST* ps, STDataType x)
    59. {
    60. assert(ps);
    61. // 满了->增容
    62. if (ps->top == ps->capacity)
    63. {
    64. STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
    65. if (tmp == NULL)
    66. {
    67. printf("realloc fail!\n");
    68. exit(-1);
    69. }
    70. else
    71. {
    72. ps->a = tmp;
    73. ps->capacity *= 2;
    74. }
    75. }
    76. ps->a[ps->top] = x;
    77. ps->top++;
    78. }
    79. // 出栈
    80. void StackPop(ST* ps)
    81. {
    82. assert(ps);
    83. // 栈空了,再调用Pop,就会直接中止程序报错
    84. assert(ps->top > 0);
    85. //ps->a[ps->top - 1] = 0; //置为0只考虑了int型等,若为char、double等就不适用了。
    86. ps->top--;
    87. }
    88. //取栈顶数据
    89. STDataType StackTop(ST* ps)
    90. {
    91. assert(ps);
    92. // 栈空了,再调用Top,就会直接中止程序报错
    93. assert(ps->top > 0);
    94. return ps->a[ps->top - 1];
    95. }
    96. //求栈大小
    97. int StackSize(ST* ps)
    98. {
    99. assert(ps);
    100. return ps->top;
    101. }
    102. //判空
    103. bool StackEmpty(ST* ps)
    104. {
    105. assert(ps);
    106. return ps->top == 0;
    107. }
    108. //maze实现
    109. ST path; //定义全局变量
    110. ST minpath; //定义最短路径
    111. //打印
    112. void PrintMaze(int** maze, int N, int M)
    113. {
    114. for (int i = 0; i < N; ++i)
    115. {
    116. for (int j = 0; j < M; ++j)
    117. {
    118. printf("%d ", maze[i][j]);
    119. }
    120. printf("\n");
    121. }
    122. printf("\n");
    123. }
    124. // 输出栈里面的坐标路径
    125. //要求从最开始的坐标先输出,而栈是后进先出,需要调整输出
    126. void PirntPath(ST* ps)
    127. {
    128. // path数据倒到rPath
    129. ST rPath;
    130. StackInit(&rPath);
    131. while (!StackEmpty(ps))
    132. {
    133. StackPush(&rPath, StackTop(ps));
    134. StackPop(ps);
    135. }
    136. //输出分为两种,仅因为题目要求,这里没有什么特定含义
    137. while (StackSize(&rPath)>1)
    138. {
    139. PT top = StackTop(&rPath);
    140. printf("[%d,%d],", top.row, top.col);
    141. StackPop(&rPath);
    142. }
    143. PT top = StackTop(&rPath);
    144. printf("[%d,%d]", top.row, top.col);
    145. StackPop(&rPath);
    146. StackDestory(&rPath);
    147. }
    148. //判断该坐标是否为有效可遍历的坐标
    149. bool IsPass(int** maze, int N, int M, PT pos)
    150. {
    151. if (pos.row >= 0 && pos.row < N
    152. && pos.col >= 0 && pos.col < M
    153. && maze[pos.row][pos.col] == 1)
    154. {
    155. return true;
    156. }
    157. else
    158. {
    159. return false;
    160. }
    161. }
    162. //解决浅拷贝的两次释放空间报错问题
    163. void StackCopy(ST* ppath, ST* pcopy)
    164. {
    165. pcopy->a = (STDataType*)malloc(sizeof(STDataType*) * ppath->capacity);
    166. memcpy(pcopy->a, ppath->a, sizeof(STDataType) * ppath->top);
    167. pcopy->top = ppath->top;
    168. pcopy->capacity = ppath->capacity;
    169. }
    170. //获取可有效遍历的路径坐标
    171. void GetMazePath(int** maze, int N, int M, PT cur,int P)
    172. {
    173. StackPush(&path, cur);
    174. // 如果走到出口
    175. if (cur.row == 0 && cur.col == M - 1)
    176. {
    177. // 找到了更短的路径,更新minpath; //P是题目要求的体力值,要求在有效范围内
    178. if (P >= 0 && StackEmpty(&minpath)
    179. || StackSize(&path) < StackSize(&minpath))
    180. {
    181. StackDestory(&minpath);
    182. StackCopy(&path, &minpath);
    183. }
    184. }
    185. // 探测cur位置得上下左右四个方向
    186. PT next;
    187. maze[cur.row][cur.col] = 2; //已经走过得,标记为2
    188. //向上遍历
    189. next = cur;
    190. next.row -= 1;
    191. if (IsPass(maze, N, M, next))
    192. {
    193. (GetMazePath(maze, N, M, next, P - 3));
    194. }
    195. //向下遍历
    196. next = cur;
    197. next.row += 1;
    198. if (IsPass(maze, N, M, next))
    199. {
    200. (GetMazePath(maze, N, M, next, P));
    201. }
    202. //向左遍历
    203. next = cur;
    204. next.col -= 1;
    205. if (IsPass(maze, N, M, next))
    206. {
    207. (GetMazePath(maze, N, M, next, P - 1));
    208. }
    209. //向右遍历
    210. next = cur;
    211. next.col += 1;
    212. if (IsPass(maze, N, M, next))
    213. {
    214. (GetMazePath(maze, N, M, next, P - 1));
    215. }
    216. //递归会回溯,探索其他通路
    217. //需要恢复
    218. maze[cur.row][cur.col] = 1;
    219. StackPop(&path);
    220. }
    221. int main()
    222. {
    223. int N = 0, M = 0, P = 0;
    224. while (scanf("%d%d%d", &N, &M,&P) != EOF)
    225. {
    226. //创建迷宫,获取迷宫地图
    227. // int a[n][m]; // vs2013 不支持
    228. // 动态开辟二维数组
    229. int** maze = (int**)malloc(sizeof(int*) * N);
    230. for (int i = 0; i < N; ++i)
    231. {
    232. maze[i] = (int*)malloc(sizeof(int) * M);
    233. }
    234. // 二维数组的输入
    235. for (int i = 0; i < N; ++i)
    236. {
    237. for (int j = 0; j < M; ++j)
    238. {
    239. scanf("%d", &maze[i][j]);
    240. }
    241. }
    242. StackInit(&path);
    243. StackInit(&minpath);
    244. // PrintMaze(maze, N, M);
    245. PT entry = { 0, 0 }; // 默认[0,0]是入口,[N-1][M-1]是出口
    246. GetMazePath(maze, N, M, entry, P); //获取遍历到的有效路径
    247. if (!StackEmpty(&minpath))
    248. {
    249. PirntPath(&minpath);
    250. }
    251. else
    252. {
    253. printf("Can not escape!\n");
    254. }
    255. StackDestory(&path);
    256. StackDestory(&minpath);
    257. //销毁迷宫
    258. for (int i = 0; i < N; ++i)
    259. {
    260. free(maze[i]);
    261. }
    262. free(maze);
    263. maze = NULL;
    264. }
    265. return 0;
    266. }

    三、总结:

     通过迷宫问题及最短路径问题,借助深度优先遍历(DFS)和回溯法求解,这里涉及到四路递归(上、下、左、右四个方向的递归),然而解决方法是一方面,解决工具也是另一方面。这里采用C语言实现求解,借助数据结构中栈的性质,实现深度遍历过程中的数据存储,然后回溯。使用C语言,涉及到二维数组的操作,这里使用了二级指针和指针数组,而在最短路径的比较更新中,涉及到了深浅拷贝问题,这里也需要注意,否则程序将会崩溃!

    解决这些题的方法还有很多,解决的工具也有很多,这里不再一一列举!

    最后,向前人的智慧结晶致敬!学者,亦有敬畏之心!亦当珍惜学习的机会与时光!

    后记:
    ●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!

                                                                           ——By 作者:新晓·故知

  • 相关阅读:
    关于面试,95%会问到的Java面试题(高级部分)
    SSH安全外壳协议
    mp4视频太大怎么压缩变小?
    【嵌入式开源库】EasyLogger的使用, 一款轻量级且高性能的日志库
    用于构建用户界面的JavaScript库--->React
    三台linux服务器部署ceph集群
    皕杰报表之调整css样式
    擎创技术流 | ckman教程(3)CKman源码分析部署集群的主要步骤
    为什么数据集中的mask是彩色的?
    安装GPT 学术优化 (GPT Academic)@FreeBSD
  • 原文地址:https://blog.csdn.net/m0_57859086/article/details/126665880