一步一步写算法(之递归和堆栈)

原文:一步一步写算法(之递归和堆栈)

【 声明:版权所有,欢迎转载,请勿用于商业用途。  联系信箱:feixiaoxing @163.com】

    看过我前面博客的朋友都清楚,函数调用主要依靠ebp和esp的堆栈互动来实现的。那么递归呢,最主要的特色就是函数自己调用自己。如果一个函数调用的是自己本身,那么这个函数就是递归函数。

    我们可以看一下普通函数的调用怎么样的。试想如果函数A调用了函数B,函数B又调用了函数C,那么在堆栈中的数据是怎么保存的呢?

函数A    ^
函数B    |    (地址递减)
函数C    |

    如果是递归函数呢,举一个简单的递归函数为例:

int iterate(int value)
{
	if(value == 1)
		return 1;
	return value + iterate(value -1);
}

    下面我们使用一个函数进行调用,看看会发生什么情况?

void process()
{
	int value = iterate(6);
}

    看看此时内存堆栈是什么样的?

iterate(int 1) line 96
iterate(int 2) line 97 + 12 bytes
iterate(int 3) line 97 + 12 bytes
iterate(int 4) line 97 + 12 bytes
iterate(int 5) line 97 + 12 bytes
iterate(int 6) line 97 + 12 bytes
process() line 102 + 7 bytes
main() line 108
mainCRTStartup() line 206 + 25 bytes
KERNEL32! 7c817067()

    大家也看到了上面的代码,递归函数和普通的函数也没有什么差别。除了自己调用本身之外,他就是一个普通的函数。那么这个函数递归到什么时候返回呢?这就是递归函数的关键了。我们看到iterate函数到1就停止了,所以上面的堆栈在(value == 1)即return。所以一个递归函数最关键的部分就是两点:(1)递归策略;(2)函数出口。

    看到这里,大家可能感到递归函数不过如此,事实上也是这样。但是,还有一点大家需要牢记在心,递归的深度是我们必须考虑的一个问题。只有递归深度在一个可控的范围内,那么整个递归过程都是可控的。那什么时候不可控呢?那就是递归深度超过了一定的数字?这个数字和具体的线程堆栈长度有关?等到堆栈溢出了,那么获得的数据已经失去了真实性,所以也就没有意义了。

    我们把上面的问题推广一下,如何用自己定义的堆栈模拟上面的递归调用呢?这样既能满足递归的属性,又能确保函数深度可控。

    大家可以先写一下自己的方案,下面只是我个人的一个思路。

int iterate(int value)
{
	int count = 0;
	int number  =0;

	push(value);
	while(-1 != (number = pop()))
	{
		if(1 != number)
			push(number -1);
		count += number;
	}

	return count;
}

【预告: 下面一篇博客介绍算法和内存】

时间: 2024-07-30 11:35:56

一步一步写算法(之递归和堆栈)的相关文章

一步一步写算法(之线性堆栈)

原文:一步一步写算法(之线性堆栈) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]    前面我们讲到了队列,今天我们接着讨论另外一种数据结构:堆栈.堆栈几乎是程序设计的命脉,没有堆栈就没有函数调用,当然也就没有软件设计.那么堆栈有什么特殊的属性呢?其实,堆栈的属性主要表现在下面两个方面:     (1)堆栈的数据是先入后出     (2)堆栈的长度取决于栈顶的高度     那么,作为连续内存类型的堆栈应该怎么设计呢?大家可以自己先试一下

一步一步写算法(之函数堆栈显示)

原文:一步一步写算法(之函数堆栈显示) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com ]     在继续图的讨论之前,我们今天开个小差,讨论一下函数堆栈的基本原理.有过编程经验的朋友都知道,堆栈调试是我们在程序开发中经常应用的一个功能.那么大家有没有想过,函数堆栈是怎么开始的啊?其实我们可以自己写一个函数堆栈输出函数分析一下.     因为一般来说,函数的压栈过程是这样的:               |    参数三  |       

一步一步写算法(之非递归排序)

原文:一步一步写算法(之非递归排序) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]        在上面一篇博客当中,我们发现普通查找和排序查找的性能差别很大.作为一个100万的数据,如果使用普通的查找方法,那么每一个数据查找平均下来就要几十万次,那么二分法的查找呢,20多次就可以搞定.这中间的差别是非常明显的.既然排序有这么好的效果,那么这篇博客中,我们就对排序算做一个总结.     按照我个人的理解,排序可以分为两种:一种是非递归排

一步一步写算法(之循环和递归)

原文:一步一步写算法(之循环和递归) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     其实编程的朋友知道,不管学什么语言,循环和递归是两个必须学习的内容.当然,如果循环还好理解一点,那么递归却没有那么简单.我们曾经对递归讳莫如深,但是我想告诉大家的是,递归其实没有那么可怕.所谓的递归就是函数自己调用自己而已,循环本质上也是一种递归.     1)求和递归函数     我们可以举一个循环的例子,前面我们说过,如果编写一个1到n的求和

一步一步写算法(之二叉树深度遍历)

原文:一步一步写算法(之二叉树深度遍历) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     深度遍历是软件开发中经常遇到的遍历方法.常用的遍历方法主要有下面三种:(1)前序遍历:(2)中序遍历:(3)后序遍历.按照递归的方法,这三种遍历的方法其实都不困难,前序遍历就是根-左-右,中序遍历就是左-根-右,后续遍历就是左-右-根.代码实现起来也不复杂.     1)前序遍历 void preorder_traverse(TREE_NOD

一步一步写算法(之快速排序)

原文:一步一步写算法(之快速排序) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     快速排序是编程中经常使用到的一种排序方法.可是很多朋友对快速排序有畏难情绪,认为快速排序使用到了递归,是一种非常复杂的程序,其实未必如此.只要我们使用好了方法,就可以自己实现快速排序.     首先,我们复习一下,快速排序的基本步骤是什么:     1. 判断输入参数的合法性     2.把数组的第一个数据作为比较的原点,比该数据小的数据排列在左边

一步一步写算法(之寻路)

原文:一步一步写算法(之寻路) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     寻路是游戏设计中需要使用到一种功能,那么我们怎么样以一个点作为起始点,快速地寻找到目标点呢?其实寻路的方法不难.一种简单有效的方法就是回溯法.如果我们从一个点出发,那么这个点周围肯定有若干条路,只要有一条路存在,我们就一直走下去,直到发现没有路走为止:要是发现路走不下去了怎么办,那就只好回头了,我们只能从剩下的选项中继续选择一条路,继续尝试.如果很不幸

一步一步写算法(之 算法总结)

原文:一步一步写算法(之 算法总结) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]       自10月初编写算法系列的博客以来,陆陆续续以来写了几十篇.按照计划,还有三个部分的内容没有介绍,主要是(Dijkstra算法.二叉平衡树.红黑树).这部分会在后面的博客补充完整.这里主要是做一个总结,有兴趣的朋友可以好好看看,欢迎大家提出宝贵意见.       (1) 排序算法     快速排序           合并排序     堆排序

一步一步写算法(之排序二叉树)

原文:一步一步写算法(之排序二叉树) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     前面我们讲过双向链表的数据结构.每一个循环节点有两个指针,一个指向前面一个节点,一个指向后继节点,这样所有的节点像一颗颗珍珠一样被一根线穿在了一起.然而今天我们讨论的数据结构却有一点不同,它有三个节点.它是这样定义的: typedef struct _TREE_NODE { int data; struct _TREE_NODE* parent;