本节主要说了递归的设计和算法实现,以及递归的基本例程斐波拉契数列、strlen的递归解法、汉诺塔和全排列递归算法。

一、递归的设计和实现

1.递归从实质上是一种数学的解决问题的思维,是一种分而治之的思想。

这个是常见的一种数学算法,其实它就是递归的本质。我们要求的是所有数的乘积,那么我们就先求出两个数的乘积,然后再根据这两个数的乘积去求第三个数的乘积,这样每一次我们实际上都是进行的两个数的相乘,也就是我们把一个很多个数的相乘转换为了两个数的相乘。

2.通过上面的例子可以发现,递归就是将大型复杂问题转化为与原问题相同,但是规模变小的问题进行处理。

4.同时我们可以发现a1 这个时候n==1,是一个特殊的条件。这就是递归的边界条件,最后的最后我们都会执行到递归的边界条件,然后再从边界条件返回。等到都返回结束后我们就真正实现了我们想要的结果。

5.如果递归没有边界条件,那么我们的递归将永远无法跳出,也就是这个问题递归是无法解决的。

6.在解决递归问题的时候首先要建立递归模型,这是解决递归类问题的第一步。但是说来容易,其实这是一个痛苦的过程,说白了,算法不是一般人能搞的。

二、斐波拉切数列的递归实现

1.斐波拉切数列实际上就是一个递归的典型表现,它的具体要求如下:

通过上图我们可以知道,斐波拉契数列的要求就是求相邻两个的数和然后赋给第三个数。这样我们可以先求前两个数的和,然后再求第二个与第三个数的和,一直求到最后,然后再返回。

2.假定我们要求的数列的元素个数为10

那么具体程序如下所示:

#include <stdio.h>

int fibonacci(int n)
{
if( n > 1 )
{
return fibonacci(n-1) + fibonacci(n-2);
}
else if( n == 1 )
{
return 1;
}
else if( n == 0 )
{
return 0;
}
} int main()
{
int i = 0; for(i=1; i<=10; i++)
{
printf("fibonacci(%d) = %d\n", i, fibonacci(i));
} return 0;
}

通过上面的程序可以看出:我们首先通过主函数调用fibonacci函数,然后通过for循环依次向里面传递值,第一次传递的值为1,返回的值为1,所以打印1.第二次传递的值为2,符合if(n>1)的条件,执行语句

fibonacci(n-1) + fibonacci(n-2);

这个时候产生第一轮递归,也就是先执行函数fibonacci(1)执行以后的返回结果是1,再执行fibonacci(0),执行以后的返回结果是0,所以这一轮的返回结果是是1.

继续调用fibonacci函数,传递的参数是3,然后依次向后执行,每一次的递归深度都在加深。

三、strlen函数使用递归方式实现

1.我们都知道strlen函数的使用方法,它是通过传递进来的字符串来判断字符串的大小,但遇见"\0"的时候返回字符的个数,"\0"不包括在内。
2.假定我们要求一个"12345"的字符串的长度,具体例程如下:

#include <stdio.h>

int strlen(const char* s)
{
if( s == NULL )
{
return -1;
}
else if( *s == '\0' )
{
return 0;
}
else
{
return strlen(s+1) + 1;
}
} int main()
{
printf("strlen(\"12345\") = %d\n", strlen("12345"));
printf("strlen(NULL) = %d\n", strlen(NULL));
printf("strlen(\"\") = %d\n", strlen("")); return 0;
}

程序分析:我们在主函数中调用strlen函数,在我们第一次进入strlen函数的时候,程序执行判定,都不满足前两个判定,程序继续向下执行,再次调用strlen函数,然后再进行判定,仍然不满足判定条件,一直执行到s指针指向'\0',这个时候各个调用函数开始返回,最外层的返回0,0+1,第二层返回1,1+1,第三层返回1,1+1 。直至所有函数全部返回。程序打印结果如下所示:

整个程序我们是把一个字符串的长度求解过程转变成了对每一个字符长度的求解,然后再进行相加,边界条件就是'\0'。

四、汉诺塔递归算法的实现

1.汉诺塔的要求我就不详细说了,汉诺塔的问题我想了足足一天,其实最后也是单步调试加上草稿纸才把它搞定,这里拿三个盘子作为分析。

2.汉诺塔的递归思想:第一,把a上的n-1个盘通过c移动到b。第二,把a上的最下面的盘移到c。第三,因为n-1个盘全在b上了,所以把b当做a重复以上步骤就好了。

3.汉诺塔的具体代码实现:

#include <stdio.h>

void hanoi(int n, char a, char b, char c)
{
if( n > 0 )
{
if( n == 1 )
{
printf("%c -> %c\n", a, c);
}
else
{
hanoi(n-1, a, c, b); printf("%c -> %c\n", a, c); hanoi(n-1, b, a, c);
}
}
} int main()
{
hanoi(3, 'a', 'b', 'c');
getchar();
return 0;
}

程序打印结果如下:

五、汉诺塔递归调用分析

因为为了调试方便观看值,所以我把a,b,c字符重新定义成了整型变量,具体程序如下:

#include <stdio.h>

void hanoi(int n, int a, int b, int c)
{
if( n > 0 )
{
if( n == 1 )
{
printf("%d -> %d\n", a, c);
}
else
{
hanoi(n-1, a, c, b); printf("%d -> %d\n", a, c); hanoi(n-1, b, a, c);
}
}
} int main()
{
hanoi(3, 5, 6, 7);
return 0;
}

1.从主函数中调用hannoi函数,传递的参数是:n=3,a(1)=5,b(1)=6,c(1)=7
2.第1次进入hannoi函数,执行if(n==1)判定,不符合条件,调用hanoi(n-1,a,c,b)函数
3.向hannoi传递的参数是:n=2,a(2)=a(1),b(2)=c(1),c(2)=b(1)
4.第2次进入hannoi函数,执行if(n==1),不符合条件,调用hanoi(n-1,a,c,b)函数
5.向hannoi传递的参数:n=1,a(3)=a(2),b(3)=c(2),c(3)=b(2)
6.第3次进入hannoi函数,执行if(n==1)判定,符合条件,执行打印函数printf("%d->%d\n",a,c)这个时候打印函数里面的a,c是第3次调用hannoi函数传递进来的参数,也就是a3),c(3),追到原始值也就是a(1),c(1)。打印结果是:5->7
7.n=3的hannoi函数调用结束,子函数第1次执行结束。返回到n=2的hannoi函数调用位置,程序继续向下执行
8.调用打印函数:printf("%d->%d\n",a,c),这个时候打印函数的参数是n=2的时候hannoi函数的参数,也就是
a(2),c(2),追到原始值a(1),b(1)。打印结果是:5->6
9.执行完打印函数以后程序继续向下执行,调用hanoi(n-1,b,a,c)函数
10.这次调用hanoi(n-1,b,a,c)是从n=2的hannoi函数开始的,所以向hannoi函数传递的参数是:n=1,a(4)=b(2),b(4)=a(2),c(4)=c(2)
11.第4次进入hannoi函数,指定if(n==1)判定,符合条件,执行打印函数printf("%d->%d\n",a,c)这个时候打印函数里的a,c是a(4),c(4),追到原始值c(1),b(1),打印结果是:7->6.
12.调用hanoi(n-1,b,a,c)函数结束,程序返回到调用hanoi(n-1,b,a,c)函数的位置,接下来程序没有语句,子函数再次结束,程序返回到n=3调用hannoi函数的位置,执行印函printf("%d->%d\n",a,c),这个时候打印函数里的参数a,c是n=3的时候的参数,也就是a(1),c(1),追到原始值。打印结果是:5->7。
13.程序继续向下执行,调用hanoi(n-1,b,a,c)函数
14.这个时候第一轮递归已经结束,向hannoi传递的参数是:n=2,a(5)=b(1),b(5)=a(1),c(5)=c(1)
15.第5次进入hannoi函数,执行if(n==1)判定,不符合条件,调用hannoi(n-1,a,c,b)函数
16.向hannoi传递的参数是:n=1,a(6)=a(5),b(6)=c(5),c(6)=b(5)
17.第6次进入hannoi函数,执行if(n==1)判定,符合条件,执行打印函数printf("%d->%d\n",a,c)这个打印函数里面的参数a,c是第6次调用hannoi函数的参数,也就是a(6),c(6),即b(1),a(1),追到原始值为6,5,打印结果是6->5
18.这个时候第6次进入hannoi函数执行完毕,程序返回到第六次调用hannoi函数的位置,继续向下执行。
19.调用打印函数printf("%d->%d\n",a,c),这个时候打印函数的参数a,c是n=2的时候第5次调用hannoi函数传递的参数,也就是a(5),c(5),追到原始值是b(1),c(1),即6,7.打印结果是6->7
20.程序继续向下执行,调用hanoi(n-1,b,a,c)函数,这个时候程序是从n=2的hannoi函数位置继续向下执行的,参数是:n=1,a(7)=b(5),b(7)=a(5),c(7)=c(5)
21.第七次进入hannoi函数,进行if(n==1)判定,符合条件,执行打印函数printf("%d->%d\n",a,c),这个时候打印函数的参数是第七次调用hannoi函数的参数,也就是a(7),c(7)即b(5),c(5),追到原始值5,7打印结果是:5->7
22.程序最后再一次返回两次调用hanoi(n-1,b,a,c)函数的位置,但是每一次返回都没有其他动作,直至程序结束。

程序分析的结果十分复杂和繁琐,而且这仅仅是三层盘子。同时由于自己的画图水平不好,所以也没有画流程图,同时网上好多大牛说是可以用树的想法去想汉诺塔问题,但是我没学习到树,所以只能用上面那种最笨额方法了。

六、全排列的递归调用

1.问题的提出:假定有三个元素a,b,c,那么这三个元素的全排列有六种方式:abc,acb,bac,bca,cba,cab。那么两个元素的全排列的是ab,ba,一个元素的全排列就是元素本身,所以一个元素的全排列就是递归的边界条件。

2.我们这里以三个元素的全排列,程序例程如下:

#include <stdio.h>

void permutation(char s[], int b, int e)
{
if( (0 <= b) && (b <= e) )
{
if( b == e )
{
printf("%s\n", s);
}
else
{
int i = 0; for(i=b; i<=e; i++)
{
char c = s[b];
s[b] = s[i];
s[i] = c; permutation(s, b+1, e); c = s[b];
s[b] = s[i];
s[i] = c;
}
}
}
} int main()
{
char s[] = "abcd"; permutation(s, 0, 3); return 0;
}

程序的递归算法框图如下所示:

由于我们传进子函数的四个字符的字符数组,所以这里我们直接执行else部分的函数,首先执行for循环,for循环从i=b开始,这样我们第一轮for循环先做的交换代码如下:

char c = s[b];
s[b] = s[i];
s[i] = c;

其实这个时候我们没有完成任何交换,然后继续调用permutation(s, b+1, e)函数,再次进入for循环,这个时候for循环是从i=b+1开始的,这个时候执行交换部分的代码还会完成另一个元素的交换。实质上这个交换部分的代码完成的就是将每一个元素作为第一个位置的作用,然后再进行其他操作。
for循环里的第二部分主要代码如下:

c = s[b];
s[b] = s[i];
s[i] = c;

这一部分代码的主要作用是在每一个permutation(s, b+1, e)进行弹出操作的时候开始执行,这样我们就可以对后面的数进行全排列了。我们也就完成图示的全排列操作。具体的递归过程可以例程单步调试来进行理解。

C语言数据结构----递归的应用(斐波拉契数列、汉诺塔、strlen的递归算法)的更多相关文章

  1. 【python】递归(阶乘、斐波纳契、汉诺塔)

  2. 算法 递归 迭代 动态规划 斐波那契数列 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  3. Python进阶(七)----带参数的装饰器,多个装饰器修饰同一个函数和递归简单案例(斐波那契数列)

    Python进阶(七)----带参数的装饰器,多个装饰器修饰同一个函数和递归简单案例(斐波那契数列) 一丶带参数的装饰器 def wrapper_out(pt): def wrapper(func): ...

  4. 斐波拉契数列加强版——时间复杂度O(1),空间复杂度O(1)

    对于斐波拉契经典问题,我们都非常熟悉,通过递推公式F(n) = F(n - ) + F(n - ),我们可以在线性时间内求出第n项F(n),现在考虑斐波拉契的加强版,我们要求的项数n的范围为int范围 ...

  5. 剑指offer-第二章算法之斐波拉契数列(青蛙跳台阶)

    递归与循环 递归:在一个函数的内部调用这个函数. 本质:把一个问题分解为两个,或者多个小问题(多个小问题相互重叠的部分,会存在重复的计算) 优点:简洁,易于实现. 缺点:时间和空间消耗严重,如果递归调 ...

  6. 剑指offer-面试题9.斐波拉契数列

    题目一:写一个函数,输入n,求斐波拉契数列的第n项. 斐波拉契数列的定义如下: { n=; f(n)={ n=; { f(n-)+f(n-) n>; 斐波拉契问题很明显我们会想到用递归来解决: ...

  7. [NEUQ-OJ] 1012 SZ斐波拉契数列

    一道水题,让我看清基础我的基础是多么薄弱. 递归,数组清零,数组名/变量名重复层出不穷...路漫漫啊.......... http://ncc.neuq.edu.cn/oj/problem.php?i ...

  8. 浅谈C#中的斐波拉契数列

    突然对那些有趣的数学类知识感兴趣了,然后就简单研究了一下斐波拉契数列,看看它的有趣之处! 斐波拉契数列(Fibonacci Sequence),又称黄金分割数列,该数列由意大利的数学家列奥纳多·斐波那 ...

  9. Go斐波拉契数列(Fibonacci)(多种写法)

    1 前言 斐波拉契数列有递归写法和尾递归和迭代写法. 2 代码 //recursion func fib(n int) int{ if n < 2{ return n }else{ return ...

随机推荐

  1. Smarty - 安装+入门小例子

    环境: smarty 1.在http://www.smarty.net/download下载最新smarty包,window选择zips,linux下选择tar.gz.以windows为例,下载后解压 ...

  2. 关于C++的子类指针指向父类

    基类指针引用派生类对象 用基类指针引用一个派生类对象,由于派生类对象也是基类的对象,所以这种引用是安全的; 但是只能引用基类成员. 若试图通过基类指针引用那些只在派生类中才有的成员,编译器会报告语法错 ...

  3. jz2440烧写开发板uboot,内核和文件系统等的相关命令

    下载文件{ftpget -u 1 -p 1 192.168.2.110 a.out a.outnfs 30000000(destination) 192.168.2.109:/home/fs/work ...

  4. 第三届蓝桥杯 c/c++真题

    第三届蓝桥杯真题 c/c++ 以下题目我自己也并不是所有的题目都是一次性就能做对或是有结题思路的.有些题目也是经过查证网上相关的资料或是参考了别人的代码和解题思路才做出来的.总的来看,这份题目考了很多 ...

  5. 六、Nginx 防盗链

    盗链是指一个网站的资源(图片或附件)未经允许在其它网站提供浏览和下载.尤其热门资源的盗链,对网站带宽的消耗非常大,本文通过nginx的配置指令location来实现简单的图片和其它类型文件的防盗链. ...

  6. HDOJ 1217 Floyed Template

    解题思路:1.map简单应用2.Floyd算法的变形,之后判断dis[i][i],如果大于1,则存在利润! #include <iostream> #include <stdio.h ...

  7. WPF4多点触摸事件

    原文 WPF4多点触摸事件 UIElement在WPF4下添加了很多支持多点触摸的事件,通过它们可以在硬件支持的情况下处理多点触摸,以下通过代码来说明通过处理这些事件,我们可以做些什么: 一.触摸相关 ...

  8. char s[]字串和char *s字串有什麼区别?

    C語言有兩種字串宣告方式char s[]和char *s,兩者有什麼差異呢? Introduction char s[] = "Hello World"; (只是用字符串常量初始化 ...

  9. 重探 DFT

    感觉上次学习DFT简直是乱来了.不知道误导了多少人,这里深感抱歉. 这次我再看了看<算法导论>,觉得收获很大,终于粗略的知道DFT的原理了! 如何将两个多项式相乘 对于一个n次多项式,\( ...

  10. Kivy: Crossplatform Framework for NUI

    Kivy: Crossplatform Framework for NUI ivy - Open source Python library for rapid development of appl ...