参考:
《数据结构教程》第五版 李春葆
⼀,递归到⾮递归的转换1,递归的分类
递归可分为尾递归和⾮尾递归2,尾递归
① 如果⼀个递归过程会 递归函数中的递归调⽤语句是最后⼀条语句,则称这种递归调⽤为尾递归 ② ⼀般情况下,尾递归可以通过循环或者迭代⽅式转化为等价的⾮递归算法。3,⾮尾递归
① 对于⾮尾递归算法 ,在理解递归调⽤实现过程的基础上可以⽤栈来模拟递归执⾏过程。4,总结
尾递归需要只⽤到递归函数的搜索功能,所以可以⽤循环替换;
⽽⾮尾递归除了循环部分,还⽤到了递归函数的回溯功能,所以只能⽤栈模拟。
⼆,⼀个栈帧只有⼀个递归函数的递归转化1,迷宫寻路递归:
#define _CRT_SECURE_NO_WARNINGS#include int dx[4] = { -1,0,1,0 }, dy[4] = { 0,1,0,-1 };struct Node{ int x, y;}way[N];void show(){ printf(\"(0,0)\"); for (int i = 0; i < cnt; i++) printf(\"->(%d,%d)\", way[i].x, way[i].y); puts(\"\");} void DFS(int a, int b){ if (a == n - 1 && b == m - 1) { show(); return; } for (int i = 0; i < 4; i++) { if (a + dx[i] < 0 || b + dy[i] < 0 || a + dx[i] >= n || b + dy[i] >= m) continue; if (map[a + dx[i]][b + dy[i]] == 0) { map[a][b] = 1; way[cnt].x = a + dx[i], way[cnt].y = b + dy[i]; cnt++; DFS(a + dx[i], b + dy[i]); cnt--; map[a][b] = 0; } }} int main(void){ // ⼊⼝是 map[0][0], 出⼝是 map[m-1][n-1] while (scanf(\"%d%d\", &n, &m) != EOF) { for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) scanf(\"%d\", &map[i][j]); cnt = 0; DFS(0, 0); } system(\"pause\"); return 0;}/* 测试数据第⼀组6 5 0 0 1 1 10 0 0 0 11 0 1 0 1 1 0 1 0 11 1 1 0 11 0 1 0 0结果 (0,0)->(0,1)->(1,1)->(1,2)->(1,3)->(2,3)->(3,3)->(4,3)->(5,3)->(5,4)(0,0)->(1,0)->(1,1)->(1,2)->(1,3)->(2,3)->(3,3)->(4,3)->(5,3)->(5,4)第⼆组6 5 0 0 1 1 10 0 0 0 11 0 1 0 11 0 1 0 11 0 1 0 11 0 0 0 0结果 (0,0)->(0,1)->(1,1)->(1,2)->(1,3)->(2,3)->(3,3)->(4,3)->(5,3)->(5,4)(0,0)->(0,1)->(1,1)->(2,1)->(3,1)->(4,1)->(5,1)->(5,2)->(5,3)->(5,4)(0,0)->(1,0)->(1,1)->(1,2)->(1,3)->(2,3)->(3,3)->(4,3)->(5,3)->(5,4)(0,0)->(1,0)->(1,1)->(2,1)->(3,1)->(4,1)->(5,1)->(5,2)->(5,3)->(5,4)*/ View Code 栈: #define _CRT_SECURE_NO_WARNINGS#include #define MaxSize 10086 int dx[] = { -1, 0, 1, 0 }, dy[] = { 0, 1, 0, -1 };int map[N][N];typedef struct Box{ int x, y; int di; // 点的编号}bx; typedef Box any; // 可修改数据类型typedef struct SqStack // 顺序栈{ #define MaxSize 666 any a[MaxSize]; int pt; // 栈顶指针 SqStack() { pt = -1; } void push(any e) { // ⼊栈 a[++pt] = e; } void pop() { // 出栈 pt--; } any top() { // 取栈顶元素 return a[pt]; } bool empty() { // 判断栈是否为空 return pt == -1; } int size() { // 返回栈的⼤⼩ return pt + 1; }}st; void show(st s){ st t; while (!s.empty()) { bx v = s.top(); s.pop(); t.push(v); } int cnt = 0; while (!t.empty()) { bx v = t.top(); t.pop(); if (cnt++ == 0) printf(\"(%d,%d)\", v.x, v.y); else printf(\"->(%d,%d)\", v.x, v.y); }puts(\"\");} int n, m; void dfs(int xi, int yi){ st s; // 定义栈 bx start = { xi,yi,0 }; map[xi][yi] = -1; s.push(start); // 起点进栈 int cnt = 1; // 记录路径数 while (!s.empty()) { // 1,取栈顶元素,相当于函数形参 bx vertex = s.top(); // 2,找到终点,回溯(关键点:消除搜索的痕迹) if (vertex.x == n - 1 && vertex.y == m - 1) { show(s); s.pop(); map[vertex.x][vertex.y] = 0; continue; } // 3,如果有路可⾛,就继续搜索 int find = 0; // find = 1 表⽰下⼀步可⾛,0 表⽰下⼀步不可⾛ for (int i = vertex.di; i < 4; i++) { bx next{ vertex.x + dx[i], next.y = vertex.y + dy[i] ,0 }; if (next.x < 0 || next.y < 0 || next.x >= n || next.y >= m) continue; if (map[next.x][next.y] == 0) { find = 1; vertex.di = i + 1; // 标记这个⽅块 已经⾛过的⽅向,⼿动确定回溯时不会重复回溯。 s.pop(); s.push(vertex); // 继续搜索 s.push(next); map[next.x][next.y] = 1; break; // ⼀次只⼊栈⼀个元素,同⼀层的其他元素在回溯时再⼊栈 } } // 4,⾛到死胡同,回溯(关键点:消除搜索的痕迹) if (!find) { s.pop(); map[vertex.x][vertex.y] = 0; } }} int main(void){ while (scanf(\"%d%d\", &n, &m) != EOF) { for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) scanf(\"%d\", &map[i][j]); dfs(0, 0); } system(\"pause\"); return 0;}/* 测试数据第⼀组6 5 0 0 1 1 10 0 0 0 11 0 1 0 11 0 1 0 11 1 1 0 11 0 1 0 0结果 (0,0)->(0,1)->(1,1)->(1,2)->(1,3)->(2,3)->(3,3)->(4,3)->(5,3)->(5,4)(0,0)->(1,0)->(1,1)->(1,2)->(1,3)->(2,3)->(3,3)->(4,3)->(5,3)->(5,4)第⼆组6 5 0 0 1 1 10 0 0 0 11 0 1 0 1 1 0 1 0 11 0 1 0 11 0 0 0 0结果 (0,0)->(0,1)->(1,1)->(1,2)->(1,3)->(2,3)->(3,3)->(4,3)->(5,3)->(5,4)(0,0)->(0,1)->(1,1)->(2,1)->(3,1)->(4,1)->(5,1)->(5,2)->(5,3)->(5,4)(0,0)->(1,0)->(1,1)->(1,2)->(1,3)->(2,3)->(3,3)->(4,3)->(5,3)->(5,4)(0,0)->(1,0)->(1,1)->(2,1)->(3,1)->(4,1)->(5,1)->(5,2)->(5,3)->(5,4)*/ View Code 2,⼋皇后递归: #define _CRT_SECURE_NO_WARNINGS#include int queen[10]; // queen[i] 代表第 i ⾏的皇后的位置int vis[10]; // vis[i] 代表第 i 列的皇后位置是否确认int n, cnt; bool judge(int n){ for (int i = 1; i < n; i++) // ⽐较 任意两个皇后的位置 { for (int j = i + 1; j <= n; j++) if (i - j == queen[i] - queen[j] || j - i == queen[i] - queen[j]) return false; } return true;} void show(){ printf(\"第%d种情况:\\n\", ++cnt); for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) if (j == queen[i]) printf(\"Q \"); else printf(\". \"); puts(\"\"); }puts(\"\");} void DFS(int k) // 循环⾏{ if (!judge(k-1)) { // 剪枝 return; } if (k > n) { show(); return; } for (int i = 1; i <= n; i++) // 循环列 { if (vis[i] > 0) continue; vis[i] = 1; queen[k] = i; DFS(k + 1); vis[i] = queen[k] = 0; }} int main(void){ while (scanf(\"%d\", &n) != EOF) { cnt = 0; DFS(1); } return 0;} View Code栈: #define _CRT_SECURE_NO_WARNINGS#include int queen[N], vis[N];typedef struct Position{ int row; // 标记循环到第⼏⾏ int col; // 标记循环到第⼏列 }pi; typedef Position any; // 可修改数据类型typedef struct SqStack // 顺序栈{ #define MaxSize 666 any a[MaxSize]; int pt; // 栈顶指针 SqStack() { pt = -1; } void push(any e) { // ⼊栈 a[++pt] = e; } void pop() { // 出栈 pt--; } any top() { // 取栈顶元素 return a[pt]; } bool empty() { // 判断栈是否为空 return pt == -1; } int size() { // 返回栈的⼤⼩ return pt + 1; }}st; bool judge(int n){ for (int i = 1; i < n; i++) // ⽐较 任意两个皇后的位置 { for (int j = i + 1; j <= n; j++) if (i - j == queen[i] - queen[j] || j - i == queen[i] - queen[j]) return false; } return true;} void show(){ printf(\"第%d种情况:\\n\", ++cnt); for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) if (j == queen[i]) printf(\"Q \"); else printf(\". \"); puts(\"\"); }puts(\"\");} void DFS(){ st s; s.push({1, 1}); // 起始于第⼀⾏第⼀列 while (!s.empty()) { // 1,取栈顶元素,相当于函数形参 pi vertex = s.top(); // 取上⼀次搜索的元素 s.pop(); pi last = {}; if (!s.empty()) last = s.top(); s.push(vertex); // 2,剪枝,,回溯(关键点:消除搜索的痕迹) if (s.size() > 1 && !judge(last.row)) // 已经确定了⼤于⼀个皇后的位置再检查 { s.pop(); vis[last.col - 1] = 0; queen[last.row] = 0; continue; } // 3,搜索完毕,回溯(关键点:消除搜索的痕迹) if (vertex.row > n) // 判断是否符合⼋皇后的条件 { show(); s.pop(); vis[last.col - 1] = 0; queen[last.row] = 0; continue; } // 4,如果有路可⾛,就继续搜索 int find = 0; for (int i = vertex.col; i <= n; i++) // col 初始化为 1 { pi next = { vertex.row + 1, 1 }; if (vis[i] == 1) continue; find = 1; s.pop(); s.push({ vertex.row, i + 1 }); // 标记这个⾏ 已经⾛过的列数,⼿动确定回溯时不会重复回溯。 vis[i] = 1; queen[vertex.row] = i; s.push(next); // 继续搜索 break; } // 5,⾛到死胡同,回溯(关键点:消除搜索的痕迹) if (s.size() > 1&&!find) { s.pop(); vis[last.col - 1] = 0; queen[last.row] = 0; continue; } } } int main(void){ while (scanf(\"%d\", &n) != EOF) { cnt = 0; DFS(); } system(\"pause\"); return 0;} View Code 3,总结 问题 ①: 栈⾥要存放的元素如何确定? 解决办法: 栈⾥存放的元素应是递归函数的形参,以便在搜索与回溯时能提取上⼀栈帧的信息。 问题 ②: 递归函数可以返回上⼀栈帧,利⽤当前栈帧独有的信息从⽽控制在回溯后不会发⽣重复搜索; 但栈在使⽤出⼊栈模拟回溯时,却⽆法利⽤上⼀栈帧独有的信息,如何解决? 解决办法: 将某⼀栈帧⾥控制回溯后不会发⽣重复搜索的变量保存在栈⾥。 如在迷宫寻路中,对于每⼀个⽅块结构体元素。我们增加⼀个变量 di 进⾏标记: di 取值 0,1,2,3,分别代表上,左,下,右。 表⽰该位置的⽅块此时应该向哪个⽅向搜索。 ⽽ 0123 的顺序限定了⽅块的搜索⽅向顺序,di 从 0 开始,每次加 1,到 di > 3 就代表遍历结束。 这样⼦,在回溯时就不会⾛上次⾛过的路了。 问题 ③: 为什么每次⼊栈时也只能⼊栈⼀个元素? 因为: 由于回溯时需要⽤到该路径上的所有元素,所以搜索时为了信息的完整,需要控制元素不能出栈,只有回溯时元素才能出栈。 ⼜因为元素不能出栈,所以每次⼊栈时也只能⼊栈⼀个元素,不然就会错乱不同路径的元素。(就像递归函数⼀样,每次只调⽤⼀次⾃⾝) 问题 ④: 为什么⼋皇后中,需要取出栈顶下⾯的第⼆个元素? 因为: 在迷宫寻路中,我们需要要标记的是下⼀个要搜索的元素,所以可以在下⼀栈帧要搜索的元素,就是标记过的元素; ⽽在⼋皇后中,我们需要标记的是当前栈帧的元素,所以在下⼀栈帧中要搜索的元素再上⼀个元素,才是标记过的元素。 三,⼀个栈帧有多个递归函数的递归转化1,汉诺塔 #define _CRT_SECURE_NO_WARNINGS#include typedef struct ElemType{ int n; char a, b, c; bool flag; }et; typedef struct SqStack{ #define N 666 et data[N]; int ptop; SqStack() { ptop = -1; } bool push(et e) { if (ptop + 1 == N) return false; data[++ptop] = e; return true; } bool pop() { if (ptop == -1) return false; ptop--; return true; } et top() { return data[ptop]; } bool empty() { return ptop == -1; }}st; void Hanoi1(int n, char A, char B, char C){ if (n == 1) printf(\"将第%d个盘⼦从%c移动到%c.\\n\", n, A, C); else { Hanoi1(n - 1, A, C, B); printf(\"将第%d个盘⼦从%c移动到%c.\\n\", n, A, C); Hanoi1(n - 1, B, A, C); }} void Hanoi2(int n, char A, char B, char C){ if (n <= 0) return; st s; et e = { n,A,B,C,false }; s.push(e); while (!s.empty()) { et vertex = s.top(); s.pop(); if (vertex.flag == false) // 注意栈是先进后出的,所以顺序要反过来 { // 相当于 Hanoi1(n - 1, B, A, C); et next3 = { vertex.n - 1,vertex.b,vertex.a,vertex.c,false }; if (next3.n == 1) next3.flag = true; s.push(next3); // 相当于 move(n, A, C); et next2 = { vertex.n,vertex.a,vertex.b,vertex.c,true }; s.push(next2); // 相当于 Hanoi1(n - 1, A, C, B); et next1 = { vertex.n - 1,vertex.a,vertex.c,vertex.b,false }; if (next1.n == 1) next1.flag = true; s.push(next1); } else printf(\"将第%d个盘⼦从%c移动到%c.\\n\", vertex.n, vertex.a, vertex.c); }} int main(void){ //printf(\"\\n⽤递归算法可得:\\n\"); //Hanoi1(4, 'A', 'B', 'C'); printf(\"⽤⾮递归算法可得汉诺塔的移动顺序为:\\n\"); Hanoi2(4, 'A', 'B', 'C'); system(\"pause\"); return 0;} View Code转化过程: 注意点 ①: 在递归函数中,调⽤函数的语句即代表⼊栈成功,出栈完成的语句。所以,其出栈顺序是 Hanoi(n-1, a, c, b),move(n, a, c),Hanoi(n-1, b, a, c) 那么,反映在⾮递归的栈⾥,其⼊栈顺序就是 Hanoi(n-1, b, a, c),move(n, a, c),Hanoi(n-1, a, c, b)。 因为,出⼊栈的顺序是反着来的。 注意点 ②: 因为⼊栈的既有 Hanoi,⼜有 move,所以需要另设⼀个变量区分两者,即代码中的 flag。 ============================================ 梦想是注定孤独的旅⾏,路上少不了质疑和嘲笑,但那⼜怎样,哪怕遍体鳞伤,也要活的漂亮
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- 99spj.com 版权所有 湘ICP备2022005869号-5
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务