leetcode - 二叉树的遍历的多种方式

leetcode - 二叉树的遍历

image-20210705161204946

1. 前序遍历

前序遍历:实际上理解为根节点在什么时候访问,

前序遍历访问顺序: 跟节点 -> 左子节点 -> 右子节点

预期结果:A,B,D,G,H,C,E,I,F

递归方法:

思路

跟节点 -> 左子节点 -> 右子节点

  1. 输出当前节点的
  2. 递归调用左子节点
  3. 递归调用右子节点

时间复杂度:O(n)

空间复杂度:O(n)


/// 前序遍历 - 递归
/// - Parameter node: node
func preorderTree(node: TreeNode<Any>?){
    if node == nil  { return }
    print(node?.value ?? "")
    preorderTree(node: node?.left)
    preorderTree(node: node?.right)
}

栈方式:

思路1

跟 左 右

  1. 创建栈
  2. 将根节点赋值给node
  3. 如果node不为空
    1. 输出node
    2. 如果右子节点不为空 入栈
    3. node = node.left
  4. 左子节点 == nil 时 判断栈的大小==0 则遍历结束
  5. 如果栈中有值 则弹出栈顶元素复制给node 继续循环
  6. 重复执行 3~5步骤

时间复杂度:O(n)

空间复杂度:O(n)

func preorderTree_Stack(root: TreeNode<Any>?){
    if root == nil  { return }
    let stack = Stack<TreeNode<Any>>() // 栈
    var node = root
    while true {
        if node != nil {
            print(node?.value ?? "")
            if let rNode = node?.right {
                stack.push(rNode)
            }
            node = node?.left
        }else if stack.size == 0{
            return  // 栈为空时
        }else{
            node = stack.pop() // 弹出栈顶元素
        }
    }
}

思路2

  1. 直接将root入栈
  2. 每次取出 栈顶元素 进行访问
  3. 判断是否有 左右子节点并入栈
  4. 重复执行 2~3 直至栈为空时 结束遍历 时间复杂度:O(n)

空间复杂度:O(n)

func preorderTree_Stack01(root: TreeNode<Any>?){
    if root == nil  { return }
    let stack = Stack<TreeNode<Any>>() // 栈
    stack.push(root!)
    while stack.isEmpty() == false {
        let node = stack.pop()
        print(node?.value ?? "")
        if let rNode = node?.right{
            stack.push(rNode)
        }
        if let lNode = node?.left{
            stack.push(lNode)
        }
    }
}

2. 中序遍历

image-20210705161204946

前序遍历:实际上理解为根节点在什么时候访问,

中序遍历访问顺序: 左子节点 -> 跟节点 -> 右子节点

预期结果:G,D,H,B,A,E,I,C,F

递归方法:

思路

  1. 递归调用左子节点
  2. 输出当前节点的
  3. 递归调用右子节点

时间复杂度:O(n)

空间复杂度:O(n)

func inorderTree(node: TreeNode<Any>?){
    if node == nil  { return }
    inorderTree(node: node?.left)
    print(node?.value ?? "")
    inorderTree(node: node?.right)
}

栈方式:

思路

  1. 将root赋值给node节点
  2. 循环执行以下操作: (退出条件 栈中的元素为空)
    1. 如果node不为空
      1. 将node入栈
      2. node 向左走, node = node.left
    2. 如果node为空
      1. 将栈中的元素弹出 node
      2. 输出 node的元素
      3. 将node向右走, (如果有值会 继续执行向左右的操作)

时间复杂度:O(n)

空间复杂度:O(n)

func inorderTree_Stack(root: TreeNode<Any>?){
    if root == nil  { return }
    let stack = Stack<TreeNode<Any>>()
    var node = root
    while node != nil {
        if let n = node{
            stack.push(n)
            node = node?.left
        }else if stack.size == 0{
            return
        }else{
            node = stack.pop()
            print(node?.value ?? "")
            node = node?.right
        }
    }
}

3. 后序遍历

image-20210705161204946

后序遍历:实际上理解为根节点在什么时候访问,

前序遍历访问顺序: 左子节点 -> 右子节点 -> 跟节点

预期结果:G,H,D,B,I,E,F,C,A

递归方法:

思路

  1. 递归调用左子节点
  2. 递归调用右子节点
  3. 输出当前节点的

时间复杂度:O(n)

空间复杂度:O(n)

func postorderTree(node: TreeNode<Any>?){
    if node == nil  { return }
    postorderTree(node: node?.left)
    postorderTree(node: node?.right)
    print(node?.value ?? "")
}

栈方式:

思路

遍历顺序 左右根 入栈顺序 根右左 则 出栈顺序 左右根

  1. 将root 入栈
  2. 创建一个 上一次弹出的元素prev 作为记录
  3. 循环执行以下操作
    1. 查看栈顶元素 peek 查看 仅仅是查看并不是出栈
    2. 如果是叶子节点 或者 上一次访问的是栈顶元素的的子节点
      1. 弹出栈顶元素 并记录为上一次弹出的元素 prev
      2. 进行访问输出
    3. 如果不是叶子节点
      1. 将右子节点入栈
      2. 将左子节点入栈 继续循环

时间复杂度:O(n)

空间复杂度:O(n)

func postorderTree_Stack(root: TreeNode<Any>?){
    if root == nil  { return }
    let stack = Stack<TreeNode<Any>>()
    var prev:TreeNode<Any>?
    stack.push(root!)
    while stack.size != 0 {
        let top = stack.peek()  // 查看栈顶元素是什么 没有删除
        if top?.isLeaf() == true || ( prev != nil && prev?.parent == top){
            // 是否是叶子节点 || 上一次访问的是top的子节点
            prev = stack.pop() // 出栈
            print(prev?.value ?? "")
        }else{
            if let rNode = top?.right {
                stack.push(rNode)
            }
            if let lNode = top?.left {
                stack.push(lNode)
            }
        }
    }
}

4. 层序遍历

image-20210705161204946

层序遍历访问顺序:顾名思义 一层一层的去遍历

预期结果:A,B,C,D,E,F,G,H,I

队列方式:

思路

利用 队列 先进先出的性质

  1. 创建队列 并将根节点加入队列中
  2. 循环执行以下条件:(如果队列中的元素为空时 退出循环)
    1. 将队列中的队顶元素node出队
    2. 输出队列的值
    3. 判断node的左子节点是否为空 不为空加入队列
    4. 判断node的右子节点是否为空 不为空加入队列

时间复杂度:O(n)

空间复杂度:O(n)

func levelTree_Queue(root: TreeNode<Any>?){
    if root == nil { return }
    var node = root
    let queue = Queue<TreeNode<Any>>()
    queue.enQeueu(node!)
    while queue.isEmpty() == false {
        node = queue.deQeueu()
        print(node?.value ?? "")
        if node?.left != nil {
            queue.enQeueu(node?.left)
        }
        if node?.right != nil {
            queue.enQeueu(node?.right)
        }
    }
}

5. 扩展

1. 二叉树的高度

递归方式:

思路:

  1. root 为空 直接返回 0
  2. 递归寻找左右子树中 最大的高度 +1 就可以了
func levelCount(root: TreeNode<Any>?) -> Int{
    if root == nil { return 0 }
    return max(levelCount(root: root?.left), levelCount(root: root?.right)) + 1
}
迭代方式

思路:

利用层序遍历

  1. 创建队列 并将根节点加入队列中
  2. 默认高度为0
  3. levelCount 当前遍历的层数 有多少个节点
  4. 循环执行以下条件:(如果队列中的元素为空时 退出循环)
    1. levelCount 减1
    2. 将队列中的队顶元素node出队
    3. 输出队列的值
    4. 判断node的左子节点是否为空 不为空加入队列
    5. 判断node的右子节点是否为空 不为空加入队列
    6. 当levelCount == 0 时 证明当前层的节点全部遍历完成
      1. 这时队列中的数量则为下一层节点的数量 levelCount = queue.size
      2. 二叉树的高度 height + 1
func levelCount_queue(n: TreeNode<Any>?) -> Int{
    if n == nil { return 0 }
    var node = n
    let queue = Queue<TreeNode<Any>>()
    queue.enQeueu(node!)
    var height = 0
    var levelCount = 1
    while queue.isEmpty() == false {
        node = queue.deQeueu()
        levelCount -= 1
        print(node?.value ?? "")
        if node?.left != nil {
            queue.enQeueu(node?.left)
        }
        if node?.right != nil {
            queue.enQeueu(node?.right)
        }
        if levelCount == 0 {
            levelCount = queue.size
            height+=1
        }
    }
    return height
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值