00.二叉树基本
基本概念
- 结点:表示树中的元素。
- 结点的度:拥有子结点的个数
- 叶子:度为0的结点,也叫终端结点
- 树的度:树中结点的最大的度
- 结点的层次:根结点是第一层,它的孩子结点是第二层,依次类推。
- 树的高度:最大层次数。
- 二叉树:所有结点的度数不超过2的树。
- 满二叉树:高度为h的二叉树恰好有2^h-1个结点时。
- 完全二叉树:叶子结点只可能出现在最后一层或倒数第二层,每个非叶子结点要么有两个孩子结点,要么只有左孩子结点。
满二叉树
叶子节点全都在最底层,除了叶子节点之外,每个节点都有左右两个子节点
完全二叉树
叶子节点都在最底下两层,最后一层的叶子节点都靠左排列,并且除了最后一层,其他层的节点个数都要达到最大
2是满二叉树,3是完全二叉树
二叉树的存储
要理解完全二叉树定义的由来,需要先了解一棵二叉树的存储
想要存储一棵二叉树,有两种方法,一种是基于指针或者引用的二叉链式存储法,一种是基于数组的顺序存储法。
链式存储法
图中应该可以很清楚地看到,每个节点有三个字段,其中一个存储数据,另外两个是指向左右子节点的指针。我们只要拎住根节点,就可以通过左右子节点的指针,把整棵树都串起来。
这种存储方式比较常用。大部分二叉树代码都是通过这种结构来实现的
基于数组的顺序存储法
把根节点存储在下标i = 1的位置,那左子节点存储在下标2 * i = 2
的位置,右子节点存储在2 * i + 1 = 3
的位置。
以此类推,B节点的左子节点存储在2 * i = 2 * 2 = 4的位置,右子节点存储在2 * i + 1 = 2 * 2 + 1 = 5的位置
如果节点X存储在数组中下标为i的位置,下标为2 * i 的位置存储的就是左子节点,下标为2 * i + 1的位置存储的就是右子节点。
反过来,下标为i/2的位置存储就是它的父节点。通过这种方式,我们只要知道根节点存储的位置(一般情况下,为了方便计算子节点,根节点会存储在下标为1的位置),这样就可以通过下标计算,把整棵树都串起来
不过,刚刚举的例子是一棵完全二叉树,所以仅仅“浪费”了一个下标为0的存储位置。
如果是非完全二叉树,其实会浪费比较多的数组存储空间。你可以看我举的下面这个例子
所以,如果某棵二叉树是一棵完全二叉树,那用数组存储无疑是最节省内存的一种方式。因为数组的存储方式并不需要像链式存储法那样,要存储额外的左右子节点的指针。这也是为什么完全二叉树会单独拎出来的原因,也是为什么完全二叉树要求最后一层的子节点都靠左的原因。
二叉树的遍历
经典的方法有三种,前序遍历、中序遍历和后序遍历。 其中,前、中、后序,表示的是节点与它的左右子树节点遍历打印的先后顺序。
前序遍历
对于树中的任意节点来说,先打印这个节点,然后再打印它的左子树,最后打印它的右子树。(本左右)
中序遍历
对于树中的任意节点来说,先打印它的左子树,然后再打印它本身,最后打印它的右子树。(左本右 有序输出)
后序遍历
对于树中的任意节点来说,先打印它的左子树,然后再打印它的右子树,最后打印这个节点本身(左右本 左右都遍历完了在操作本身,释放二叉树时使用 )
前序遍历的递推公式:
preOrder(r) = print r->preOrder(r->left)->preOrder(r->right)
中序遍历的递推公式:
inOrder(r) = inOrder(r->left)->print r->inOrder(r->right)
后序遍历的递推公式:
postOrder(r) = postOrder(r->left)->postOrder(r->right)->print r
1个节点有左右子树,被分为3个点
递归的访问整棵树的时候都要访问这三个点