2020-08-26:裸写算法:树的非递归先序遍历。
时间:2022-07-24
本文章向大家介绍2020-08-26:裸写算法:树的非递归先序遍历。,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
福哥答案2020-08-26:
方法 1:迭代
算法
从根节点开始,每次迭代弹出当前栈顶元素,并将其孩子节点压入栈中,先压右孩子再压左孩子。
在这个算法中,输出到最终结果的顺序按照 Top->Bottom 和 Left->Right,符合前序遍历的顺序。
算法复杂度
时间复杂度:访问每个节点恰好一次,时间复杂度为 O(N) ,其中 N 是节点的个数,也就是树的大小。
空间复杂度:取决于树的结构,最坏情况存储整棵树,因此空间复杂度是 O(N)。
方法 2:莫里斯遍历
方法基于 莫里斯的文章,可以优化空间复杂度。算法不会使用额外空间,只需要保存最终的输出结果。如果实时输出结果,那么空间复杂度是 O(1)。
算法
算法的思路是从当前节点向下访问先序遍历的前驱节点,每个前驱节点都恰好被访问两次。
首先从当前节点开始,向左孩子走一步然后沿着右孩子一直向下访问,直到到达一个叶子节点(当前节点的中序遍历前驱节点),所以我们更新输出并建立一条伪边 predecessor.Right = root 更新这个前驱的下一个点。如果我们第二次访问到前驱节点,由于已经指向了当前节点,我们移除伪边并移动到下一个顶点。
如果第一步向左的移动不存在,就直接更新输出并向右移动。
算法复杂度
时间复杂度:每个前驱恰好访问两次,因此复杂度是 O(N),其中 N 是顶点的个数,也就是树的大小。
空间复杂度:我们在计算中不需要额外空间,但是输出需要包含 N 个元素,因此空间复杂度为 O(N)。
代码用golang编写,如下:
package test34_preordertraversal
import (
"fmt"
"testing"
)
//https://leetcode-cn.com/problems/binary-tree-preorder-traversal/solution/er-cha-shu-de-qian-xu-bian-li-by-leetcode/
//go test -v -test.run TestPreorderTraversal
func TestPreorderTraversal(t *testing.T) {
root := &TreeNode{}
root.Val = 1
root.Left = &TreeNode{}
root.Left.Val = 2
root.Right = &TreeNode{}
root.Right.Val = 3
root.Left.Left = &TreeNode{}
root.Left.Left.Val = 4
root.Left.Right = &TreeNode{}
root.Left.Right.Val = 5
root.Right.Left = &TreeNode{}
root.Right.Left.Val = 6
root.Right.Right = &TreeNode{}
root.Right.Right.Val = 7
fmt.Println(preorderTraversal1(root))
fmt.Println(preorderTraversal2(root))
}
//Definition for a binary tree node.
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
//方法 1:迭代
//从根节点开始,每次迭代弹出当前栈顶元素,并将其孩子节点压入栈中,先压右孩子再压左孩子。
//在这个算法中,输出到最终结果的顺序按照 Top->Bottom 和 Left->Right,符合前序遍历的顺序。
//算法复杂度
//时间复杂度:访问每个节点恰好一次,时间复杂度为 O(N) ,其中 N 是节点的个数,也就是树的大小。
//空间复杂度:取决于树的结构,最坏情况存储整棵树,因此空间复杂度是 O(N)。
func preorderTraversal1(root *TreeNode) []int {
stack := make([]*TreeNode, 0)
output := make([]int, 0)
if root == nil {
return output
}
//push 根
stack = append(stack, root)
for len(stack) > 0 {
//pop
node := stack[len(stack)-1]
stack = stack[0 : len(stack)-1]
output = append(output, node.Val)
if node.Right != nil {
//push右
stack = append(stack, node.Right)
}
if node.Left != nil {
//push左
stack = append(stack, node.Left)
}
}
return output
}
//方法 2:莫里斯遍历
//方法基于 莫里斯的文章,可以优化空间复杂度。算法不会使用额外空间,只需要保存最终的输出结果。如果实时输出结果,那么空间复杂度是 O(1)。
//算法
//算法的思路是从当前节点向下访问先序遍历的前驱节点,每个前驱节点都恰好被访问两次。
//首先从当前节点开始,向左孩子走一步然后沿着右孩子一直向下访问,直到到达一个叶子节点(当前节点的中序遍历前驱节点),所以我们更新输出并建立一条伪边 predecessor.Right = root 更新这个前驱的下一个点。如果我们第二次访问到前驱节点,由于已经指向了当前节点,我们移除伪边并移动到下一个顶点。
//如果第一步向左的移动不存在,就直接更新输出并向右移动。
//算法复杂度
//时间复杂度:每个前驱恰好访问两次,因此复杂度是 O(N),其中 N 是顶点的个数,也就是树的大小。
//空间复杂度:我们在计算中不需要额外空间,但是输出需要包含 N 个元素,因此空间复杂度为 O(N)。
func preorderTraversal2(root *TreeNode) []int {
output := make([]int, 0)
node := root
for node != nil {
if node.Left == nil {
//push根
output = append(output, node.Val)
//右
node = node.Right
} else {
predecessor := node.Left
for predecessor.Right != nil && predecessor.Right != node {
predecessor = predecessor.Right
}
if predecessor.Right == nil {
output = append(output, node.Val)
predecessor.Right = node
node = node.Left
} else {
predecessor.Right = nil
node = node.Right
}
}
}
return output
}
敲命令 go test -v -test.run TestPreorderTraversal ,执行结果如下:
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- FFmpeg + OpenGL ES 实现 3D 全景播放器
- 手把手教你自定义实现一个npm audit
- 异步神器CompletableFuture
- IoT安全测试指北
- Kubernetes 故障解决心得(一)
- Kubernetes 临时存储需要限制吗?
- PHP安全:变量的前世今生
- 如何使用ParamSpider在Web文档中搜索敏感参数
- Spring 的 Controller 是单例还是多例?怎么保证并发的安全
- 《剑指offer》第28天:最长上升子序列(高频)
- 《剑指offer》第27天:三角形最小路径和
- 《剑指offer》第26天:最大子序和
- Threat Dragon:一款针对OWASP的威胁模型构建平台
- 《剑指offer》第25天:最简单的动态规划
- 基于web页面开发串口程序界面---html代码