使用栈来模拟递归过程

2024-06-18 03:18

本文主要是介绍使用栈来模拟递归过程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

     使用栈来模拟递归过程

    
.为什么要学习递归与非递归的转换的实现方法
   1)并不是每一门语言都支持递归的
   2)有助于理解递归的本质
   3)有助于理解栈,树等数据结构

二.预先知识

   1)《数据结构》栈,二叉树,树的相关知识

   2)对递归有明确的认识

三.图的深度优先搜索

最近写有关图的算法,首先写的便是基础中的基础,深度优先与广度优先搜索。写完这两个算法的时候,看到了算法导论课后一题,讲的是将深度优先算法使用栈来模拟。

先贴出写下的有关深度优先搜索的代码

 

 

void DFS(MatrixPtr mptr, int d[], int f[],int father[], int color[])
//其中d[]用于记录结点第一次被发现时的递归的深度,f[]用于记录完成深度优先探索时的深度
{int temp = mptr->column,time=0;for(int i=0; i!=temp; i++){color[i] = WHITE;d[i] = 0;f[i] = 0;father[i] = NIT;}for(int i=0; i!=temp; i++){time=0;if(color[i]==WHITE)DFS_VISIT(i,mptr,&time,d,f,father,color);}
}void DFS_VISIT(int i,MatrixPtr mptr, int* time, int d[], int f[], int father[],int color[])
//i表示参与深度优先搜索的标号,返回值表示当前的深度
{d[i] = (*time)++;color[i] = GREY;for(int j=0; j!=mptr->column; j++)if(mptr->matrix[i][j]&&color[j]==WHITE){father[j] = i;DFS_VISIT(j,mptr,time,d,f,father,color);}color[i] = BLACK;f[i] = ++*time;}


其实两个函数里只有DFS_VISIT中存在递归程序。于是开始写,最初写程序,其实写的是非常的混乱的。后来,经过网上开了相应的文章才有所顿悟。尤其是这篇

 

http://www.chinaunix.net/old_jh/23/331522.html如何用栈实现递归与非递归的转换

 

带给我非常大的触动。文中提出  “递归与非递归的转换基于以下的原理:所有的递归程序都可以用树结构表示出来.需要说明的是这个"原理"并没有经过严格的数学证明有三种方法可以遍历树:前序,中序,后序.理解这三种遍历方式的递归和非 递归的表达方式是能够正确实现转换的关键之处,所以我们先来谈谈这个.需要说明的是,这里以特殊的 二叉树来说明,不过大多数情况下二叉树已经够用,而且理解了二叉树的遍历,其它的树遍历方式就不难了

 递归实际上是在执行到递归函数时,将现有的函数中现场保存入栈中,执行完函数后,恢复现场。于是在处理这类问题是,关键便是思考那些数据是必须要存入栈中的,这点非常的重要。(其实什么程序,预先思考都是非常重要的)

我开始观察这段代码,发现DFS_VSIT只是树的后序遍历。同时在执行递归是,必须装入当前的i值与执行到for循环中的哪一步。然后还要装入下一次要完成的i值。

现在给出完成的代码

void DFS_VISIT2(int i,MatrixPtr mptr, int time, int d[], int f[], int father[],int color[])
//i表示参与深度优先搜索的标号,返回值表示当前的深度
{stack<int> stk;stk.push(i);//推入有节点,几乎所有类似的题目都是这么写的stk.push(-1);while(!stk.empty()){int j = stk.top()+1;//由于推入的j还原的之后必然要加1,所以这么处理stk.pop();int i = stk.top();stk.pop();if(j==0){d[i] = time++;color[i] = GREY;}for(; j!=mptr->column; j++)if(mptr->matrix[i][j]&&color[j]==WHITE){father[j] = i;stk.push(i);stk.push(j);stk.push(j);stk.push(-1); //最初是是使用一个if语句来判断是否是返回上一个的递归//状态中,后来想到干脆推入新的新的结点是也推入一个初始的j值             break;}if(j==mptr->column){color[i] = BLACK;f[i] = ++time;}}
}


四.二叉树的遍历算法

为了对上述方法,进行验证,我对二叉树的三种遍历进行了尝试。

typedef struct BitNode 
{unsigned int data;BitNode* lchild,* rchild;
}BitNode,* BitNodePtr,** BitNodePtrPtr;


先序遍历比较的简单只存在访问数据,两个子树指针的入栈

 

void PreTravel2(BitNodePtr bitptr)//使用栈替代递归的算法
{stack<BitNode*> stk;stk.push(bitptr);
while(!stk.empty()){BitNode* tempbitptr = stk.top();stk.pop();if(tempbitptr!=NULL){visit(tempnbitptr);stk.push(tempbitptr->rchild);stk.push(tempbitptr->lchild);}}
}


中序遍历最初的想法是,先将左子树的指针一口气推到底,在访问数据,然后在推入右子树。

 

void InTravel3(BitNodePtr bitptr)
{stack<BitNodePtr> stk;stk.push(bitptr);while(!stk.empty()){BitNodePtr p = stk.top();while (p !=NULL ) 	/* 向左走到尽头 */{stk.push(p->lchild);p=stk.top();}	stk.pop();	 /* 空指针退栈 */	if (!stk.empty()) {p = stk.top();	            stk.pop();		    visit(p);	 /* 访问当前结点 */		    stk.push(p->rchild);	/* 向右走一步 */		}}
}


在这里巧妙地利用了空指针与退栈操作。

不过我在写这段代码的,时候出现了一些问题那就是无法有效的判断是否已经完成了左子树的访问。为此依据他人的写法完成后,在我又另外写了一段代码,来弥补上次的失误。

 

void InTravel2(BitNodePtr bitptr)
{stack<Node> stk;Node d = {bitptr,true};stk.push(d);while(!stk.empty()){BitNodePtr temptr = stk.top().p;Nodeptr top = &stk.top();while(stk.top().flag&&temptr!=NULL&&temptr->lchild!=NULL)//推入所有的左子树内容{temptr = temptr->lchild;Node d = {temptr,true};stk.push(d);}top->flag = false;//关键的地方temptr = stk.top().p;stk.pop();if(temptr!=NULL){Visit(temptr);Node d = {temptr->rchild,true};stk.push(d);}}
}


在标有注释的地方将已经推入所有的左子树的指针的标志记为false,下次在访问的时候就不会在进行推左子树的操作。

为什么这么做呢,因为在思考中序遍历的操作是发现恢复现场的时候,实际上是恢复到了推完所有的左子树的瞬间。因此在这里写的时候,理由一个flag来判断是否处于需要左子树的状态。

 

 

后序遍历

与前面的中序遍历相似,在这里有一个flag,来判断是否处于需要推入子树的阶段。

在这里解释一下,在后序遍历的时候,先访问左右的子树,再访问自身,恢复现场时,是已经访问完左右子树的状态。同时,如果是叶节点的话直接访问。

void AfterTravel1(BitNodePtr bitptr)
{stack<Node> stk;Node d = {bitptr,false};stk.push(d);while(!stk.empty()){Node d = stk.top();stk.pop();if(d.flag||NULL==d.p->lchild&&NULL==d.p->rchild){visit(d.p);}else{Node e = {d.p,true};stk.push(e);if(d.p->rchild!=NULL){Node e = {d.p->rchild,false};stk.push(e);}if(d.p->lchild!=NULL){Node e = {d.p->lchild,false};stk.push(e);}}}
}


五.最后的总结

多思考,多尝试。在写之前,不妨在纸上模拟一下状态。

这篇关于使用栈来模拟递归过程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1071162

相关文章

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

作业提交过程之HDFSMapReduce

作业提交全过程详解 (1)作业提交 第1步:Client调用job.waitForCompletion方法,向整个集群提交MapReduce作业。 第2步:Client向RM申请一个作业id。 第3步:RM给Client返回该job资源的提交路径和作业id。 第4步:Client提交jar包、切片信息和配置文件到指定的资源提交路径。 第5步:Client提交完资源后,向RM申请运行MrAp

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

usaco 1.2 Transformations(模拟)

我的做法就是一个一个情况枚举出来 注意计算公式: ( 变换后的矩阵记为C) 顺时针旋转90°:C[i] [j]=A[n-j-1] [i] (旋转180°和270° 可以多转几个九十度来推) 对称:C[i] [n-j-1]=A[i] [j] 代码有点长 。。。 /*ID: who jayLANG: C++TASK: transform*/#include<

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma