[算法笔记]二叉树基础

前言:部分资料引自极客大学《数据结构与算法之美》,感谢王争老师!
参考链接:

  • https://visualgo.net/en/bst?slide=1
  • https://zh.wikipedia.org/wiki/二叉树#前(先)序、中序、後序遍歷
  • https://leetcode-cn.com/problems/binary-tree-inorder-traversal/
  • https://time.geekbang.org/column/article/67856
如何表示(或者存储)一棵二叉树?

想要存储一棵二叉树,我们有两种方法,一种是基于指针或者引用的二叉链式存储法,一种是基于数组顺序存储法。
1. 链式存储
每个节点有三个字段,其中一个存储数据,另外两个是指向左右子节点的指针。我们只要拎住根节点,就可以通过左右子节点的指针,把整棵树都串起来。这种存储方式我们比较常用。大部分二叉树代码都是通过这种结构来实现的。
在这里插入图片描述
2. 基于数组的顺序存储法
把根节点存储在下标 i = 1 的位置,那左子节点存储在下标 2 * i = 2 的位置,右子节点存储在 2 * i + 1 = 3 的位置。以此类推,B 节点的左子节点存储在 2 * i = 2 * 2 = 4 的位置,右子节点存储在 2 * i + 1 = 2 * 2 + 1 = 5 的位置。
在这里插入图片描述
思考:根节点存储在下标为1的位置,而不是0的位置,有可能和二进制位有关。
下表用4位二进制数来表示图中完全二叉树的示例:

根节点 左节点 右节点
0001 0010 0011
0010 0100 0101
0011 0110 0111
0100 1000 1001
0101 1010 1011

可以发现:
左节点下标 = 根节点下标右移1位
右节点下标 = 根节点下标右移1位 + 1
如果根节点下标从0开始,就不好运算了…
在这里插入图片描述
如果是非完全二叉树,且以数组的形式存储,在相应空白结点处还是会浪费空间的。所以完全二叉树使用数组来存储,空间浪费最少。

二叉树的属性

关于“树”,还有三个比较相似的概念:高度(Height)、深度(Depth)、层(Level)。它们的定义是这样的:
在这里插入图片描述
以下图为例:
在这里插入图片描述
王争老师对于二叉树的个人理解:
“高度”这个概念,其实就是从下往上度量,比如我们要度量第 10 层楼的高度、第 13 层楼的高度,起点都是地面。所以,树这种数据结构的高度也是一样,从最底层开始计数,并且计数的起点是 0。
“深度”这个概念在生活中是从上往下度量的,比如水中鱼的深度,是从水平面开始度量的。所以,树这种数据结构的深度也是类似的,从根结点开始度量,并且计数起点也是 0。
“层数”跟深度的计算类似,不过,计数起点是 1,也就是说根节点位于第 1 层。

二叉树的遍历

经典的方法有三种,前序遍历中序遍历后序遍历。其中,前、中、后序,表示的是节点与它的左右子树节点遍历打印的先后顺序

  • 前序遍历是指,对于树中的任意节点来说,先打印这个节点,然后再打印它的左子树,最后打印它的右子树。
  • 中序遍历是指,对于树中的任意节点来说,先打印它的左子树,然后再打印它本身,最后打印它的右子树。
  • 后序遍历是指,对于树中的任意节点来说,先打印它的左子树,然后再打印它的右子树,最后打印这个节点本身

这里使用了递归的知识,具体代码实现引自wiki:
前序遍历

visit(node)print node.valueif node.left  != null then visit(node.left)if node.right != null then visit(node.right)

中序遍历

visit(node)if node.left  != null then visit(node.left)print node.valueif node.right != null then visit(node.right)

后序遍历

visit(node)if node.left  != null then visit(node.left)if node.right != null then visit(node.right)print node.value

在这里插入图片描述
在这个二叉树中,
前序遍历的结果:M,G,D,B,A,C,F,E,J,H,I,K,L,S,P,O,N,Q,R,W,U,T,V,X,Z,Y
中序遍历的结果:A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z
后序遍历的结果:A,C,B,E,F,D,I,H,L,K,J,G,N,O,R,Q,P,T,V,U,Y,Z,X,W,S,M

对于各个遍历过程的个人理解:
前序遍历:

  • 用小明家族继承传家宝打比喻或许比较形象:继承家族传家宝的原则是传长子不传幼子,传后代不传兄弟 —- 从祖父,到父亲,到小明本人。假设小明绝后(没有子孙结点),那么小明的遗产就交给他的弟弟小磊(当前节点的 兄弟节点 )。如果小磊也很不幸绝后(好惨),那么就将财产留给他的叔叔,由叔叔传承给小磊的堂兄弟。 感觉传完宝物之后一个家族就完蛋了…

中序遍历:

  • 每次取走二叉树中 最左边的元素,其中最左边是字面意思(哈哈)。
    就拿这幅图为例,ABCDEFGHIJKL…是不是一目了然(手动狗头)
    注意根节点永远在左节点以及其后代的右部,右节点及其后代的左部。
    在这里插入图片描述
    后序遍历:
    假设小明要搭积木城堡,规则是先打好地基,再盖上楼顶。而且两个相邻城堡可以组合成一个大城堡的地基。
    还是以这幅图为例,先打好地基AC,盖上屋顶B。此时一个城堡搭建好了,可以搭建下一个城堡:先打好地基E,然后盖上屋顶F。最后盖第三层D…
    在这里插入图片描述

用二叉树表示下述表达式:a+b*(c-d)-e/f (又是取走最左边元素…同时)
在这里插入图片描述

  • 先序遍历 的序列是:-+a*b-cd/ef (传承家产)

  • 中序遍历 的序列是:a+b*c-d-e/f (摘除最左边)

  • 后序遍历 的序列是:abcd-*+ef/- (搭城堡)

小结:遍历二叉树:L、D、R分别表示遍历左子树、访问根结点和遍历右子树,则先(根)序遍历二叉树的顺序是DLR,中(根)序遍历二叉树的顺序是LDR,后(根)序遍历二叉树的顺序是LRD。还有按层遍历二叉树。这些方法的时间复杂度都是O(n),n为结点个数。

最后安利一个很好用的二叉树在线可视化平台: https://visualgo.net/en/bst?slide=1

Published by

风君子

独自遨游何稽首 揭天掀地慰生平