递归法和回溯法.doc_第1页
递归法和回溯法.doc_第2页
递归法和回溯法.doc_第3页
递归法和回溯法.doc_第4页
递归法和回溯法.doc_第5页
已阅读5页,还剩6页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

递归法和回溯法有人说,回溯实际上是递归的展开,但实际上。两者的指导思想并不一致。打个比方吧,递归法好比是一个军队要通过一个迷宫,到了第一个分岔口,有3条路,将军命令3个小队分别去探哪条路能到出口,3个小队沿着3条路分别前进,各自到达了路上的下一个分岔口,于是小队长再分派人手各自去探路只要人手足够(对照而言,就是计算机的堆栈足够),最后必将有人找到出口,从这人开始只要层层上报直属领导,最后,将军将得到一条通路。所不同的是,计算机的递归法是把这个并行过程串行化了。而回溯法则是一个人走迷宫的思维模拟他只能寄希望于自己的记忆力,如果他没有办法在分岔口留下标记(电视里一演到什么迷宫寻宝,总有恶人去改好人的标记)。想到这里突然有点明白为什么都喜欢递归了,他能够满足人心最底层的虚荣难道你不觉得使用递归就象那个分派士兵的将军吗?想想汉诺塔的解法,也有这个倾向,“你们把上面的N1个拿走,我就能把下面的挪过去,然后你们在把那N1个搬过来”。笑谈,切勿当真。这两种方法的例程,我不给出了,网上很多。我只想对书上的递归解法发表点看法,因为书上的解法有偷梁换柱的嫌疑迷宫的储存不是用的二维数组,居然直接用岔路口之间的连接表示的简直是人为的降低了问题的难度。实际上,如果把迷宫抽象成(岔路口)点的连接,迷宫就变成了一个“图”,求解入口到出口的路线,完全可以用图的遍历算法来解决,只要从入口DFS到出口就可以了;然而,从二维数组表示的迷宫转化为图是个很复杂的过程。并且这种转化,实际上就是没走迷宫之前就知道了迷宫的结构,显然是不合理的。对此,我只能说这是为了递归而递归,然后还自己给自己开绿灯。但迷宫并不是只能用上面的方法来走,前提是,迷宫只要走出去就可以了,不需要找出一条可能上的最短路线确实,迷宫只是前进中的障碍,一旦走通了,没人走第二遍。下面的方法是一位游戏玩家提出来的,既不需要递归,也不需要栈来回溯玩游戏还是有收获的。另一种解法请注意我在迷宫中用粗线描出的路线,实际上,在迷宫中,只要从入口始终沿着一边的墙走,就一定能走到出口,那位玩家称之为“靠一边走”如果你不把迷宫的通路看成一条线,而是一个有面积的图形,很快你就知道为什么。编程实现起来也很简单。下面的程序在TC2中编译,不能在VC6中编译为了动态的表现人的移动情况,使用了gotoxy(),VC6是没有这个函数的,而且堆砌迷宫的219号字符是不能在使用中文页码的操作系统的32位的console程序显示出来的。如果要在VC6中实现gotoxy()的功能还得用API,为了一个简单的程序没有必要,所以,就用TC2写了,突然换到C语言还有点不适应。#include typedef struct hero int x,y,face; HERO;void set_hero(HERO* h,int x,int y,int face)h-x=x;h-y=y;h-face=face;void go(HERO* h)if(h-face%2) h-x+=2-h-face;else h-y+=h-face-1;void goleft(HERO* h)if(h-face%2) h-y+=h-face-2;else h-x+=h-face-1;void turnleft(HERO* h)h-face=(h-face+3)%4;void turnright(HERO* h)h-face=(h-face+1)%4;void print_hero(HERO* h, int b) gotoxy(h-x + 1, h-y + 1); if (b) switch (h-face) case 0: printf(%c, 24); break; case 1: printf(%c, 16); break; case 2: printf(%c, 25); break; case 3: printf(%c, 27); break; default: break; else printf( );int maze1010 = 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0;void print_maze() int i, j; for (i = 0; i 10; i+) for (j = 0; j 10; j+) if (mazeij) printf(%c, 219); else printf( ); printf(n); int gomaze(HERO* h) HERO t = *h; int i; for (i = 0; i = 0 & t.x = 0 & t.y x = 9 & h-y = 9) return 1; goleft(&t); if (h-x = 0 & h-y = 0) i+; if (t.x = 0 & t.x = 0 & t.y 10 & !mazet.yt.x) turnleft(h);/*左方无墙向左转*/ else turnright(h);/*前方不可走向右转*/ return 0; main() HERO Tom;/*有个英雄叫Tom*/ set_hero(&Tom, 0, 0, 0);/*放在(0,0)面朝北*/ clrscr(); print_maze(); gomaze(&Tom);/*Tom走迷宫*/总结书上讲的基本上就这些了,要是细说起来,几天几夜也说不完。前面我并没有讲如何写递归算法,实际上给出的都是非递归的方法,我也觉得有点文不对题。我的目的是使大家明白,能写出什么算法,主要看你解决问题的指导思想,换而言之,就是对问题的认识程度。所以初学者现在就去追求“漂亮”的递归算法,是不现实的,结果往往就是削足适履,搞的一团糟有位仁兄写了个骑马游世界的“递归”程序,在我机器上10分钟没反映。其实优秀的递归算法是在对问题有了清楚的认识后才会得出的。最后说说用汇编语言写递归函数。我的汇编水平并不高,不过我想说的是用汇编写递归函数,绝对不像汇编与c解决递归问题之比较/develop/article/17/17597.shtm那篇文章说的,实际上比高级语言并不复杂,甚至在masm32v7中,和高级语言一样,因为那里面有一句很象代参函数调用的INVOKE expression ,arguments。那位作者显然连教科书都没看全,因为在我们的讲8086汇编语言的书上就有一个阶乘的递归函数例程,如果他看过,就不会有那个结论了。 迷宫关于迷宫,有一个引人入胜的希腊神话,这也是为什么现今每当人们提到这个问题,总是兴致勃勃(对于年青人,估计是RPG玩多了),正如虽然九宫图连小学生都能做出来,我们总是自豪的说那叫“洛书”。这个神话我不复述了,有兴趣的可以在搜索引擎上输入“希腊神话 迷宫”,就能找到很多的介绍。迷宫的神话讲述了一位英雄如何靠着“线团”杀死了牛头怪(玩过英雄无敌的朋友一定知道要想造牛头怪,就必须建迷宫,也是从这里来的),我看到的一本编程书上援引这段神话讲述迷宫算法的时候,不知是有意杜撰,还是考证不严,把这个过程叙述成:英雄靠着线团的帮助在走过的路上铺线,每到分岔口向没铺线的方向前进,如果遇到死胡同,沿铺的线返回,并铺第二条线走进了迷宫深处,杀死了牛头怪。然而,神话传说讲的是,英雄被当成贡品和其他的孩子送到了迷宫的深处,英雄杀死了牛头怪,靠着线团标识的路线退出了迷宫。实际上,这个线团只是个“栈”,远没有现代人赋予给它的“神奇作用”。我想作者也是RPG玩多了,总想着怎样“勇者斗恶龙”,然而,实际上却是“胜利大逃亡”。迷宫问题实际上是一个心理测试,它反映了测试者控制心理稳定的能力在一次次失败后,是否失去冷静最终陷在迷宫之中,也正体现了一句诗,“不识庐山真面目,只缘身在此山中”。换而言之,我们研究迷宫的计算机解法,并没有什么意义,迷宫就是为人设计的,而不是为机器设计的,它之所以称为“迷”宫,前提是人的记忆准确性不够高;假设人有机器那样的准确的记忆,只要他不傻,都能走出迷宫。现在可能有人用智能机器人的研究来反驳我,实际上,智能机器人是在更高的层面上模拟人的思考过程,只要它完全再现了人的寻路过程,它就能走出迷宫。但是,研究迷宫生成的计算机方法,却是有意义的,因为人们总是有虐待自己的倾向(不少人在RPG里的迷宫转了三天三夜也不知道疲倦),呵呵,笑谈。不管怎么说,还是亲自研究一下计算机怎么走迷宫吧。迷宫的存储按照惯例,用一个二维数组来表示迷宫,0表示墙,1表示通路,以后我们的程序都走下面这个迷宫。 #include #include using namespace std;class Needlepublic: Needle() a.push_back(100); /每一个柱子都有一个底座 void push(int n) a.push_back(n); int top() return a.back(); int pop() int n = a.back(); a.pop_back(); return n; int movenum(int n) int i = 1;while (ai n) i+; return a.size() - i; int size() return a.size(); int operator (int n) return an; private: vector a;void Hanoi(int n) Needle needle3, ns;/3个柱子,ns是转换柱子时的保存栈,借用了Needle的栈结构 int source = 0, target, target_m = 2, disk, m = n; for (int i = n; i 0; i-) needle0.push(i);/在A柱上放n个盘子 while (n)/问题规模为n,开始搬动 if (!m) source = ns.pop(); target_m = ns.pop();m = needlesource.movenum(ns.pop(); /障碍盘子搬走后,回到原来的当前柱 if (m % 2) target = target_m; else target = 3 - source - target_m;/规律1的实现 if (needlesource.top() needletarget.top()/当前柱顶端盘子可以搬动时,移动盘子 disk = needlesource.top();m-; cout disk move (char)(source + 0x41) to (char)(target + 0x41) endl;/显示搬动过程 needletarget.push(needlesource.pop();/在目标柱上面放盘子 if (disk = n) source = 1 - source; target_m = 2; m = -n; 规律3的实现 else/规律2的实现 ns.push(needlesourceneedlesource.size() - m);ns.push(target_m); ns.push(source); m = needletarget.movenum(needlesource.top(); target_m = 3 - source - target; source = target; 这个算法实现比递归算法复杂了很多(递归算法在网上、书上随便都可以找到),而且还慢很多,似乎是多余的,然而,这是有现实意义的。我不知道现在还在搬64个盘子的僧人是怎么搬的,不过我猜想一定不是先递归到1个盘子,然后再搬等递归出来,估计胡子一打把了(能不能在人世还两说)。我们一定是马上决定下一步怎么搬,就如我上面写的那样,这才是人的正常思维,而用递归来思考,想出来怎么搬的时候,黄瓜菜都凉了。

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论