如何从递归里面快速退出?

Asimov 2002-08-15 11:45:04
我写了一个递归函数,查找某个东西,假设我在递归的某一层找到了,希望就此返回到主程序,即,从递归的某一层直接返回调用它的主程序里,怎么做?用return只能返回递归的上一层。
...全文
3158 90 打赏 收藏 转发到动态 举报
写回复
用AI写文章
90 条回复
切换为时间正序
请发表友善的回复…
发表回复
jiangfan0576 2010-06-03
  • 打赏
  • 举报
回复
在每层设置判断,若符合条件则退出,退出当然用return啦!因为符合条件到上一层也会自动退出。
这样就可以啦
bbsRoson 2002-08-24
  • 打赏
  • 举报
回复
要这样的话,那还不如开个多线程来跑,到时,直接退出就可以了!
lx_cyh 2002-08-24
  • 打赏
  • 举报
回复
to ajoo:
现在我挺喜欢你的,因为我看到了你的另一面和以前我的看法相辅相成,活生生的.

其实setjmp/longjmp 我也没真正用过,只是看过一点说明而已."如果你真地很清楚,那我对我的怀疑道歉。但我相信C库的设计者的程度要超过相信你无数倍。
C库的设计者不会连栈顶指针这种问题都考虑不到吧?"我这么说也是因为我对C的栈式结构,setjmp/longjmp 的理解,对C库的设计者的相信(实际就是我对自己并不十分肯定
setjmp/longjmp 肯定是有side effect的,如过程中malloc以后有可能没被free掉.
的确它不是完美的.如在实际情况中,我也会同意你的看法."but anyway, I would choose iteration rather than resorting to setjmp/longjmp."
最后,为我在过程中的不好行为致歉!

ajoo 2002-08-23
  • 打赏
  • 举报
回复
many sorries to lx_cyh!
My fault.
setjmp/longjmp does not have stack problem. as long as the statements after the recursive call have no side-effect (like destructor, close(), delete etc), setjmp/longjmp should work.
Slap me!

but anyway, I would choose iteration rather than resorting to setjmp/longjmp.

50 points off ajoo!
Elkel 2002-08-23
  • 打赏
  • 举报
回复
不知道我会引入什么错误;)

///////////////////////////////////////
//
// 递归算法
//
///////////////////////////////////////
Tree *find(Tree *t, string s)
{
Tree *r = NULL;
if(t->s == s)
r = t;
else
{
if(t->left)
r = find(t->left, s);
if(!r && t->right)
r = find(t->right, s);
}
return r;
}

//////////////////////////////////////////
//
// 非递归算法
//
//////////////////////////////////////////
struct context
{
Tree *t;
int flag; // 0 第一次访问
// 1 已经访问过左子树
// 2 已经访问过右子树
}

Tree *find(Tree *t, string s)
{
stack<struct context> mystack;
Tree *r = NULL;
struct context ctx = { t, 0 };

while(1)
{
if(flag == 0)
{
if(ctx.t->s == s)
{
r = ctx.t;
break;
}
else
{
if(cur->left)
{
ctx.flag = 1;
mystack.push(ctx);
ctx.t = ctx.t->left;
ctx.flag = 0;
continue;
}
if(cur->right)
{
ctx.flag = 2;
myStack.push(ctx);
ctx.t = ctx.t->right;
flag = 0;
continue;
}
ctx = mystack.pop();
}
}
else if(flag == 1)
{
if(cur->right)
{
ctx.flag = 2;
myStack.push(ctx);
ctx.t = ctx.t->right;
flag = 0;
continue;
}
ctx = mystack.pop();
}
else
ctx.t = mystack.pop();

if(!mystack.size())
break;
}
return r;
}
Elkel 2002-08-23
  • 打赏
  • 举报
回复
想提高性能为什么还要用递归??? -_-#
pi1ot 2002-08-23
  • 打赏
  • 举报
回复
头一回听说用goto来退出递归的,上大学怎么学的啊,该打屁股。

不会和我一样整天打Quake了吧,哈哈。
lx_cyh 2002-08-23
  • 打赏
  • 举报
回复
to ajoo
你又来吓我了。告诉我,你真的非常清楚setjmp和longjmp的语义吗?如果你真地很清楚,那我对我的怀疑道歉。但我相信C库的设计者的程度要超过相信你无数倍。
C库的设计者不会连栈顶指针这种问题都考虑不到吧?

"前面嚷嚷要用setjmp的朋友,你歇了吧。就算你用C, 不需要担心析构函数的调用。那你的栈顶指针谁帮你恢复啊?好嘛。您老运行这个函数10次后,堆栈就溢出了!程序运行都有问题,还效率个啥呀?
你干脆拿鞭子抽一抽cpu, 没准儿倒会快一点呢。“
Elkel 2002-08-23
  • 打赏
  • 举报
回复
faint,果然有问题。退出循环的条件不对。
Tree *find(Tree *t, string s)
{
stack<struct context> mystack;
Tree *r = NULL;
struct context ctx = { t, 0 };

while(1)
{
if(flag == 0)
{
if(ctx.t->s == s)
{
r = ctx.t;
break;
}
else
{
if(cur->left)
{
ctx.flag = 1;
mystack.push(ctx);
ctx.t = ctx.t->left;
ctx.flag = 0;
continue;
}
if(cur->right)
{
ctx.flag = 2;
myStack.push(ctx);
ctx.t = ctx.t->right;
flag = 0;
continue;
}
if(!mystack.size())
break;
ctx = mystack.pop();
}
}
else if(flag == 1)
{
if(cur->right)
{
ctx.flag = 2;
myStack.push(ctx);
ctx.t = ctx.t->right;
flag = 0;
continue;
}
if(!mystack.size())
break;
ctx = mystack.pop();
}
else
{
if(!mystack.size())
break;
ctx.t = mystack.pop();
}
}
return r;
}
丑陋的实现,谁能给一个更好的实现。先谢了
zheng_can 2002-08-22
  • 打赏
  • 举报
回复
首先,我对说是用 goto 的同仁们感到钦佩 :)

我觉得如果在递归函数里没有申请内存之类必须撤销的动作的话
内嵌汇编应该是最好的:
就帖主的例子,在主函数里执行递归调用前
保存 SS 和 SP(一般只需 SP 即可)
递归退出时,通过重新设置 SS、SP、CS、IP 的值来达到目的

至于楼上说的标志位等,也可以在保存 SP 的同时一并存了
ajoo 2002-08-22
  • 打赏
  • 举报
回复
呵呵,有点乱说了。为免怡笑方家,特此更正:
一个变量是否压栈不是由寄存器分配决定的。
它是静态代码分析里变量生存期分析的责任。
如果调用一个函数之后,一个变量的值仍可能被读,它就是live的,就要压栈保存。
变量生存期分析的算法很容易,所以任何一个象样的编译器都会做的很好。它也是寄存器分配的基础,不过寄存器分配则是NP问题。


ajoo 2002-08-22
  • 打赏
  • 举报
回复
anrxhzh,
没觉得你的代码有什么问题呀。
这是我前面写的版本,有错误请指正。
Tree* find(Tree* t, T data){
//throw exception if not found
if(t->data == data)return t;
try{
return find(t->getLeft(), data);
}
catch(char* msg){
return find(t->getRight(), data);
}
}
关于编译器优化,我觉得如果你用的是比较新的,这种寄存器分配优化是必不可少的。
虽然编译器用的是次优算法,但结果肯定也比你自己绞尽脑汁出来的差不多甚至更好。
就象我前面给出的例子,a+b+c往往会被编译器先处理成
int tmp = a+b;
tmp+c;
所以代码表面上有没有临时变量真的无足轻重。


我以前曾问过微软内部技术支持的人,VC是支持尾递归优化的。
相信gcc肯定也支持。

xiaonian_3654 2002-08-22
  • 打赏
  • 举报
回复
我 xiaonian_3654(小周)说了n遍
可是有些人就是不听,goto连语法都不通!!!
肯定给你个lable not defined!
icr_mio 2002-08-22
  • 打赏
  • 举报
回复
从递归里面快速退出?
递归到达最底层后的任务不就只剩下返回和释放内存吗?有什么不好?
就算有方法快速退出了,内存不用释放了吗?
goto肯定不行。
anrxhzh 2002-08-22
  • 打赏
  • 举报
回复
我前面曾经做过分析:
“假设返回操作的时间为X,则估算进入操作(压栈)的时间为X,函数平均执行时间为20X(需测试,跟数据分布有关,这里假设字符串的平均长度为5),则X占整个调用时间的比例约为1/22,就算我们把X优化到0,也只能调高5%左右,意义不大。”

看来和实验结果有些出入。
anrxhzh 2002-08-22
  • 打赏
  • 举报
回复
ajoo,我想明白了,我的那个算法没有错误,你的也没有错误。哎,我是不是比较笨?还是回到效率问题上来吧。我的分析是:一般情况下,异常比正常的返回要慢百倍左右(有测试依据),所以我预测用异常来退出递归的方法在递归深度大于100时将在效率上有所提高。空口无凭,我写了个测试程序来证实这一预测。下面是测试结果和源代码:

次数 100000 深度 0
hello 40
hello 962

次数 100000 深度 200
hello 7501
hello 7321

次数 10000 深度 1000
hello 3896
hello 3304

#include <windows.h>
#include <iostream>
#include <string>
using std::string;

struct Tree {
Tree()
:left(0),right(0)
{}
Tree *left,*right;
string str;
};

Tree tree;
const int count(100000);
const int deep(200);
const string fs("hello");

Tree* fnd(Tree* p,const string& s)
{
if( p && s!=p->str ) {
Tree* __p=p;
if( !(p=fnd(__p->left,s)) ) p=fnd(__p->right,s);
}
return p;
}

void fnd2(Tree* p,const string& s)
{
if(p) {
if(s==p->str) throw p; //found s
if(p->left) fnd2(p->left,s);
if(p->right) fnd2(p->right,s);
}
}

void build()
{
Tree* p=&tree;
for(int i=0; i<deep; i++,p=p->left)
p->left = new Tree;
p->str = fs;
}

int main()
{
std::cout << "次数 " << count << " 深度 " << deep << std::endl;
build();
DWORD t0,t1;
t0=GetTickCount();
Tree* p=0;
for(int i=0;i<count;i++)
p=fnd(&tree,fs);
t1=GetTickCount();
std::cout << p->str << " " << t1-t0 << std::endl;

t0=GetTickCount();
for(i=0;i<count;i++){
try{
fnd2(&tree,fs);
}
catch(Tree* e){
p=e;
}
}
t1=GetTickCount();
std::cout << p->str << " " << t1-t0 << std::endl;
}

ksyou 2002-08-21
  • 打赏
  • 举报
回复
xuexi
anrxhzh 2002-08-21
  • 打赏
  • 举报
回复
to ajoo:

你的想法很有趣,也是可行的方案。
理论上,tail recursion 并不是递归,因为它没有 stack frame。也就是说tail recursion 等价于迭代算法。我觉得你的方案实际上是将一个递归算法转换成了迭代算法。在我看来,递归的最大价值就是它简单清晰,容易理解。
我想没有人会否认下面的递归算法比你的迭代算法(tail recursion)更容易理解。
Tree* fnd(Tree* p,const string& s)
{
if( p && s!=p->str ) {
Tree* __p=p;
if( !(p=fnd(__p->left,s)) ) p=fnd(__p->right,s);
}
return p;
}
我们也应该承认,下面的递归算法比上面的递归算法更加容易理解。
void fnd(Tree* p,const string& s)
{
if(s==p->str) throw p; //found s
if(p->left) fnd(p->left,s);
if(p->right) fnd(p->right,s);
}
回到效率问题上来,你给出的方案在节点数据比较庞大并且搜索深度比较高的情况下,会增加算法的空间复杂度,这一点是需要权衡的。
另外,请再看一下这里的两个递归算法,我一直以为后者的长处是简洁,没有想到前者还必须付出临时变量的代价,到底有没有可能去掉这个临时变量呢(前提是依旧使用递归算法)?
ajoo 2002-08-21
  • 打赏
  • 举报
回复
to anrxhzh:

for searching a bst (I think that's more reasonable if we use tree):
Tree* find(Tree* t, int data){
if(t->data==data)return t;
else if(t->data > data)return find(t->getLeft(), data);
else return find(t->getRight(), data);
}
and that is tail recursion.

for searching a non-bst, a typical solution(not too efficient, but clear):
the getLeft(), getRight() may throw exception.

Tree* find(Tree* t, T data){
//throw exception if not found
if(t->data == data)return t;
try{
return find(t->getLeft(), data);
}
catch(char* msg){
return find(t->getRight(), data);
}
}


a C version would be:
Tree* find(Tree* t, int data){
Tree* ret;
if(t==NULL)return NULL;
if(t->data==data)return t;
ret = find(t->left, data);
if(ret != NULL )return ret;
else return find(t->right, data);
}

a finer solution can use continuation to get tail recursion:
Tree* find(Tree* t, int data, List* cont){
if(t==NULL){
if(cont == NULL) return NULL;
else{
Tree* nextTree = cont->tree;
List* nextCont = cont->next;
free(cont);
return find(nextTree, data, nextCont)
}
}
else{
if(t->data==data){
freeList(cont);
return t;
}
else{
return find(t->left, data, consList(cont, t->right));
}
}
}

the two helper functions, consList and freeList would be straight forward.
I did not test it, it may still have bugs in it. But you can see the logic here.
sclzmbie 2002-08-21
  • 打赏
  • 举报
回复
我同意xiaonian_3654(小周) ( )的见解,不过他实现上有错误。除了用汇编以外,没有比return更有效的方法了。

xiaonian_3654(小周) ( )的帖子我COPY 一份:
----------------------》》》
回复人: xiaonian_3654(小周) ( ) 信誉:100 2002-08-15 14:45:00 得分:0
goto 只能在函数内部跳来跳去,
你这样从递归函数直接跳到main();
抛开语法不说,你想一想,你的堆栈里是什么东西???
更何况语法不允许,(如果允许你还得平衡堆栈,你有那把握吗?)
我上面那么说是因为我试了一下,
下面是我个人看法,可能有一点偏差(错误):
内嵌汇编实现:
你得知道返回的地址,保留在一个全局变量里:
jmp 到即可
还可以生成汇编代码,这下你回做了吧
jmp 标号!

《《《-------------------------
因为递归后堆栈中都是函数的返回地址和参数,所以如果能精确定位到一开始调用递归函数的栈的位置上,可以用汇编的语句直接移动堆栈指针 SP 恢复堆栈,再移动指令指针 IP 到调用递归函数之后的那条语句上。保存的值可以存在EAX等寄存器中。

当然这是比较危险的行为,因为标志位等等都没有恢复。而且这种条件只能用在前面几位高手所说的“尾递归”的情况。

事实上,尾递归可以很容易的转换成循环。递归程序和循环程序是可以完全相互转换的。前面高手的程序我没有看,如果说了重复的话,呵呵,以前面几位的为准~~

加载更多回复(70)

69,364

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧