层序遍历
-
Go中list包的链表是一个双向链表,可用来作为一个先进先出的队列
-
广度优先遍(BFS)历通常借助队列实现
-
层序遍历代码
func levelOrder(root *TreeNode) [][]int {
if root == nil {
return [][]int{}
}
res := [][]int{}
queue := list.New() // 双向链表
queue.PushBack(root)
for queue.Len() > 0 {
len := queue.Len()
temp := []int{}
for i := 0; i < len; i++ {
// 类型断言,用于将 interface{} 类型的值转换为具体的类型 *TreeNode
node := queue.Remove(queue.Front()).(*TreeNode)
temp = append(temp, node.Val)
if node.Left != nil {
queue.PushBack(node.Left)
}
if node.Right != nil {
queue.PushBack(node.Right)
}
}
res = append(res, temp)
}
return res
}
- T(n) = O(n), S(n)最差 = O(n)
力扣相关题目:
前、中、后序遍历的递归实现
-
深度优先搜索(DFS)通常基于递归实现
-
递归的关键是:找到最后return的条件,并写在递归函数的开头
-
前序遍历
func preorderTraversal(root *TreeNode) []int {
res := []int{}
var preFunc func(root *TreeNode)
preFunc = func(root *TreeNode) {
if root == nil {
return
}
// 根左右
res = append(res, root.Val)
preFunc(root.Left)
preFunc(root.Right)
}
preFunc(root)
return res
}
- T(n) = O(n), S(n)最差 = O(n)
前、后序遍历的非递归实现(迭代法实现)
-
迭代法遍历二叉树实际上就是模拟一个栈进行操作,Go中用切片可以模拟栈操作
-
前序遍历
- 前序遍历为例(根左右),栈顶元素出栈时,先把左孩子压栈,再把右孩子压栈,这和前序遍历应该有的顺序相反,因为这是栈的FILO特性决定的
func preorderTraversal(root *TreeNode) []int {
if root == nil {
return []int{}
}
// 非递归实现,迭代法
res := []int{}
stack := []*TreeNode{}
stack = append(stack, root)
for len(stack) != 0 {
node := stack[len(stack)-1]
res = append(res, node.Val)
stack = stack[:len(stack)-1]
//FILO特性 先入右子树
if node.Right != nil {
stack = append(stack, node.Right)
}
if node.Left != nil {
stack = append(stack, node.Left)
}
}
return res
}
-
后序遍历
- 后序遍历(左右根),由于第一个入栈的一定是肯定是根节点,所以转换思路,按照根右左的顺序遍历,然后把遍历结果翻转即可
- 和前序的迭代遍历,只需要改变两步:1.左右子树入栈顺序交换 2.翻转遍历结果
func postorderTraversal(root *TreeNode) []int {
if root == nil {
return []int{}
}
// 非递归实现,迭代法
// 左右根
// 根右左 -> reverse
res := []int{}
stack := []*TreeNode{}
stack = append(stack, root)
for len(stack) != 0 {
node := stack[len(stack)-1]
res = append(res, node.Val)
stack = stack[:len(stack)-1]
//FILO特性 先入左子树
if node.Left != nil {
stack = append(stack, node.Left)
}
if node.Right != nil {
stack = append(stack, node.Right)
}
}
var reverse func(nums []int) []int
reverse = func(nums []int) []int {
p, q := 0, len(nums)-1
for i := 0; i < len(nums)/2; i++ {
nums[p], nums[q] = nums[q], nums[p]
p++
q--
}
return nums
}
res = reverse(res)
return res
}
- 中序遍历
- 前序遍历的顺序是根左右,先访问的元素是根节点,要输出的元素也是中间节点,访问的元素和要输出的元素顺序是一致的。而中序遍历的迭代法没这么简单,中序遍历要输出节点的顺序是左根右,而先访问的节点却是根节点,先访问的和先输出的不一致,故无法像前、后序列遍历那样简便。
- 我们需要分清遍历的两个组成部分,1.访问(遍历) 2.输出(处理)
- 当此刻访问的节点和要输出的节点不一致时,此时我们需要一个辅助指针cur用来表示当前访问的节点,当前访问的节点并不一定是需要立即输出的
func inorderTraversal(root *TreeNode) []int {
if root == nil {
return []int{}
}
// 左根右
cur := root
res := []int{}
stack := []*TreeNode{}
for len(stack) != 0 || cur != nil{
if cur != nil {
stack = append(stack, cur)
cur = cur.Left
} else {
cur = stack[len(stack)-1]
stack = stack[:len(stack)-1]
res = append(res, cur.Val)
cur = cur.Right
}
}
return res
}