面试题目之逆向输出链表

逆向输出一个链表,不使用循环。

解决如下 

1 void reverseprint(listnode *head)
2 {
3     if (head->next!=NULL)
4     {
5         reverseprint(head->next);
6     }
7     cout<<head->val<<endl;
8 }
9 

这个题目用常规方法来做,比较繁琐,因为链表是没有前向指针的,我们即使遍历到链表的尾部,也无法直接得到其前一个节点。所以必须要使用一个结构将前面的节点保存起来,然后输出。所以这道题目使用递归还是很巧妙的。

递归要注意的就是这些语句前后放置的顺序,否则就得不到想要的结果了。汗,添上这句话是因为我就写错了,后来才改正好。

其实递归就是利用C的函数调用规则,C在函数调用的时候,会将参数放入栈中,每调用一次函数,就会在栈中深入一层,当这个函数调用结束后,就会发生退栈操作,再执行下面的语句。递归就是函数自身调用自身,当然,在调用自身的时候,其参数和前提条件会发生相应的变化,从而使得不会产生无穷递归的情况。

如果你在编写递归程序时没有小心处理,填错参数或者考虑没有详细的话,那有可能整个程序会异常终止,有时会报出内存访问违例的错误,但是有时什么都不会报,直接什么都不输出,此时你进入debug,可以看到一般都是发生了stack overflow的错误。因为C的函数栈空间是有限的,当递归的次数过多时,栈中容纳不下这么多的内容,就会产生栈溢出错误。C中的栈的大小可以由程序员在编程的时候在编译器中指定,默认有一个值,似乎是1M。要注意的是,这里的栈溢出和一般漏洞攻击中提到的栈溢出不能算是一个概念。那里的栈溢出是利用一般是早期的字符串操作函数对字符参数不做安全性检查,而构造一些特殊的字符,用来将下次函数调用的保存地址修改至自己函数地址,而达到执行自己代码的目的。这里就不详细展开,只是提到一下。

对于递归,主要有两个问题,一个是递归算法一般来说,会出现重复性运算比较多,从而在运算效率上不是很高。(当然,这个也要看具体情况,可以看斐波那契数列的运算,用递归来计算斐波那契数列就会比较慢),同时,由于递归调用会用到函数调用,所以会有函数调用的开销,函数调用会涉及到EBP,ESP等的入栈出栈操作,而自己直接使用栈则不会有这些开销,但是自己申请的栈如果在堆区的话,堆区的访问操作似乎比较慢一些。一般还是在栈区来申请一个数组来做,这样基本就是指针的移动操作,比较快;第二个就是其对栈的占用问题,这个在PC机上体现不是很明显,但是在嵌入式系统中,这个问题就比较严重,因为嵌入式系统本身的内存就不是很多,而分给栈的部分就更加的少,所以一般你看嵌入式开发公司的编程规范中,一般都会提到不要在其中使用递归。当然,节省内存是嵌入式系统一向的原则了。这里就还有一点,因为你不用递归来做,一般还是在栈上分配一块内存来操作,这样的话,如果内存分配过多,就一下子栈溢出了,所以一般内存分配比较多的,还是在堆上进行分配。

以上面这个逆向输出链表为例,当遍历到第一个节点时,如果没有到链表尾部,则进入递归的函数调用,参数为下一个节点,此时就是递归的开始,此时函数调用不会结束,而是判断是否到了链表尾部,没有到则再次进入函数,如此反复,直到最后的一个节点,到达这个节点,就判断到是到了尾部,此时不会进入判断,而是执行cout输出,此时就达到我们的逆向输出目的,第一个输出的是最后的一个节点;而其他节点呢,不是如我们所想的值啊什么的都存在栈中,而是前一个函数调用没有结束,是前面的函数调用存在在栈中,当尾部节点输出完毕后,该函数调用即会退出。而函数的退出点在reverseprint函数中第5行的后面,所以接着就会执行cout输出,而输出时的head值为前一个节点的值,所以输出前一个节点的值,一直到最后第一个节点的值。

cout语句输出第一个节点值之后,在栈中已经没有对reverseprint函数的调用了,此时整个递归调用结束。

【变体】

这个问题的扩展变体还有

1. 逆向输出字符串

2. 定义一个函数求字符串的长度,要求该函数体内不能声明任何变量 

这里第一条根据上面一样的道理,很快写出来,但是要注意字符串,也就是char指针的问题

我第一次写出的错误代码如下

1 void reverseprintstr(char str[])
2 {
3     if (str!='\0')
4     {
5         reverseprintstr(str+1);
6     }
7     printf("%c",str);
8 }

修改之后,将最后的’\0’也一起输出了

 

1 void reverseprintstr(char str[])
2 {
3     if (*str!='\0')
4     {
5         reverseprintstr(str+1);
6     }
7     printf("%c",*str);
8 }
9 

最后的正确输出代码

 

 1 void reverseprintstr(char str[])
 2 {
 3     if (*str!='\0')
 4     {
 5         reverseprintstr(str+1);
 6         printf("%c",*str);
 7     }
 8 }
 9 
10 int main()
11 {
12     char str[]="hello world.";
13     reverseprintstr(str);
14 }
15 

 


题2求字符串长度

 1 int mystrlen(char str[])
 2 {
 3     if (*str=='\0')
 4     {
 5         return 0;
 6     }
 7     return mystrlen(str+1)+1;
 8 }
 9 
10 int main()
11 {
12     char str[]="hello";
13     cout<<mystrlen(str)<<endl;
14 }

参考:http://bbs.tech.ccidnet.com/read.php?tid=681721

 

简单的递归求解还有

用递归输出乘法表,也就一起列在这里了

 

 1 void output(int val1,int val2)
 2 {   
 3     cout<<val1<<'*'<<val2<<'='<<val1*val2<<' ';   
 4 }   
 5 
 6 void fun(int val)
 7 {   
 8     if(val==1)   
 9         output(val,val);   
10     else   
11     {   
12         fun(val-1);   
13         for(int i=1;i<=val;++i)   
14             output(i,val);   
15     }   
16     cout<<endl;   
17 }   
18 
19 void main()   
20 {   
21     int i;   
22     cin>>i;   
23     fun(i);   
24 }
25 

 

 参考:http://topic.csdn.net/t/20020226/20/543919.html

 

上面的递归还是比较简单的,如果在递归中再加上循环,在循环内进行递归,同时还有一些回溯的话,就比较复杂了,一般面试中比较复杂一些的递归题大概就是这个难度。

下面这段时间主要就是研究递归,将一些递归的题目列出来,并试着解决。大家有好的题目,也可以留言给我,多谢多谢。

posted on 2009-11-01 13:11  cnyao  阅读(2866)  评论(40编辑  收藏  举报