二叉树基础(上):如何遍历二叉树?

二叉树

二叉树,顾名思义,每个结点最多有两个“叉”,也就是两个子节点,分别是左子节点和右子节点。不过,二叉树并不要求每个结点都必须有两个子节点,可以只有左子节点或者只有右子节点。

最坏情况下,二叉树退化成链表,也就是每个节点只有左子节点或右子节点,这种情况下,二叉树就跟链表没有什么区别了。

满二叉树,除叶子节点外,每一层每一个节点都两个子节点的二叉树。满二叉树很好理解,只要看这颗二叉树的每一个节点有没有缺失,除了叶子节点外,其他的节点的有没有两个子节点就行了。

完全二叉树,最后一层叶子节点都靠左排列,并且除了最后一层,其他层的节点个数达到最大。

如何存储二叉树?

有两种存储方式,一种是基于链表的链式存储法,一种是基于数组顺序存储法。

链式存储

我们先来看链式存储法,每个节点有三个字段,其中一个存储数据,另外两个存储指向左右子节点的指针。链式存储法看起来很直观,大部分的代码都是通过链表这种结构实现的。

顺序存储

我们在做 leetcode 二叉树部分的代码时,能发现每次题目给的输入出都是数组这种形式,这其实就是二叉树的顺序存储方式。

普通二叉树
    3
   / \
  9   20
      / \
     15  6
[3, 9, 20, null, null, 15, 6]
完全二叉树
    1
   / \
  2   3
 / \  /
4  5 6
[1, 2, 3, 4, 5, 6]

我们不难看出,二叉树的顺序存储是按照从上往下,从左往右依次访问二叉树节点的,空节点的地方在数组中存储 null

我们发现,二叉树的顺序存储,在数组中出现元素为空的情况,这样就浪费了大量的存储空间。

所以,如果一棵完全二叉树,用数组存储无疑是最节省内存的一种方式。因为数组的存储方式不需要像链表那样,要存储额外的左右子节点的指针。这也是为什么完全二叉树要求最后一层的子节点都靠左的原因。

二叉树的遍历

前中序三种遍历方式的区别就是访问根节点的顺序

  • 前序遍历, 根-左-右
  • 中序遍历, 左-根-右
  • 后序遍历, 左-右-根
  • 层序遍历,一层一层地遍历

实际上,二叉树的前、中、后序遍历就是一个递归的过程。比如,前序遍历,其实就是先打印根节点,然后递归地打印左子树,最后递归地打印右子树

func traversal(root *TreeNode) {
  // 前序遍历
  traversal(root.Left)
  // 中序遍历
  traversal(root.Right)
  // 后序遍历
}
func levelOrder(root *TreeNode) (vals []int) {
    if root == nil {
        return
    }
    queue := []*TreeNode{root}
    for len(queue) != 0 {
        node := queue[0]
        vals = append(vals, node.Val)
        if node.Left != nil {
            queue = append(queue, node.Left)
        }
        if node.Right != nil {
            queue = append(queue, node.Right)
        }
        queue = queue[1:]
    }
    return
}

二叉树遍历的时间复杂度

二叉树每个节点最多会被访问两次,所以遍历操作的时间复杂度,跟节点个数 n 成正比,也就是说二叉树遍历的时间复杂度是 O(n)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值