C#性能优化-树形结构递归优化
前言
大家好,我是wacky,最近在工作中遇到一个有趣的问题,同事反馈说WPF中有一个树形结构的集合,在加载时会直接报堆栈溢出,一直没时间(懒得)看,导致很久了也没人解决掉。于是,组长就把这个"艰巨"的任务交给了我。作为新人中的"高手",必然要义不容辞地接受挑战喽,废话不多说,走起。
分析
由于同事此前已经定位到出现问题的代码段,所以到我手中时要省掉不少功夫。打开代码后看了下,原来是这个树形结构使用了典型的递归操作来对每个节点的数据进行更新,在数据量一般时一切正常,但是当数据量达到几万个节点后,这段代码会直接报堆栈溢出的错误。
代码示例如下所示,已简化:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Tree
{
internal class TreeNode
{
public int Value { get; set; }
public List<TreeNode> Children { get; set; } public TreeNode(int value)
{
Value = value;
Children = new List<TreeNode>();
}
}
}
// See https://aka.ms/new-console-template for more information
// 创建一个树形结构
using Tree; internal class Program
{
static void Main(string[] args)
{
TreeNode root = new TreeNode(1);
TreeNode node2 = new TreeNode(2);
TreeNode node3 = new TreeNode(3);
TreeNode node4 = new TreeNode(4);
TreeNode node5 = new TreeNode(5);
TreeNode node6 = new TreeNode(6); root.Children.Add(node2);
root.Children.Add(node3);
node2.Children.Add(node4);
node2.Children.Add(node5);
node3.Children.Add(node6); PrintTreeNode(root);
Console.Read();
} static void PrintTreeNode(TreeNode node)
{
if (node == null)
{
return;
}
Console.WriteLine(node.Value);
foreach (TreeNode child in node.Children)
{
PrintTreeNode(child);
}
}
}
上述代码我们定义了一个树形结构的类,并加入对应节点,然后使用递归的方式将所有节点输出,在数据量达到前文提到的数量级时就会发生堆栈溢出。
既然是堆栈溢出,那么我们就需要考虑减少堆栈溢出的方式,也就是降低栈的深度。这里我们需要分析下为什么递归会导致堆栈溢出?顺便复习一下部分计算机基础知识点。
在计算机中,函数调用是通过栈(stack)这种数据结构去实现的,每当程序在调用一次函数时,就会进行压栈(push),每当函数返回后,才会进行出栈(pop)。但是栈的大小本身并不是无限的,加上我们使用C# CLR给的默认分配也不会很大,通常是在1MB左右,这样就会出现函数调用次数过多时,超出栈本身的大小,导致堆栈溢出。
而递归调用,一般都是在到达最后的结束点时,才会一层一层返回每个函数执行的结果。在本次例子中,树形结构存在几万个父子节点,就会导致递归层数过深,函数在栈中无法及时出栈,进而报错。
到这一步时,我们的思路就开始明朗了,既然递归会导致堆栈过深,那我们不妨把递归进行改写,使用其他方式来进行遍历。在通常的解法中,存在两种方式:尾递归优化和迭代。
尾递归优化
什么是尾递归优化?我们先说说什么是尾递归,尾递归是指在一个函数中,所有递归的调用都出现在函数的末尾,也就是递归的那一句在函数执行的最后,或代码路径在最后一句出现,我们就可以称之为尾递归。所以如果我们的递归调用本身不是尾递归的时候,可以通过改写,让它变成尾递归的方式。
为什么尾递归可以进行优化?原因是堆栈需要保存每次调用的返回地址及当时所有的局部变量状态,期间堆栈空间是无法释放的。使用尾递归堆栈可以不用保存上次的函数返回地址/各种状态值,而方法遗留在堆栈上的数据完全可以释放掉,这是尾递归优化的核心思想。
回到我们本次的例子中来,我们的代码已经是尾递归的形式了,但还会导致溢出,那这时我们就需要使用另外一种方法迭代去解决问题了。
迭代
迭代,在本质上就是循环,由于我们已经提到了递归在函数调用的过程中不会对栈进行弹出,那么我们就可以用迭代来模拟入栈出栈的方式来对遍历做优化。我们可以先定义一个栈用来存放所有父子节点,然后对父节点进行压栈,并使用while循环来模拟所有遍历操作,当栈不为空时就一直执行。在循环中我们可以对已经压栈的数据进行弹栈,做完逻辑操作后,再对其子节点进行压栈,一直重复此过程,直到所有节点都弹栈完成。
相关代码如下所示:
// See https://aka.ms/new-console-template for more information
// 创建一个树形结构
using Tree; internal class Program
{
static void Main(string[] args)
{
TreeNode root = new TreeNode(1);
TreeNode node2 = new TreeNode(2);
TreeNode node3 = new TreeNode(3);
TreeNode node4 = new TreeNode(4);
TreeNode node5 = new TreeNode(5);
TreeNode node6 = new TreeNode(6); root.Children.Add(node2);
root.Children.Add(node3);
node2.Children.Add(node4);
node2.Children.Add(node5);
node3.Children.Add(node6); IterativeTraversal(root);
Console.Read();
} static void IterativeTraversal(TreeNode root)
{
if (root == null)
{
return;
}
//定义一个栈,存放所有的树节点
Stack<TreeNode> stack = new Stack<TreeNode>();
//把根节点压栈
stack.Push(root);
while (stack.Count > 0)
{
TreeNode node = stack.Pop();
Console.WriteLine(node.Value);
//遍历完父节点后,将子节点压栈
for (int i = node.Children.Count - 1; i >= 0; i--)
{
stack.Push(node.Children[i]);
}
}
}
}
在这种方式中,我们每遍历一层节点,都会对栈进行释放,这样就保证了已经在栈中的层级不会太深,进而解决了堆栈溢出的问题。
总结
探寻好思路后,我和同事做了尝试,将代码改写完成后,遍历几万个节点一切正常,且不会出现卡死之类的其他问题,完美解决!虽然我们本次性能优化的思路并不复杂,代码写起来也相对简单,但背后其实蕴含着比较深刻的计算机原理知识。我们在日常工作中也需要多重视基础知识,包括数据结构和算法,这样才可以在遇到难以解决的问题时游刃有余,诸君共勉!
本文首发于我的公众号【wacky的碎碎念】,喜欢的话可以微信扫码关注哟,我们一起来聊聊技术,谈谈职场和人生~

C#性能优化-树形结构递归优化的更多相关文章
- MySQL 性能优化--优化数据库结构之优化数据类型
MySQL性能优化--优化数据库结构之优化数据类型 By:授客 QQ:1033553122 优化数字数据(Numeric Data) l 对于唯一ID或其它可用字符串或数字表示的值,选择 ...
- MySQL 性能优化--优化数据库结构之优化数据大小
MySQL性能优化--优化数据库结构之优化数据大小 By:授客 QQ:1033553122 尽量减少表占用的磁盘空间.通常,执行查询期间处理表数据时,小表占用更少的内存. 表列 l 尽可能使 ...
- mysql数据优化--数据库结构的优化
1,比如存时间类型的就使用int类型 其中mysql的两个函数可以拿来使用 unix_timestamp 将时间日期转化为时间戳
- 递归、嵌套for循环、map集合方式实现树形结构菜单列表查询
有时候, 我们需要用到菜单列表,但是怎么样去实现一个菜单列表的编写呢,这是一重要的问题. 比如我们需要编写一个树形结构的菜单,那么我们可以使用JQuery的zTree插件:http://www.tre ...
- 如何快速优化手游性能问题?从UGUI优化说起
WeTest 导读 本文作者从自身多年的Unity项目UI开发及优化的经验出发,从UGUI,CPU,GPU以及unity特有资源等几个维度,介绍了unity手游性能优化的一些方法. 在之前的文 ...
- Android 性能优化:使用 Lint 优化代码、去除多余资源
前言 在保证代码没有功能问题,完成业务开发之余,有追求的程序员还要追求代码的规范.可维护性. 今天,以“成为优秀的程序员”为目标的拭心将和大家一起精益求精,学习使用 Lint 优化我们的代码. 什么是 ...
- ORACLE性能优化之SQL语句优化
版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 操作环境:AIX +11g+PLSQL 包含以下内容: 1. SQL语句执行过程 2. 优化器及执行计划 3. 合 ...
- PLSQL_性能优化系列04_Oracle Optimizer优化器
2014-09-25 Created By BaoXinjian
- 性能调优之SQL优化
poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大家咨询qq:908821478,咨询电话010-845052 ...
- 一:MySQL数据库的性能的影响分析及其优化
MySQL数据库的性能的影响分析及其优化 MySQL数据库的性能的影响 一. 服务器的硬件的限制 二. 服务器所使用的操作系统 三. 服务器的所配置的参数设置不同 四. 数据库存储引擎的选择 五. 数 ...
随机推荐
- 在基于nuxt的移动端页面中引用mint UI的popup组件之父子组件传值
最近在做移动端的wap页面,考虑到要做SEO,所以选定了nuxt+vue+mint ui. 有一个需求是这样的,点击头部菜单栏,出现一个气泡,点击返回首页. 由于一些页面没有统一引用mint的mt-h ...
- 2021-09-25:给定一个字符串数组,将字母异位词组合在一起。可以按任意顺序返回结果列表。字母异位词指字母相同,但排列不同的字符串。示例 1:输入: strs = [“eat“, “tea“, “
2021-09-25:给定一个字符串数组,将字母异位词组合在一起.可以按任意顺序返回结果列表.字母异位词指字母相同,但排列不同的字符串.示例 1:输入: strs = ["eat" ...
- TokenObtainPairView
TokenObtainPairView是由Django REST framework的SimpleJWT库提供的视图.它用于生成JSON Web Token(JWT)
- HINT: Add or change a related_name argument to the definition for 'usersApp.
错误原因是你的项目使用的不是Django自带的用户表,采用的自定义的用户表,这个时候需要在settings.py里面进行指定. AUTH_USER_MODEL = 'usersApp.UserProf ...
- phpstudy-sqlilabs-less-11
题目:POST - Error Based - Single quotes- String 基于错误的单引号post型字符变形的注入 看到有个账密输入口第一反应尝试post注入 打开post data ...
- 记一次618军演压测TPS上不去排查及优化
本文内容主要介绍,618医药供应链质量组一次军演压测发现的问题及排查优化过程.旨在给大家借鉴参考. 背景 本次军演压测背景是,2B业务线及多个业务侧共同和B中台联合军演. 现象 当压测商品卡片接口的时 ...
- python利用subprocess执行shell命令
subprocess以及常用的封装函数 运行python的时候,我们都是在创建并运行一个进程.像Linux进程那样,一个进程可以fork一个子进程,并让这个子进程exec另外一个程序.在Python中 ...
- Python socket记录
目录 网络编程 1.基本概念 Python中的网络编程 网络编程 1.基本概念 1.什么是客户端/服务器架构? 服务器就是一系列硬件或软件,为一个或多个客户端(服务的用户)提供所需的"服务& ...
- Apikit SaaS 10.9.0 版本更新: 接口测试支持通过 URL 请求大型文件,支持导出为 Postman 格式文件
Hi,大家好! Eolink Apikit 即将在 2023年 6月 8日晚 18:00 开始更新 10.9.0 版本.本次版本更新主要是对多个应用级资源合并,并基于此简化付费套餐和降低费率. 本次应 ...
- 前端分页组件简单好用列表分页page组件
快速实现 简单好用列表分页组件, 分页器组件,用于展示页码.请求数据等 ,包含翻页. 详情请访问uni-app插件市场地址:https://ext.dcloud.net.cn/plugin?id=12 ...