前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >递归与循环的效率迷思

递归与循环的效率迷思

作者头像
用户2615200
发布于 2019-07-02 10:14:49
发布于 2019-07-02 10:14:49
1.4K00
代码可运行
举报
运行总次数:0
代码可运行

本文简单比较了一下相同逻辑下,递归实现和循环实现的效率差异

已经不记得最初是从哪里获取的信息了,自己总有一个印象是递归的效率比循环差,因为递归有很大的函数调用开销,再加上递归可能存在的堆栈溢出问题(本文暂不考虑该问题),所以书写代码时还是尽量使用循环为好.

简单举个加法的例子(求解前 n 个自然数的和):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// C#

// recur version
int AddRecur(int val)
{
	if (val > 0)
	{
		return val + AddRecur(val - 1);
	}
	
	return 0;
}

// iter version
int AddIter(int val)
{
	var ret = 0;
	
	for (int i = 1; i <= val; ++i)
	{
		ret += i;
	}
	
	return ret;
}

简单 Profile 一下,发现循环版本比递归版本要快 64% 左右 ~

再举个耳熟能详的例子: 斐波那契数列

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// C#

// recur version
long FibonacciRecur(long index)
{
	if (index <= 1)
	{
		return index;
	}
	else 
	{
		return FibonacciRecur(index - 1) + FibonacciRecur(index - 2);
	}
}

// iter version
long FibonacciIter(long index)
{
	if (index <= 1)
	{
		return index;
	}
	else 
	{
		long pre = 0;
	    long cur = 1;
	    long next = 0;
	    
	    for (long i = 2; i <= index; ++i)
	    {
	    	// calc next
	    	next = pre + cur;
	    	// update pre and cur
	    	pre = cur;
	    	cur = next;
	    }
	    
	    return next;
	}
}

继续 Profile 一下,发现循环版本比递归版本要快了 96% 左右 ! 不过稍有递归经验的朋友都会看出,上面的递归实现会做很多的重复计算,更好的方式就是缓存一下中间的计算结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// C#

Dictionary<long, long> s_buffer = new Dictionary<long, long>();
		
long FibonacciRecur(long index)
{
	if (index <= 1)
	{
		return index;
	}
	else 
	{
		long pre = 0;
		if (!s_buffer.TryGetValue(index - 1, out pre)) 
		{
		    pre = FibonacciRecur(index - 1);
            s_buffer[index - 1] = pre;		    
		}
		
		long cur = 0;
		if (!s_buffer.TryGetValue(index - 2, out cur))
		{
		    cur = FibonacciRecur(index - 2);
            s_buffer[index - 2] = cur;		    
		}
		
		return pre + cur;
	}
}

改动之后,循环版本比递归版本就只快 64% 左右了 ~

试验到现在,似乎都印证了我之前的印象: 递归比循环慢,写代码就要写循环~

我们最后来看个真实的(也更复杂的)示例:查找指定名字的子节点(假设我们有一颗树形结构的节点树,给出根节点,查找某个指定名字的子节点)

以下是一个简易的树形结构实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// C#

public class Node 
{
	string m_name;
	List<Node> m_children = new List<Node>();
	
	public Node(string name, params Node[] children)
	{
		m_name = name;
		m_children.AddRange(children);
	}
	
	public string GetName()
	{
		return m_name;
	}
	
	public int GetChildCount()
	{
		return m_children.Count;
	}
	
	public Node GetChild(int index)
	{
		if (index >= 0 && index < m_children.Count) 
		{
			return m_children[index];
		}
		
		return null;
	}
}

查找树形结构的指定节点一般可以采用 BFSDFS,这里我们使用 DFS :

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// C#

Node FindChildRecur(Node parent, string name)
{
	if (parent != null)
	{	
		var childCount = parent.GetChildCount();
		for (int i = 0; i < childCount; ++i)
		{
			var child = parent.GetChild(i);
			if (child.GetName() == name) 
			{
				return child;
			}
			else 
			{
				var childNode = FindChildRecur(child, name);
				if (childNode != null)
				{
					return childNode;
				}
			}
		}
	}
	
	return null;
}

如果要将上面的递归代码改为循环代码,方法就没有之前那么简明了,需要我们自行来模拟调用栈:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// C#

Stack<Node> s_stack = new Stack<Node>();
		
Node FindChildIter(Node parent, string name)
{
	if (parent != null)
	{
		s_stack.Clear();
		s_stack.Push(parent);
		
		while (s_stack.Count > 0)
		{
			var curParent = s_stack.Pop();
			
			var childCount = curParent.GetChildCount();
			for (int i = childCount - 1; i >= 0; --i)
			{
				var child = curParent.GetChild(i);
				if (child.GetName() == name)
				{
					return child;
				}
				else
				{
					if (child.GetChildCount() > 0)
					{
					    s_stack.Push(child);
					}
				}
			}
		}
	}
	
	return null;
}

考虑到递归调用的高消耗,似乎我们应该将之前的递归代码改写为这种循环形式,但是 Profile 之后发现,其实循环版本还略慢于递归版本,原因就在于(模拟)调用栈的引入抵消了(甚至超过了)函数调用的开销.

其实一般而言,栈内存的操作消耗都要小于堆内存的操作消耗,上面例子中引入的(模拟)调用栈其实就是一种堆操作,考虑到 CLR(C#) 的可能影响,我也用 C++ 进行了一样的实现对比,最终结果也是一致的,甚至在 C++ 中实现的循环版本还要显著慢于其递归版本.

还有一个问题之前没有提及,就是代码可读性问题,从我个人经验来讲,递归代码的可读性大体上还是要优于循环代码的.

结论

一般而言,将递归代码改写为循环代码可以提高效率,但是一旦改写过程中引入了堆操作,那么结果往往是相反的.

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019年06月27日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
纯代码给WordPress网站手机端底部添加菜单栏功能
前段时间突发奇想,想给自己的WordPress网站手机端底部加个菜单栏,但是怎么实现呢,于是全百科网研究了两天终于有了办法,所以今天就分享给大家如何实现。
于飞云计算
2019/07/22
1.3K0
wordpress网站内置前端工具箱美化版分享
近突然想给自己的wordpress博客加一个前端工具箱的功能,网上流传的都也有很多,但是个人感觉不是太好看也不太好用,今天就给大家分享下全百科在用前端工具箱源码。
于飞云计算
2019/06/03
1.2K0
wordpress网站内置前端工具箱美化版分享
分享给WordPress添加欢迎语音,还可自定义欢迎语
当你访问本文章的时候是不是听到了”欢迎来到全百科网…”的欢迎语。接下来全百科网就教大家这个欢迎语如何设置成进入WordPress网站首页的时候播放。
于飞云计算
2019/07/02
6500
分享给WordPress添加欢迎语音,还可自定义欢迎语
纯代码给WordPress文章添加卡片式内链的方法
写文章的时候会经常文章中引用其他文章链接,是为了让用户方便浏览,也增加文章关联度;更重要的是适当的引用文章,也可以让内容更加丰满,对用户体验上也是有提高;但是常规的文章内链一般就是直接放一个链接进去,干巴巴的一个链接不管是美观度还是用户体验都不是很好,所有全百科网搞了个简约美观的卡片内链样式。
于飞云计算
2019/07/22
1.4K0
纯代码给WordPress文章添加卡片式内链的方法
html5+CSS3+JS实现七夕言情功能代码
因为今天2月14日是情人节,作为我这个程序猿一枚也不甘落后,还有一颗脱单的心,下面小编给大家制作了基于html5+css3+js实现的情人节特效,具体实例代码,大家参考下本文。
用户7293182
2020/07/20
1.3K0
html5+CSS3+JS实现七夕言情功能代码
利用Node中间层,对接讯飞实现h5页面文章tts(自动朗读)功能
调研发现,竞品h5是app原生实现,而我司都是h5实现文章阅读,所以开始进行h5的调研
super.x
2019/04/12
1.3K0
利用Node中间层,对接讯飞实现h5页面文章tts(自动朗读)功能
WordPress评论统计图
[wymusic title=”你知道我的迷惘 – Beyond”]347687[/wymusic]
楚客追梦
2022/11/11
6870
WordPress评论统计图
给网站添加鼠标点击特效富强、民主、和谐
(function() { var s = "_" + Math.random().toString(36).slice(2); document.write('<div style="" id="' + s + '"></div>'); (window.slotbydup = window.slotbydup || []).push({ id: "u4235867", container: s }); })();
爱游博客
2019/08/06
1.5K0
Emlog实现文章标题语音朗读
  昨天有朋友找我二次开发,需要实现文章标题语音朗读的功能,博文广记的收费版就有这个功能,找了找资料,或许speak.js可以实现,但我没有深入研究,因为我找到了一种更简单的方法,那就是百度提供的文字转语音接口。到这里就很简单了,把下面代码添加到ech0_log.php的合适位置。
陌涛
2019/03/01
7830
移动端H5开发入门
1. http://www.w3school.com.cn/h.asp
lpe234
2020/07/28
2.3K0
分享WordPress各种标签大全集合 以及如何调用
wordpress程序日渐成熟,开发者越来越多,各种模版层出不穷,但是想要做一个好的wordpress模版,前提不只是要掌握HTML5前端技术,还必须了解wordpress的各种标签如何调用才可以。今天全百科网就把整理的WordPress各种标签以及是如何调用分享给大家。
于飞云计算
2019/07/22
3.2K0
分享WordPress各种标签大全集合 以及如何调用
教你让b站视频的弹幕发出语音!
侦查弹幕非常简单,我常介绍的:用元素选择器,选中窗口,一看这个类名,然后看这里面这一个个标签,就知道和弹幕有关。
coder_koala
2020/12/07
1.5K0
教你让b站视频的弹幕发出语音!
纯代码为WordPress文章内容添加锚点目录功能 支持悬浮跟随版
之前写过一个利用代码给WordPress文章内容添加锚点目录功能,通俗讲就是给网站文章加上目录导航,今天全百科网就通过之前的基础又进一步优化了一下,让这个文章目录导航不占用文章内容的位置,悬浮于文章的左侧或者右侧,而且固定位置,跟随页面滚动,给浏览者更佳的浏览体验。
于飞云计算
2019/07/22
2.3K1
WordPress纯代码外链跳转效果的3款样式免费分享
前面给大家讲了WordPress怎么利用纯代码实现WordPress的外链跳转效果,今天给大家分享几款跳转样式。
于飞云计算
2019/06/26
1.6K0
WordPress纯代码外链跳转效果的3款样式免费分享
丰富排版页面——为你的wordpress主题添加短代码形式美化框
相信有些wordpresser知道这个东东,在一些主题上这是标配,如deve主题、iartwork主题。原理大概是通过wordpress本身的短代码功能,事先在主题用css样式定义一些美化框,在编辑文章时写入短代码修饰,正式发表后再前台就可以看到效果。 如果你不会或不想修改主题代码实现这个功能,可以考虑一款短代码插件S-shortcodes。使用插件与直接代码增加的效果几乎是一样的(即安装插件对WordPress 速度上影响不大)。详细可以见《S-shortcodes:WordPress短代码形式美
Jeff
2018/01/19
2.3K0
丰富排版页面——为你的wordpress主题添加短代码形式美化框
python根据ip获取地理位置再查询天气情况调百度语音合成朗读
虽然是造轮子,不过还是挺好玩的。主要的困难点再于编码问题。还有一个是部分使用python2.7的代码和python3.4之间的兼容性问题。代码发布在github中。https://github.com/luyishisi/The_python_code/tree/master/automatic_weather
十四君
2019/11/27
9850
TTS Text-to-speech(文字转语音)服务
官网链接:Speech Studio - Microsoft Azure (https://speech.azure.cn/audiocontentcreation)
红目香薰
2022/11/29
3.5K0
TTS Text-to-speech(文字转语音)服务
分享纯代码WordPress判断并自动添加图片ALT属性
如果你做SEO,一定会知道图片识需要添加alt属性的。但是手动每次添加还是相对比较麻烦的,尤其是图片较多的文章。所以全百科网花了点时间修改了站外链接添加nofollow的代码来实现判断是否有alt属性并自动添加alt属性,测试后十分完美。
于飞云计算
2019/07/08
1K0
分享纯代码WordPress判断并自动添加图片ALT属性
纯代码实现WordPress文章远程图片(外链)自动本地化
其实有很多插件是可以实现 wordpress 远程图片本地化的,但是有可能插件太多了,会影响网站的性能或者拖累服务器,降低网站的运行速度。不过如果你是代码控,不喜欢用插件,那么下面这段“wordpress 远程图片自动本地化“的代码也许适合你,复制下面的代码,然后粘贴到你当前 WordPress 主题的模版函数(functions.php)文件中保存即可:
于飞云计算
2019/07/08
1.8K0
Javascript快速入门(下篇)
Javascript, cheer up。 Ajax:其通过在Web页面与服务器之间建立一个额外的处理层,这个处理层就被称为Ajax引擎,它解释来自用户的请求,在后台以异步的方式处理服务器通信,其结
用户1216676
2018/01/24
9590
Javascript快速入门(下篇)
推荐阅读
相关推荐
纯代码给WordPress网站手机端底部添加菜单栏功能
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验