[toc]
数据结构与算法笔记-数据结构-二叉树
树(Tree)
树结构基本上就是和现实中的树外观看起来差不多,每个元素叫做节点,相邻节点的称之为父子关系.
树结构的节点关系如下图
-
A节点就是B节点的父节点
-
B节点是A节点的子节点
-
B,C,D这三个节点的父节点是同一个节点,所以它们之间互称为兄弟节点
-
没有父节点的节点的为根节点,也就是图中的节点E
-
没有子节点的节点为叶子节点或者叶节点,比如图中的G,H,I,J,K,L都是叶子节点
树结构有三个相似的概念: 高度(Height),深度(Depth),层(Level)
高度(Height),深度(Depth),层(Level),如图
二叉树(Binary Tree)
树结构多种多样,常用的是二叉树,顾名思义二叉树最多有两个叉.
二叉树,每个节点最多有两个子节点
二叉树,不要求每个节点必须有两个子节点
二叉树如下图:
上图中比较特殊的2号和3号
2号的叶子节点都在最底一层,除了叶子节点,每个节点都有左右两个子节点, 这就是满二叉树
3号的叶子节点都在最底两层,最后一层的叶子都靠左排列,且除了最后一层,其他层的节点个数都要达到最大,这就是完全二叉树
存储二叉树
存储方式一般有两种
-
基于指针的二叉链式存储法,最常用也是最简单的,大部分二叉树代码都是通过这种结构实现的.
-
基于数组的顺序存储法,最好只用来存储完全二叉树,否则本来是可以节省内存的结果造成更多的浪费.
下图二叉链式存储法:
每个节点有三个字段一个存数据,另外外两个是指向左右子节点的指针
注意,并不是说,只能有三个字段,也可以是四个字段,多出了一个字段作为指向父节点的指针.
下图基于数组的顺序存储法的完全二叉树:
- 把根节点放在下标为1的位置,设i=1
- 左子节点的下标为2*i的位置,也就是2.
- 右子节点的下标为2*i+1的位置,也就是3
- 以此类推,如果求B节点的子节点的话i=2
- B节点的左子节点应该是2*i也就是4
- B节点的右子节点应该是2*i+1也就是5的位置
如果节点X存储在下标为i的位置,那节点X的左子节点就是2*i
,右子节点为2*i+1
.反过来i/2也就是他的父节点.
只要知道根节点的存储位置,就可以通过计算下表拿到整个树.
下图基于数组的顺序存储法的非完全二叉树:
完全二叉树只不过浪费了数组中下标为0的一个位置, 而非完全二叉树会浪费比较多的数组存储空间.
完全二叉树, 用数组存储是最节省内存的, 因为数组的存储方式并不需要像链式存储法要存储额外的左右子节点的指针.
也就是为啥完全二叉树要最后一层的子节点都靠左的原因
堆其实也是一种完全二叉树,最常用的存储方式就是数组.
遍历二叉树
一般有三种方式:
前序遍历, 中序遍历, 后序遍历;
而前,中,后,表示节点和左右子树节点遍历打印的先后顺序
- 前序遍历,对于树中的任意节点来说,先打印该节点,然后再打印左子树,最后打印右子树.
- 中序遍历,对于树中的任意节点来说,先打印左子树,然后再打印该节点,最后打印右子树.
- 后序遍历,对于树中的任意节点来说,先打印左子树,然后再打印右子树,最后打印该节点.
二叉树的前,中,后序遍历其实是一个递归的过程.
比如,前序遍历:
- 先打印根节点
- 然后再递归地打印左子树
- 最后递归地打印右子树
写递归最重要的是推出递推公式,推公式最重要的是解决问题A,解决问题A要假设B和C已解决,然后在看如何用B和C来解决A.
所以这里可以把,前,中,后序遍历的递推公式都写出来.
前序遍历的递推公式:
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
通过递推公式写三种遍历方式的代码
void preOrder(Node* root) {
if (root == null) return;
print root // 此处为伪代码,表示打印root节点
preOrder(root->left);
preOrder(root->right);
}
void inOrder(Node* root) {
if (root == null) return;
inOrder(root->left);
print root // 此处为伪代码,表示打印root节点
inOrder(root->right);
}
void postOrder(Node* root) {
if (root == null) return;
postOrder(root->left);
postOrder(root->right);
print root // 此处为伪代码,表示打印root节点
}
从之前的前,中,后序遍历的顺序图可以看到每个节点最多会被访问两次,
所以遍历操作的时间复杂度跟节点的个数n成正比,那么二叉树遍历的时间复杂度是O(n).
小结
树是非线性表数据结构, 常用的概念: 根节点,叶子节点,父节点,子节点,兄弟节点,节点的高度,深度,层数,和树的高度.
二叉树是树中最常用的,二叉树每个节点最多两个子节点,为左子节点和右子节点.
二叉树中有两种特殊树,为满二叉树和完全二叉树, 满二叉树又是完全二叉树的一种特殊情况。
二叉树既可以用链式存储,也可以用数组顺序存储.
但是数组顺序存储比较适合完全二叉树,其他类型的二叉树用数组存储会比较浪费存储空间.
二叉树最重要的操作就是前,中,后序遍历,遍历的时间复杂度是O(n),需要理解并能用递归代码来实现。