树的遍历 所有遍历方式,这一篇就够了
生成随机二叉树并彩色打印

喜欢的话,记得点赞和收藏哟!

1 模拟系统调用栈

编译器使用堆栈传递函数参数、保存返回地址等、这里我们把子问题的参数都压入栈中,通过顺序判断参数对应的过程是否执行来模拟返回地址,即下步应该做什么。

例子1:汉诺塔问题(属于中序遍历)
汉诺塔递归解
def hanoi(n, a, b, c):"""常规的汉诺塔问题,可以直接从a到c:param n: 数量:param a: 起始位置:param b: 辅助位置:param c: 目标位置:return: 总的移动次数"""num = 0if n == 0: return 0part1 = hanoi_original(n-1, a, c, b)print("move %s from %s to %s" % (n, a, c))part2 = hanoi_original(n-1, b, a, c)num = part1 + 1 + part2return num
汉诺塔非递归解
def hanoiStack(n, a, b, c):stack = []while n > 0 or len(stack)>0:while n > 0:# 问题(n,a,b,c)可以转为3个子问题,1汉诺塔(n-1,a,c,b),2移动(打印)(n,a,c)和3汉诺塔(n-1,b,a,c)# 压入子问题1的三个子问题,子问题1的解决转化为三个新的子问题,相当于子问题1已经解决了,所以用True来表示,False表示子问题未解决。params = [[True, n-1, a, c, b], [False, n, a, c], [False, n-1, b, a, c]]stack.append(params)n -= 1a, b, c = a, c, bfinish = stack.pop()# 当if not finish[1][0]:print("move %s from %s to %s" % (finish[1][1], finish[1][2], finish[1][3]))finish[1][0] = Trueif not finish[2][0]:n = finish[2][1]finish[2][0] = Trueif n > 0:stack.append(finish)a, b, c = finish[2][2:]
优化1 压栈的时候两个汉诺塔子问题的参数分别压入,打印移动过程的子问题不入栈
def hanoiStack(n, a, b, c):stack = []while n > 0 or len(stack) > 0:while n > 0:paramsLeft = [True, n-1, a, c, b]paramsRight = [False, n-1, b, a, c]stack.append(paramsRight)stack.append(paramsLeft)n -= 1c, b = b, cfinishLeft = stack.pop()finishRight = stack.pop()if not finishRight[0]:# 如果子问题3是False,先解决子问题2,再把子问题3转为它自己的子问题,False改为True.print("move %s from %s to %s" % (finishRight[1]+1, finishRight[3], finishRight[4]))finishRight[0] = Truen, b, a, c = finishRight[1:]a, b, c = b, a, cif n > 0:stack.append(finishRight)stack.append(finishLeft)
优化2 可以发现paramsLeft参数就是打酱油的,去掉
def hanoiOriginalStack3(n, a, b, c):stack = []while n > 0 or len(stack) > 0:while n > 0:# paramsLeft = [True, n-1, a, c, b]paramsRight = [False, n-1, b, a, c]stack.append(paramsRight)# stack.append(paramsLeft)n -= 1c, b = b, c# finishLeft = stack.pop()finishRight = stack.pop()if not finishRight[0]:print("move %s from %s to %s" % (finishRight[1]+1, finishRight[3], finishRight[4]))finishRight[0] = Truen, b, a, c = finishRight[1:]a, b, c = b, a, cif n > 0:stack.append(finishRight)# stack.append(finishLeft)
例子2 归并排序(后序遍历)
合并函数(L到mid有序,mid+1到R有序,排序L到R)
def mergeList(lyst, L, mid, R):help = []left, right = L, mid+1for i in range(L, R+1):if right > R or left <= mid and lyst[left] <= lyst[right]:help.append(lyst[left])left += 1else:help.append(lyst[right])right += 1for i in help:lyst[L] = iL += 1
归并排序递归解(使用了嵌套函数,外层函数只要传入要排序的列表就好了)
def mergeSort(lyst):def process(lyst, L, R):if L >= R:returnmid = L + ((R - L) >> 1)process(lyst, L, mid)process(lyst, mid + 1, R)mergeList(lyst, L, mid, R)L, R = 0, len(lyst)-1process(lyst, L, R)return lyst
归并排序非递归解(这个例子中没有像汉诺塔那个例子里用True或False来表示每个子问题是否解决,而是通过把解决的子问题参数置为1的方式,两种方式本质一样,这种从模拟系统栈的角度更好理解)
def mergeSortStack(lyst):L, R = 0, len(lyst)-1stack = []while L < R or len(stack) > 0:while L < R:mid = L + ((R - L) >> 1)pushargs = [(L, mid), (mid+1, R), (L, mid, R)]stack.append(pushargs)R = midlast = stack[-1]if last[1] != 1:L = last[1][0]R = last[1][1]if L == R:last[1] = 1elif last[2] != 1:mergeList(lyst, last[2][0], last[2][1], last[2][2])last[2] = 1else:stack.pop()if len(stack) > 0:# 把第一个不是1的子问题参数置为1if stack[-1][0] != 1:stack[-1][0] = 1elif stack[-1][1] != 1:stack[-1][1] = 1else:stack[-1][2] = 1return lyst
优化1 减少压栈的参数
def mergeSortStack1(lyst):L, R = 0, len(lyst) - 1stack = []while L < R or len(stack) > 0:while L < R:mid = L + ((R - L) >> 1)pushargsLeft = [True, L, mid]pushargsRight = [False, mid + 1, R]stack.append(pushargsRight)stack.append(pushargsLeft)R = midpopLeft = stack.pop()popRight = stack.pop()if not popRight[0]:L, R = popRight[1:]popRight[0] = Trueif L < R:stack.append(popRight)stack.append(popLeft)continueif popRight[0]:mergeList(lyst, popLeft[1], popLeft[2], popRight[2])return lyst
优化2 pushargsLeft像是在打酱油,mergeList函数用到了pushargsLeft的参数,把L放到pushargsRight中就可以不压入pushargsLeft了,改对应参数
def mergeSortStack2(lyst):L, R = 0, len(lyst) - 1stack = []while L < R or len(stack) > 0:while L < R:mid = L + ((R - L) >> 1)pushargsRight = [False,L, mid+1, R]stack.append(pushargsRight)R = midpopRight = stack.pop()if not popRight[0]:L, R = popRight[2], popRight[3]popRight[0] = Trueif L < R:stack.append(popRight)continueif popRight[0]:mergeList(lyst, popRight[1], popRight[2]-1, popRight[3])return lyst

这里小总结一下,用归纳法的归并排序时间复杂度常数项<递归函数<上面的非递归。
用这种思路改写非递归,套路性很强,通过比较可以发现,不管是中序遍历类型的递归还是后续遍历类型的递归,在这个套路里只是对应非递归子问题处理的位置不同而已。下面是这个套路的代码模板

def ...:stack = []while condition or len(stack) > 0:while condition:params = ...stack.append(params)condition = ...                   last = stack[-1]if last[1] != 1: (使用True,False的方式就是if not last[1][0]:)params = ... Or do somethinglast[1] = 1elif last[2] != 1:params = ... Or do somethinglast[2] = 1elif ...:......else:stack.pop()if len(stack) > 0:# 把第一个不是1的子问题参数置为1if stack[-1][0] != 1:stack[-1][0] = 1elif stack[-1][1] != 1:stack[-1][1] = 1elif ...:......else:stack[-1][.] = 1return something

2 模拟递归的调用过程

不把所有子问题的参数都压入栈中,只压入下步需要解决的子问题的参数。
这个思路的出发点是:如果有一个复杂的问题(这里解释一下什么是复杂问题和简单问题。复杂问题,比如大于1层的汉诺塔,超过两个数的合并排序,不能直接解决。简单问题,打印汉诺塔某一步的移动过程,只有一层的汉诺塔问题,少于两个数的归并排序问题,已经有序的两部分合并的问题,可以直接处理),用归简法的思想,把复杂的问题分解成若干个子问题,这样做的好处可能是,有的子问题规模比原来的问题小,有的子问题是简单问题,可以直接解决。
实现思路是,如果一个问题是复杂问题那么把它放入栈中(可以处理的简单问题就直接处理了,处理不了的复杂问题放入栈中待处理),压入表示待解决的问题,那么从栈中弹出就是要解决这个复杂问题,而一般的解决思路就是(递归的)分解问题,解决简单问题,把复杂问题继续放入栈中,循环这个过程(不会一直这样无限循环下去,因为复杂问题不断分解后,问题规模会不断变小直到成为简单问题)。

假设A问题可以分解为A1,A2,A3三个子问题解决。模拟系统栈的方式则是[(A1,A2,A3),(A11,A12,A12),(A111,A112,A113)…],
而模拟调用过程的方式则是[A,A1,A11,A111…]。
按照这个思路看代码。

汉诺塔非递归解

用这个观点考虑4层汉诺塔问题。
(4,left,mid,right) 是个复杂问题, 压入栈 [(4,left,mid,right)]
弹出(4,left,mid,right),是个复杂问题没有解决,所以还压回栈内(这里可以优化,如果子问题还是复杂问题就不弹出原来的问题,而直接压入子问题)。把这个复杂问题分解为 (3,left,right,mid) 复杂问题, (4,left,right) 把4从left移动到右,简单问题, (3,mid,left,right) 复杂问题。
虽然子问题2是简单问题,但是要等子问题1解决后才能解决,而子问题1是复杂问题,按照前面的思路,压入栈。
重复这个过程直到子问题变成了简单问题(也就是递归函数达到了basecase边界条件)
这时候需要分析弹出的问题和栈顶的问题之间的关联。弹出的问题是栈顶的问题的子问题1,对于栈顶的问题来说,它的子问题1已经解决了。所以每弹出一个问题,只需要解决子问题2和子问题3即可,子问题2是简单问题,可以直接解决,子问题3通常是复杂问题压入栈。

def hanoiOriginalStack4(n, a, b, c):stack = []params = [n, a, b, c]while stack or params[0] > 0:# params[0]即n, n>0是复杂问题,压入栈(把n=1作为边界)while params[0] > 0:stack.append(params)# 子问题1的参数,n>0压入栈params = [params[0] - 1, params[1], params[3], params[2]]# 按照上面的讨论, 弹出问题的子问题1已经解决finish = stack.pop()# 子问题2打印某一步移动过程是个简单问题print("move %s from %s to %s" % (finish[0], finish[1], finish[3]))# 子问题3的参数,会进入到上面的循环来判断是否是复杂问题params = [finish[0]-1, finish[2], finish[1], finish[3]]
归并排序非递归解

归并排序的子问题1:排序左边部分(复杂问题),子问题2:排序右边部分(复杂问题),子问题3:排序两个有序列表(简单问题)有的问题会没有子问题1,但都会有子问题2的。没有子问题1的时候,要解决的就只有子问题2,和子问题3.这时候把子问题2压入栈。弹出的问题说明达到了边界条件成为了简单问题或者子问题1,子问题2都解决了,只要解决子问题3就好了,而子问题3是个简单问题。

def mergeSort6(lyst):stack = []L, R = 0, len(lyst) - 1params=(L, R)while stack or params[0] < params[1]:# 排序的列表最左边元素下标小于最右边元素下标,复杂问题while params[0] < params[1]:stack.append(params)mid = params[0] + ((params[1]-params[0])>>1)if params[0] < mid:# 子问题1的参数params = (params[0], mid)else:# 没有子问题1的时候,才子问题2的参数送入循环params = (mid+1, params[1])L,  R = stack.pop()mid = L + ((R - L) >> 1)# 子问题3,mergeList(lyst, L, mid, R)if stack and L == stack[-1][0]:params = (R + 1, stack[-1][1])# else:#     passreturn lyst

前序遍历类型的递归(比如快速排序)改写成非递归,比较简单,这里没有给出改写的例子。
用这个思路来理解树的前序遍历,中序遍历和后序遍历就很自然了。

欢迎留言讨论。

递归改写成非递归的两种套路 Python实现相关推荐

  1. 递归转化成非递归过程_8086微处理器中的递归和重入过程

    递归转化成非递归过程 As we all know that a procedure is a set of instruction written separately which can be u ...

  2. 规范化的递归转换成非递归

    递归函数被调用时,系统需要一个运行栈.系统的运行栈要保存函数的返回地址,保存调用函数的局部变量,每一层递归调用所需保存的信息构成运行栈的一个工作记录,在没进入下一层递归调用是,系统就会建立一个新的工作 ...

  3. 数据结构--Avl树的创建,插入的递归版本和非递归版本,删除等操作

    AVL树本质上还是一棵二叉搜索树,它的特点是: 1.本身首先是一棵二叉搜索树. 2.带有平衡条件:每个结点的左右子树的高度之差的绝对值最多为1(空树的高度为-1). 也就是说,AVL树,本质上是带了平 ...

  4. 递归函数就兔子数C语言,【C语言】求斐波那契(Fibonacci)数列通项(递归法、非递归法)...

    意大利的数学家列昂那多·斐波那契在1202年研究兔子产崽问题时发现了此数列.设一对大兔子每月生一对小兔子,每对新生兔在出生一个月后又下崽,假若兔子都不死亡.问:一对兔子,一年能繁殖成多少对兔子?题中本 ...

  5. 二叉树遍历详解(递归遍历、非递归栈遍历,Morris遍历)

    一.前言 <二叉查找树全面详细介绍>中讲解了二叉树操作:搜索(查找).遍历.插入.删除.其中遍历深度优先遍历(DFS)按照实现方法可以分为:递归遍历实现.非递归遍历实现.Morris遍历实 ...

  6. 算法练习day10——190328(二叉树的先序、 中序、 后序遍历, 包括递归方式和非递归方式、找到一个节点的后继节点、二叉树的序列化和反序列化)

    1.实现二叉树的先序. 中序. 后序遍历, 包括递归方式和非递归方式 1.1 访问节点的顺序 节点访问顺序如下图所示: 访问顺序:1 2 4 4 4 2 5 5 5 2 1 3 6 6 6 3 7 7 ...

  7. 二叉树先中后序递归遍历与非递归遍历、层次遍历

    文章目录 1 先序遍历 1.1 先序遍历递归 1.2 先序遍历非递归 2 中序遍历 2.1 中序遍历递归 2.2 中序遍历非递归 3 后序遍历 3.1 后序遍历递归 3.2 后序遍历非递归 4 层序遍 ...

  8. C++-二叉树递归遍历与非递归遍历实现

    -二叉树递归遍历与非递归遍历实现 引言 0 有关线性表结点定义-LinkNode 1 栈的链式存储结构实现-LinkedStack 2 队列的链式存储结构实现-LinkedQueue 3 二叉树的链式 ...

  9. 二叉树创建,递归遍历,非递归遍历

    二叉树 博主是一个大一刚刚放暑假的大学生,大学我们只学习了c语言,现在这么卷只学c语言肯定不够,所以博主打算从零开始恶补c++顺便写文章记录一下,另外博主这个暑假还想记录一些算法基础内容欢迎关注哦.这 ...

最新文章

  1. MacOS系统下的图形化工具
  2. HTML表单input类型有哪些,HTML表单之input元素的23种type类型
  3. delphi查找对话框
  4. 7.python字符串-内置方法分析
  5. HDU 1059 Dividing
  6. 浅谈C++ STL中的优先队列(priority_queue)
  7. Tomcat version 6.0 only supports J2EE 1.2, 1.3, 1.4, and Java EE 5 Web modules
  8. layui表单元素的radio单选框问题
  9. Org-mode五分钟教程ZZZ
  10. c++ 中——fatal error: opencv2/opencv.hpp: No such file or directory #include <opencv2/opencv.hpp>
  11. 1.22.FLINK Watermark\Flink窗口(Window)\watermark有什么用?\如何使用Watermarks处理乱序的数据流?\机制及实例详解\生成方式\代码实例
  12. uni-app+微信小程序+云开发 爬取必应首页每日图片
  13. 记录第二次进行的助教培训-评分
  14. 记一次抓取网页内容(二)
  15. EVG实现芯片到晶圆的融合和混合键合
  16. 数据分析 - 9.MECE法(学习笔记)
  17. 忽尔今夏,SpringSide 3.0
  18. 使用HBuilder开发移动APP
  19. TP5.1自定义创建命令(php think make:controller app\index\User)
  20. C++运算符重载(类内、外重载)

热门文章

  1. Unity实时阴影实现——Cascaded Shadow Mapping
  2. androidui基础教程,来看看这份超全面的《Android面试题及解析》
  3. zoom 前台_Zoom Phone 电话会议快速入门指南
  4. VRML2.0的关键字
  5. 职称计算机考试在线答题,职称计算机考试试题答题技巧
  6. 小学生奥数倒水问题的数学模型与算法求解
  7. Unity3D实现地图编辑器的插件
  8. 机械设备制造行业ERP系统对企业有什么帮助?
  9. 抖音seo矩阵系统源码/系统搭建/系统架构
  10. 【网络电子杂志制作】云展网教程 | 编辑电子杂志下的目录设置