递归算法工作栈的变化详解

合集下载

栈与递归

栈与递归

栈与递归的关系一、引言栈是一种重要的线性结构。

从数据结构角度看,栈也是线性表,其特殊性在于栈的基本操作是线性表操作的子集,它们是操作受限的线性表。

栈是限定仅在表尾进行插入或删除操作的线性表。

栈还有一个重要应用是在程序设计语言中实现递归。

一个直接调用自己或通过一系列的调用语句间接地调用直接的函数,称做递归函数。

二、栈与递归的关系:递归在实现过程中是借助于栈来实现的。

高级语言的函数调用,每次调用,系统都要自动为该次调用分配一系列的栈空间用于存放此次调用的相关信息:返回地址,局部变量等。

这些信息被称为工作记录(或活动记录)。

而当函数调用完成时,就从栈空间内释放这些单元,但是,在该函数没有完成前,分配的这些单元将一直保存着不被释放。

递归函数的实现,也是通过栈来完成的。

在递归函数没有到达递归出口前,都要不停地执行递归体,每执行一次,就要在工作栈中分配一个工作记录的空间给该“层”调用存放相关数据,只有当到达递归出口时,即不再执行函数调用时,才从当前层返回,并释放栈中所占用的该“层”工作记录空间。

请大家注意,递归调用时,每次保存在栈中的是局部数据,即只在当前层有效的数据,到达下一层时上一层的数据对本层数据没有任何影响,一切从当前调用时传过来的实在参数重新开始。

由此可见,从严老师P版教材中,利用栈将递归向非递归转化时所采用的方法,实质是用人工写的语句完成了本该系统程序完成的功能,即:栈空间中工作记录的保存和释放。

可以参照以上的分析来理解递归函数的运行过程。

三、对栈与递归的分析1、栈的定义栈(Stack)是限制仅在表的一端进行插入和删除运算的线性表。

(1)通常称插入、删除的这一端为栈顶(Top),另一端称为栈底(Bottom)。

(2)当表中没有元素时称为空栈。

(3)栈为后进先出(Last In First Out)的线性表,简称为LIFO表。

栈的修改是按后进先出的原则进行。

每次删除(退栈)的总是当前栈中"最新"的元素,即最后插入(进栈)的元素,而最先插入的是被放在栈的底部,要到最后才能删除。

数据结构(C语言)第3章 栈和队列

数据结构(C语言)第3章 栈和队列

Data Structure
2013-8-6
Page 13
栈的顺序存储(顺序栈)
利用一组地址连续的存储单元依次存放自栈底到栈顶的数 据元素。 结构定义: #define STACK_INIT_SIZE 100; // 存储空间初始分配量 #define STACKINCREMENT 10; // 存储空间分配增量 typedef struct { SElemType *base; // 存储空间基址 SElemType *top; // 栈顶指针 int stacksize; // 当前已分配的存储空间,以元素位单位 } SqStack;
解决方案2:
顺序栈单向延伸——使用一个数组来存储两个栈
Data Structure 2013-8-6 Page 21
两栈共享空间 两栈共享空间:使用一个数组来存储两个栈,让一个 栈的栈底为该数组的始端,另一个栈的栈底为该数组 的末端,两个栈从各自的端点向中间延伸。
Data Structure
2013-8-6
链栈需要加头结点吗? 链栈不需要附设头结点。
Data Structure
2013-8-6
Page 27
栈的链接存储结构及实现
Data Structure
2013-8-6
Page 11
GetTop(S, &e) 初始条件:栈 S 已存在且非空。 操作结果:用 e 返回S的栈顶元素。 Push(&S, e) 初始条件:栈 S 已存在。 操作结果:插入元素 e 为新的栈顶元素。 Pop(&S, &e) 初始条件:栈 S 已存在且非空。 操作结果:删除 S 的栈顶元素,并用 e 返回其值。
Data Structure

程序设计员实操考核:深入理解递归算法

程序设计员实操考核:深入理解递归算法

程序设计员实操考核:深入理解递归算法一、引言递归算法是计算机科学中的重要概念,也是程序设计员实际工作中经常使用的算法之一。

通过递归算法,我们可以解决一系列与问题的分解和子问题求解有关的计算任务。

在程序设计员的实操考核中,深入理解递归算法是一项重要的能力要求。

本文将从概念、原理、应用和实操等多个方面对递归算法进行介绍和解析,以帮助程序设计员更好地掌握和运用递归算法。

二、概念与原理2.1 递归算法的定义递归算法是一种通过函数调用自身的方式来解决问题的方法。

在递归算法中,将一个大问题分解为一个或多个同类的子问题,逐步解决子问题,最终得到原问题的解。

2.2 递归算法的基本原理递归算法的基本原理包括以下几点:•基准情况:递归算法必须设定一个或多个基准情况,当满足基准情况时,递归停止,并返回结果。

•递归调用:递归算法通过调用自身来解决子问题。

在每次递归调用中,问题的规模都会减小,直到满足基准情况。

•合并子问题:递归算法在解决子问题之后,需要将子问题的解合并起来,得到原问题的解。

2.3 递归算法的特点递归算法有以下几个特点:•问题分解:递归算法将一个大问题分解为多个同类的子问题,将问题简化为更小的规模。

•自相似性:在递归算法中,子问题与原问题具有相同的性质,只是规模不同。

•代码简洁:递归算法通常代码较为简洁,能够更清晰地表达问题的结构。

•空间复杂度高:递归算法通常会占用较多的栈空间,可能会导致栈溢出。

三、递归算法的应用递归算法在实际工作中有广泛的应用,以下是几个常见的应用场景:3.1 数学运算递归算法可以用于解决数学中的一些复杂运算问题,比如计算阶乘、斐波那契数列等。

通过递归调用自身,可以简化问题的解决过程。

3.2 数据结构操作递归算法在处理树、图等数据结构时往往更加方便。

通过递归算法,可以对树进行遍历、搜索、插入、删除等操作。

递归算法还可以用于实现图的深度优先搜索等算法。

3.3 文件系统操作递归算法在处理文件系统中的文件和目录时也很常见。

递归算法详解完整版

递归算法详解完整版

递归算法详解完整版递归算法是一种重要的算法思想,在问题解决中起到了很大的作用。

它通过将一个大问题划分为相同或类似的小问题,并将小问题的解合并起来从而得到大问题的解。

下面我们将详细介绍递归算法的定义、基本原理以及其应用。

首先,我们来定义递归算法。

递归算法是一种通过调用自身解决问题的算法。

它通常包括两个部分:基础案例和递归步骤。

基础案例是指问题可以被直接解决的边界情况,而递归步骤是指将大问题划分为较小问题并通过递归调用自身解决。

递归算法的基本原理是"自顶向下"的思维方式。

即从大问题出发,不断将问题划分为较小的子问题,并解决子问题,直到达到基础案例。

然后将子问题的解合并起来,得到原始问题的解。

递归算法的最大特点是简洁而优雅。

通过将复杂问题分解为简单问题的解决方式,可以大大减少代码的复杂程度,提高程序的效率和可读性。

但是递归算法也有一些缺点,包括递归深度的限制和复杂度的不确定性。

过深的递归调用可能导致栈溢出,而不合理的递归步骤可能导致复杂度过高。

递归算法有许多应用场景,我们来介绍其中一些典型的应用。

1.阶乘问题:计算一个数的阶乘。

递归算法可以通过将问题划分为更小的子问题来解决。

例如,n的阶乘可以定义为n乘以(n-1)的阶乘。

当n 等于1时,我们可以直接返回1作为基础案例。

代码如下:```int factorial(int n)if (n == 1)return 1;}return n * factorial(n - 1);```2.斐波那契数列问题:求斐波那契数列中第n个数的值。

斐波那契数列的定义是前两个数为1,然后从第三个数开始,每个数都是前两个数的和。

递归算法可以通过将问题划分为两个子问题来解决。

当n等于1或2时,直接返回1作为基础案例。

代码如下:```int fibonacci(int n)if (n == 1 , n == 2)return 1;}return fibonacci(n - 1) + fibonacci(n - 2);```3.二叉树问题:对于给定的二叉树,递归算法可以通过递归调用左子树和右子树的解来解决。

python中的递归算法详解

python中的递归算法详解

python中的递归算法详解递归算法是一种在函数内部调用自身的方法。

在Python中,递归可以实现复杂的问题解决,它的实现遵循以下几个关键步骤。

第一,定义基本情况:递归算法在每一次调用中都需要判断是否达到了基本情况,即算法可以直接返回结果而不继续调用自身。

这个基本情况确保算法不会无限递归下去,而是能够找到问题的解。

第二,拆分问题:递归算法需要将大问题拆分成子问题,在每一次递归调用中解决子问题。

这样,较大的问题就会逐渐转化为较小的问题,直到最终达到基本情况。

第三,调用自身:递归算法通过调用自身来解决子问题。

在每次递归调用中,算法会传递不同的参数,以便在下一次调用中解决不同的子问题。

递归算法的关键在于它能够将复杂的问题转化为简单的子问题,并通过不断调用自身来解决这些子问题。

举个例子,考虑一个计算阶乘的递归算法:```pythondef factorial(n):# 基本情况if n == 0:return 1# 拆分问题subproblem = factorial(n - 1)# 调用自身result = n * subproblemreturn result```在这个例子中,递归算法使用了基本情况 `n == 0` 来停止递归。

当输入参数为0时,算法直接返回1,否则继续调用自身,解决 `n - 1` 的阶乘问题。

最终,算法将计算 `n` 的阶乘并返回结果。

需要注意的是,递归算法在处理大规模问题时可能会遇到效率问题。

递归算法会调用自身多次,每次调用都需要保存状态并分配栈空间,所以在某些情况下,循环迭代可能会更加高效。

因此,在使用递归算法时,需要谨慎考虑问题的规模和算法实现的复杂度,以充分利用递归的优势。

离散数学中递归算法的工作原理解析

离散数学中递归算法的工作原理解析

离散数学中递归算法的工作原理解析离散数学是一门研究离散对象和离散结构的数学学科,其在计算机科学中有着广泛的应用。

递归算法是离散数学中的一个重要概念,本文将对递归算法的工作原理进行解析。

1. 递归算法的定义递归算法是一种通过反复调用自身来解决问题的算法。

它通常包含了一个递归出口(基本情况)和一个递归体(递归情况)。

当问题达到递归出口时,算法停止递归并返回结果。

否则,算法继续递归调用自身,将问题分解为规模更小的子问题,并在子问题上进行递归求解。

2. 递归算法的优点与注意事项递归算法具有以下优点:1) 逻辑清晰简洁:递归算法能够使用简洁的方式描述问题的解决过程。

2) 结构灵活:递归算法能够解决各种类型的问题,适用范围广泛。

然而,递归算法也需要注意以下事项:1) 递归深度:递归算法的性能与问题的规模成反比。

递归深度过大可能导致栈溢出或性能下降。

2) 重复计算:递归算法中可能存在重复计算,增加了计算量。

可以使用记忆化技术(如动态规划)来优化递归算法。

3. 递归算法的应用场景递归算法在计算机科学中有广泛的应用,包括但不限于以下领域:1) 数据结构:递归算法常用于处理树、图、链表等数据结构,如树的遍历、图的深度优先搜索等。

2) 排列组合:递归算法可以用于生成排列组合,如全排列、组合数等。

3) 分治算法:分治算法通常使用递归来将问题分解为更小的子问题,并分别求解。

4. 递归算法的实现步骤实现一个递归算法通常包括以下步骤:1) 定义递归出口:确定递归算法何时停止递归,返回结果。

2) 确定递归体:根据问题的特点,将问题分解为规模更小的子问题,并调用自身来解决子问题。

3) 设计递归调用:根据子问题的规模和性质,设计递归调用的方式。

4) 处理子问题的结果:将子问题的结果合并得到原问题的结果。

5. 递归算法的示例:阶乘计算下面通过计算阶乘的例子来具体说明递归算法的工作原理:```python# 递归算法计算阶乘def factorial(n):if n == 0:return 1else:return n * factorial(n-1)```上述代码中,factorial函数通过递归来计算阶乘。

栈与递归的关系

栈与递归的关系

栈与递归的关系姓名:郭小兵学号:1007010210专业:信息与计算科学院系:理学院指导老师:彭长根2012年10月17日栈与递归的关系郭小兵摘要递归是计算机科学中一个极为重要的概念,许多计算机高级语言都具有递归的功能,对于初学计算机者来讲,递归是一个简单易懂的概念,但真正深刻理解递归,正确自如的运用递归编写程序却非易事,本文通过一些实例来阐述递归在计算机内的实现及递归到非递归的转换,也许使读者能加深对递归的理解。

关键词栈递归非递归引言递归是一种程序设计的方式和思想。

计算机在执行递归程序时,是通过栈的调用来实现的。

栈,从抽象层面上看,是一种线性的数据结构,这中结构的特点是“先进后出”,即假设有a,b,c三个元素,依次放某个栈式存储空间中,要从该空间中拿到这些元素,那么只能以c、b、a的顺序得到。

递归程序是将复杂问题分解为一系列简单的问题,从要解的问题起,逐步分解,并将每步分解得到的问题放入“栈”中,这样栈顶是最后分解得到的最简单的问题,解决了这个问题后,次简单的问题可以得到答案,以此类推。

分解问题是进栈(或者说压栈)的过程,解决问题是一个出栈的过程。

科学家对栈与递归都做了很多深入的研究,研究表明“递归算法和栈都有后进先出这个性质,基本上能用递归完成的算法都可以用栈完成,都是运用后进先出这个性质的”这个性质可用于进制的转换。

与汇编程序设计中主程序和子程序之间的链接及信息交换相类似,在高级语言编制的程序中,调用函数和被调用函数之间的链接及信息交换需过栈来进行。

递归是计算科学中一个极为重要的概念。

许多计算机高级语言都具有递归的功能,本文将通过一些是例来阐述递归在计算机内的实现及递归到非递归的转换,也许能加深对递归的理解。

递归是某一事物直接或间接地由自己完成。

一个函数直接或间接地调用本身,便构成了函数的递归调用,前者称之为直接递归调用,后者为间接递归调用。

递归会使某些看起来不容易解决的问题变得容易解决。

递归与栈的转化(迷宫+n皇后+汉诺塔)

递归与栈的转化(迷宫+n皇后+汉诺塔)

递归与栈的转化(迷宫+n皇后+汉诺塔)参考:《数据结构教程》第五版李春葆⼀,递归到⾮递归的转换1,递归的分类 递归可分为尾递归和⾮尾递归2,尾递归 ① 如果⼀个递归过程会递归函数中的递归调⽤语句是最后⼀条语句,则称这种递归调⽤为尾递归 ② ⼀般情况下,尾递归可以通过循环或者迭代⽅式转化为等价的⾮递归算法。

3,⾮尾递归 ① 对于⾮尾递归算法,在理解递归调⽤实现过程的基础上可以⽤栈来模拟递归执⾏过程。

4,总结 尾递归需要只⽤到递归函数的搜索功能,所以可以⽤循环替换; ⽽⾮尾递归除了循环部分,还⽤到了递归函数的回溯功能,所以只能⽤栈模拟。

⼆,⼀个栈帧只有⼀个递归函数的递归转化1,迷宫寻路递归:#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>#define N 1111int n, m, cnt;int map[N][N];int dx[4] = { -1,0,1,0 }, dy[4] = { 0,1,0,-1 };struct Node{int x, y;}way[N];void show(){printf("(0,0)");for (int i = 0; i < cnt; i++)printf("->(%d,%d)", way[i].x, way[i].y);puts("");}void DFS(int a, int b){if (a == n - 1 && b == m - 1){show();return;}for (int i = 0; i < 4; i++){if (a + dx[i] < 0 || b + dy[i] < 0 || a + dx[i] >= n || b + dy[i] >= m)continue;if (map[a + dx[i]][b + dy[i]] == 0){map[a][b] = 1;way[cnt].x = a + dx[i], way[cnt].y = b + dy[i];cnt++;DFS(a + dx[i], b + dy[i]);cnt--;map[a][b] = 0;}}}int main(void){// ⼊⼝是 map[0][0], 出⼝是 map[m-1][n-1]while (scanf("%d%d", &n, &m) != EOF){for (int i = 0; i < n; i++)for (int j = 0; j < m; j++)scanf("%d", &map[i][j]);cnt = 0;DFS(0, 0);}system("pause");return0;}/*测试数据第⼀组6 50 0 1 1 10 0 0 0 11 0 1 0 11 1 1 0 11 0 1 0 0结果(0,0)->(0,1)->(1,1)->(1,2)->(1,3)->(2,3)->(3,3)->(4,3)->(5,3)->(5,4) (0,0)->(1,0)->(1,1)->(1,2)->(1,3)->(2,3)->(3,3)->(4,3)->(5,3)->(5,4)第⼆组6 50 0 1 1 10 0 0 0 11 0 1 0 11 0 1 0 11 0 1 0 11 0 0 0 0结果(0,0)->(0,1)->(1,1)->(1,2)->(1,3)->(2,3)->(3,3)->(4,3)->(5,3)->(5,4) (0,0)->(0,1)->(1,1)->(2,1)->(3,1)->(4,1)->(5,1)->(5,2)->(5,3)->(5,4) (0,0)->(1,0)->(1,1)->(1,2)->(1,3)->(2,3)->(3,3)->(4,3)->(5,3)->(5,4) (0,0)->(1,0)->(1,1)->(2,1)->(3,1)->(4,1)->(5,1)->(5,2)->(5,3)->(5,4) */View Code栈:#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>#define N 101#define MaxSize 10086int dx[] = { -1, 0, 1, 0 }, dy[] = { 0, 1, 0, -1 };int map[N][N];typedef struct Box{int x, y;int di; // 点的编号}bx;typedef Box any; // 可修改数据类型typedef struct SqStack // 顺序栈{#define MaxSize 666any a[MaxSize];int pt; // 栈顶指针SqStack() {pt = -1;}void push(any e) { // ⼊栈a[++pt] = e;}void pop() { // 出栈pt--;}any top() { // 取栈顶元素return a[pt];}bool empty() { // 判断栈是否为空return pt == -1;}int size() { // 返回栈的⼤⼩return pt + 1;}}st;void show(st s){st t;while (!s.empty()){bx v = s.top();s.pop();t.push(v);}int cnt = 0;while (!t.empty()){bx v = t.top();t.pop();if (cnt++ == 0)printf("(%d,%d)", v.x, v.y);elseprintf("->(%d,%d)", v.x, v.y);}puts("");}void dfs(int xi, int yi){st s; // 定义栈bx start = { xi,yi,0 };map[xi][yi] = -1;s.push(start); // 起点进栈int cnt = 1; // 记录路径数while (!s.empty()){// 1,取栈顶元素,相当于函数形参bx vertex = s.top();// 2,找到终点,回溯(关键点:消除搜索的痕迹)if (vertex.x == n - 1 && vertex.y == m - 1){show(s);s.pop();map[vertex.x][vertex.y] = 0;continue;}// 3,如果有路可⾛,就继续搜索int find = 0; // find = 1 表⽰下⼀步可⾛,0 表⽰下⼀步不可⾛for (int i = vertex.di; i < 4; i++){bx next{ vertex.x + dx[i], next.y = vertex.y + dy[i] ,0 };if (next.x < 0 || next.y < 0 || next.x >= n || next.y >= m)continue;if (map[next.x][next.y] == 0){find = 1;vertex.di = i + 1; // 标记这个⽅块已经⾛过的⽅向,⼿动确定回溯时不会重复回溯。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

通常,一个函数在调用另一个函数之前,要作如下的事情:a)将实在参数,返回地址等信息传递给被调用函数保存; b)为被调用函数的局部变量分配存储区;c)将控制转移到被调函数的入口.从被调用函数返回调用函数之前,也要做三件事情:a)保存被调函数的计算结果;b)释放被调函数的数据区;c)依照被调函数保存的返回地址将控制转移到调用函数.所有的这些,不论是变量还是地址,本质上来说都是"数据",都是保存在系统所分配的栈中的.ok,到这里已经解决了第一个问题:递归调用时数据都是保存在栈中的,有多少个数据需要保存就要设置多少个栈,而且最重要的一点是:控制所有这些栈的栈顶指针都是相同的,否则无法实现同步.下面来解决第二个问题:在非递归中,程序如何知道到底要转移到哪个部分继续执行?回到上面说的树的三种遍历方式,抽象出来只有三种操作:访问当前结点,访问左子树,访问右子树.这三种操作的顺序不同,遍历方式也不同.如果我们再抽象一点,对这三种操作再进行一个概括,可以得到:a)访问当前结点:对目前的数据进行一些处理;b)访问左子树:变换当前的数据以进行下一次处理;c)访问右子树:再次变换当前的数据以进行下一次处理(与访问左子树所不同的方式).下面以先序遍历来说明:void preorder_recursive(Bitree T) /* 先序遍历二叉树的递归算法*/{if (T) {visit(T); /* 访问当前结点*/preorder_recursive(T->lchild); /* 访问左子树*/preorder_recursive(T->rchild); /* 访问右子树*/}}visit(T)这个操作就是对当前数据进行的处理, preorder_recursive(T->lchild)就是把当前数据变换为它的左子树,访问右子树的操作可以同样理解了.现在回到我们提出的第二个问题:如何确定转移到哪里继续执行?关键在于一下三个地方:a)确定对当前数据的访问顺序,简单一点说就是确定这个递归程序可以转换为哪种方式遍历的树结构;b)确定这个递归函数转换为递归调用树时的分支是如何划分的,即确定什么是这个递归调用树的"左子树"和"右子树"c)确定这个递归调用树何时返回,即确定什么结点是这个递归调用树的"叶子结点".二).三个例子好了上面的理论知识已经足够了,下面让我们看看几个例子,结合例子加深我们对问题的认识.即使上面的理论你没有完全明白,不要气馁,对事物的认识总是曲折的,多看多想你一定可以明白(事实上我也是花了两个星期的时间才弄得比较明白得).1)例子一: f(n) = n + 1; (n <2)= f[n/2] + f[n/4](n >= 2);这个例子相对简单一些,递归程序如下:int f_recursive(int n){ int u1, u2, f;if (n < 2)f = n + 1;else {u1 = f_recursive((int)(n/2)); u2 = f_recursive((int)(n/4));f = u1 * u2;}return f; }下面按照我们上面说的,确定好递归调用树的结构,这一步是最重要的.首先,什么是叶子结点,我们看到当n < 2时f = n + 1,这就是返回的语句,有人问为什么不是f = u1 * u2,这也是一个返回的语句呀?答案是:这条语句是在u1 = exmp1((int)(n/2))和u2 = exmp1((int)(n/4))之后执行的,是这两条语句的父结点. 其次,什么是当前结点,由上面的分析,f = u1 * u2即是父结点.然后,顺理成章的u1 = exmp1((int)(n/2))和u2 = exmp1((int)(n/4))就分别是左子树和右子树了.最后,我们可以看到,这个递归函数可以表示成后序遍历的二叉调用树.好了,树的情况分析到这里,下面来分析一下栈的情况,看看我们要把什么数据保存在栈中:非递归程序中我们已经看到了要加入一个标志域,因此在栈中要保存这个标志域;另外,u1,u2和每次调用递归函数时的n/2和n/4参数都要保存,这样就要分别有三个栈分别保存:标志域,返回量和参数,不过我们可以做一个优化,因为在向上一层返回的时候,参数已经没有用了,而返回量也只有在向上返回时才用到,因此可以把这两个栈合为一个栈.如果对于上面的分析你没有明白,建议你根据这个递归函数写出它的递归栈的变化情况以加深理解,再次重申一点:前期对树结构和栈的分析是最重要的,如果你的程序出错,那么请返回到这一步来再次分析,最好把递归调用树和栈的变化情况都画出来,并且结合一些简单的参数来人工分析你的算法到底出错在哪里.ok,下面给出我花了两天功夫想出来的非递归程序(再次提醒你不要气馁,大家都是这么过来的).代码:int f_nonrecursive(int n){int stack[20], flag[20], cp;/* 初始化栈和栈顶指针*/cp = 0;stack[0] = n;flag[0] = 0;while (cp >= 0) {switch(flag[cp]) {case 0: /* 访问的是根结点*/if (stack[cp] >= 2) { /* 左子树入栈*/flag[cp] = 1; /* 修改标志域*/cp++;stack[cp] = (int)(stack[cp - 1] / 2);flag[cp] = 0;} else { /* 否则为叶子结点*/stack[cp] += 1;flag[cp] = 2;}break;case 1: /* 访问的是左子树*/if (stack[cp] >= 2) { /* 右子树入栈*/flag[cp] = 2; /* 修改标志域*/cp += 2;stack[cp] = (int)(stack[cp - 2] / 4);flag[cp] = 1;} else { /* 否则为叶子结点*/stack[cp] += 1;flag[cp] = 2;}break;case 2: /* */if (flag[cp - 1] == 2) { /* 当前是右子树吗? *//** 如果是右子树, 那么对某一棵子树的后序遍历已经* 结束,接下来就是对这棵子树的根结点的访问*/stack[cp - 2] = stack[cp] * stack[cp - 1];flag[cp - 2] = 2;cp = cp - 2;} else/* 否则退回到后序遍历的上一个结点*/cp--;break; } }return stack[0]; }算法分析:a)flag只有三个可能值:0表示第一次访问该结点,1表示访问的是左子树,2表示已经结束了对某一棵子树的访问,可能当前结点是这棵子树的右子树,也可能是叶子结点.b)每遍历到某个结点的时候,如果这个结点满足叶子结点的条件,那么把它的flag域设为2;否则根据访问的是根结点,左子树或是右子树来设置flag域,以便决定下一次访问该节点时的程序转向.2)例子二快速排序算法递归算法如下:代码:void swap(int array[], int low, int high){ int temp;temp = array[low];array[low] = array[high];array[high] = temp; }int partition(int array[], int low, int high){int p;p = array[low];while (low < high) {while (low < high && array[high] >= p)high--;swap(array,low,high);while (low < high && array[low] <= p)low++;swap(array,low,high);}return low; }void qsort_recursive(int array[], int low, int high){ int p;if(low < high) {p = partition(array, low, high);qsort_recursive(array, low, p - 1);qsort_recursive(array, p + 1, high);} }需要说明一下快速排序的算法: partition函数根据数组中的某一个数把数组划分为两个部分,左边的部分均不大于这个数,右边的数均不小于这个数,然后再对左右两边的数组再进行划分.这里我们专注于递归与非递归的转换,partition函数在非递归函数中同样的可以调用(其实partition函数就是对当前结点的访问).再次进行递归调用树和栈的分析: 递归调用树:a)对当前结点的访问是调用partition函数;b)左子树:q sort_recursive(array, low, p - 1);c)右子树:qsort_recursive(array, p +1, high); d)叶子结点:当low < high时;e)可以看出这是一个先序调用的二叉树.栈:要保存的数据是两个表示范围的坐标.代码: void qsort_nonrecursive(int array[], int low, int high){ int m[50], n[50], cp, p;/* 初始化栈和栈顶指针*/cp = 0;m[0] = low;n[0] = high;while (m[cp] < n[cp]) {while (m[cp] < n[cp]) { /* 向左走到尽头*/p = partition(array, m[cp], n[cp]); /* 对当前结点的访问*/cp++; m[cp] = m[cp - 1];n[cp] = p - 1; }/* 向右走一步*/m[cp + 1] = n[cp] + 2;n[cp + 1] = n[cp - 1];cp++;} }3)例子三阿克曼函数: 代码:akm(m, n) = n + 1; (m = 0时)akm(m - 1, 1); (n = 0时)akm(m - 1, akm(m, n - 1)); (m != 0且n != 0时)递归算法如下: 代码:int akm_recursive(int m, int n){ int temp;if (m == 0)return (n + 1);else if (n == 0)return akm_recursive(m - 1, 1);else {temp = akm_recursive(m, n - 1);return akm_recursive(m - 1, temp);} }这道题的难点就是确定递归调用树的情况,因为从akm函数的公式可以看到,有三个递归调用,一般而言,有几个递归调用就会有几棵递归调用的子树,不过这只是一般的情况,不一定准确,也不一定非要机械化的这么作,因为通常情况下我们可以做一些优化,省去其中的一些部分,这道题就是一个例子.递归调用树的分析:a)是当m=0时是叶子结点;b)左子树是akm(m - 1, akm(m, n - 1))调用中的akm(m, n - 1)调用,当这个调用结束得出一个值temp时,再调用akm(m - 1, temp),这个调用是右子树.c)从上面的分析可以看出,这个递归调用树是后序遍历的树.栈的分析:要保存的数据是m, n,当n = 0 或m = 0时开始退栈,当n = 0时把上一层栈的m值变为m - 1,n变为1,当m = 0时把上一层栈的m值变为0,n变为n + 1.从这个分析过程可以看出,我们省略了当n = 0时的akm(m - 1, 1)调用,原来在系统机械化的实现递归调用的过程中,这个调用也是一棵子树,不过经过分析,我们用修改栈中数据的方式进行了改进.代码int akm_nonrecursive(int m, int n){ int m1[50], n1[50], cp;cp = 0; m1[0] = m; n1[0] = n;do {while (m1[cp] > 0) { /* 压栈, 直到m1[cp] = 0 */while (n1[cp] > 0) { /* 压栈, 直到n1[cp] = 0 */cp++;m1[cp] = m1[cp - 1];n1[cp] = n1[cp - 1] - 1; }/* 计算akm(m - 1, 1),当n = 0时*/m1[cp] = m1[cp] - 1;n1[cp] = 1;}/* 改栈顶为akm(m - 1, n + 1),当m = 0时*/cp--;m1[cp] = m1[cp] - 1; n1[cp] = n1[cp + 1] + 1;} while (cp > 0 || m1[cp] > 0);return n1[0] + 1;}递归算法详解C通过运行时堆栈支持递归函数的实现。

相关文档
最新文档