这里面的每个元素叫做“节点”,用来连接相邻节点之间的关系,叫做“父子关系”。
如图所示,
A节点就是B节点的父节点,B节点是A节点的子节点;
B,C,D这三个节点的父节点是同一个节点,所以它们之间互称为兄弟节点;
没有父节点的节点叫做根节点,即图中的节点E。
没有子节点的节点叫做叶子节点或者叶节点,即图中的G,H,I,J,K,L;
关于“树”,还有三个比较相似的概念:高度(Height)/深度(depth)/层(Level)。
高度就是从下往上度量,从最底层开始计数,并且计数的起点是0;
深度是从上往下度量,从根节点开始度量,并且计数起点也是0;
层数跟深度的计算类似,不过,计数起点是1,也就是说根节点位于第一层。
二叉树
树结构多种多样,不过最常用的还是二叉树。
二叉树,每个节点最多有两个叉,也就是两个子节点,分别是左子节点和右子节点。如上图1,2,3所示。
编号2的二叉树中,叶子节点全都在最底层,除了叶子节点之外,每个节点都有左右两个子节点,这种二叉树叫做满二叉树;
编号3的二叉树中,叶子节点都在最底下两层,最后一层的叶子节点都靠左排列,并且除了最后一层,其他层的节点个数都要达到最大,这种二叉树叫做完全二叉树;
要理解完全二叉树的由来,我们需要先了解,如何表示或者存储一颗二叉树?
- 基于指针的二叉链式存储法。
从图中可以看到,每个节点有三个字段,其中一个存储数据,另外两个是指向左右子节点的指针。我们只要拎住根节点,就可以通过左右子节点的指针,把整棵树都串起来。大部分二叉树代码都是通过这种结构来实现的。 - 基于数组的顺序存储法。
如上图所示,如果节点X存储在数组中下标为i的位置,下标2i的位置存储的就是左子节点,下标为2i+1的位置存储的就是右子节点,下标为i/2的位置存储的就是它的父节点。通过这种方式,我们只要知道根节点存储的位置,就可以通过下标计算,把整棵树都串起来。
以上的例子是一颗完全二叉树,所以仅仅浪费了一个下标为0的存储位置。如果是非完全二叉树,其实会浪费比较多的数组存储空间,比如:
由此可见,对于一颗完全二叉树,用数组存储是最节省内存的一种方式。
二叉树的遍历
经典的方法有三种:前序遍历/中序遍历/后序遍历。
- 前序遍历是指,对于树中的任意节点来说,先打印这个节点,然后再打印它的左字数,最后打印它的右子数。
- 中序遍历是指,…,先打印它的左子树,然后打印它本身,最后打印它的右子树。
- 后序遍历是指,…,先打印它的左子树,然后打印它的右子树,最后打印这个节点本身。
实际上,二叉树的前/中/后序遍历就是一个递归的过程。
从上面前/中/后序遍历的顺序图可以看出,每个节点最多会被访问两次,所以遍历操作的时间复杂度,跟节点的个数n成正比,即二叉树遍历的时间复杂度是O(n)。