二叉树的基础---四种遍历方式的 Java 实现

0. 前言

你们好,我是多选参数的程序锅,一个正在“研究”操做系统、学数据结构和算法以及 Java 的硬核菜鸡。本篇将带来的是二叉树的相关知识,知识提纲如图所示。git

1. 基本介绍

树结构多种多样,可是最经常使用的仍是二叉树。二叉树中每一个节点最多有两个子节点,这两个节点分别是左子节点和右子节点。注意:不要求都有两个子节点,能够只有左子节点,也能够只有右子节点。github


2. 二叉树的存储

2.1. 链式存储法

每一个节点至少有三个字段,其中一个存储数据,另外两个是指向左右子节点的指针。这种存储方式比较经常使用,大部分二叉树代码都是经过这种结构来实现的。web


2.2. 数组存储法

咱们把根节点存储在下标 i=1 的位置,它的左子节点存储在下标为 2 * i 的位置,右子节点存储在下标为 2*i+1 的位置。以此类推,B 节点、C 节点的左右子节点都按照这种规律进行存储,最终以下图所示。算法

综上,若是节点 X 存储在数组中下标为 i 的位置,那么下标为 2*i 的位置存储的就是它的左子节点,下标为 2*i+1 的位置存储的就是它的右子节点。反过来,i/2 的位置存储的就是它的父节点。通常状况下,为了方便计算,根节点会被存储在下标为 1 的位置。数组


经过上述能够看到,针对通常树来讲,使用数组的方式存储树会浪费比较多的存储空间。可是针对下文会提到的满二叉树或者彻底二叉树来讲,数组存储的方式是最节省内存的一种方式。由于数组存储时,不须要再存储额外的左右子节点的指针。微信

3. 二叉树的遍历

二叉树的遍历就是将二叉树中的全部节点遍历打印出来。经典的方法有三种,前序遍历、中序遍历和后序遍历,还能够按层遍历(我的理解的按层遍历其实就是按照图的广度优先遍历方法来进行遍历)。数据结构

前、中、后是根据节点被打印的前后来进行区分的:前序就是先打印节点自己,以后再打印它的左子树,最后打印它的右子树;中序就是先打印节点的左子树,再打印节点自己,最后打印右子树,即把节点放中间的位置输出;后序就是先打印节点的左子树,再打印节点的右子树,最后打印节点自己。以下图所示app


按层遍历相似于图的广度优先遍历,先打印第一层的节点,以后再依次打印第二层的节点,以此类推。数据结构和算法


3.1. 代码实现

实际上,二叉树的前、中、后序遍历是一个递归的过程。好比,前序遍历,其实就是先打印根节点,而后递归遍历左子树,最后递归遍历右子树。递归遍历左右子树其实就跟遍历根节点的方法同样。下面先写出这三者遍历的递推公式:编辑器

前序遍历的递推公式:
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

以后将递推公式转化为代码以下所示:

/**
* 前序遍历
*/

public void preOrder(Node tree) {
if (tree == null) {
return;
}

System.out.print(tree.data + " ");

preOrder(tree.left);

preOrder(tree.right);
}

/**
* 中序遍历
*/

public void inOrder(Node tree) {
if (tree == null) {
return;
}

inOrder(tree.left);

System.out.print(tree.data + " ");

inOrder(tree.right);
}

/**
* 后序遍历
*/

public void postOrder(Node tree) {
if (tree == null) {
return;
}

postOrder(tree.left);

postOrder(tree.right);

System.out.print(tree.data + " ");
}

递归代码的关键,在于写出递推公式。而递推公式的关键在于,A 问题能够被拆解成 B、C 两个问题。假设要解决 A 问题,那么假设 B、C 问题已经解决了。那么在 B、C 已经解决的提早下,看如何利用 B、C 来解决 A 。千万不要模拟计算机一层一层想下去,不然你就会发现你本身都不知道在哪了。

下面是按层遍历的代码,按层遍历须要用到队列的入队和出队等操做。先将根节点放入到队列中,而后循环从队列中取节点(出队),再将该节点的左右子节点入队。出队的顺序就是层次遍历的结果。

/**
* 层次遍历
*/

public void BFSOrder(Node tree) {
if (tree == null) {
return;
}

Queue<Node> queue = new LinkedList<>();
Node temp = null;
queue.offer(tree);
while (!queue.isEmpty()) {
temp = queue.poll();
System.out.print(temp.data + " ");
if (temp.left != null) {
queue.offer(temp.left);
}

if (temp.right != null) {
queue.offer(temp.right);
}
}
}

完整的代码可查看 github 仓库 https://github.com/DawnGuoDev/algos ,这个仓库将主要包含经常使用数据结构及其基本操做的手写实现(Java),也会包含经常使用算法思想经典例题的实现(Java)。在程序锅找到工做以前,这个仓库将会保持更新状态,在此之间学到的关于数据结构和算法的知识或者实现也都会往里面 commit,因此赶忙来 star 哦。

3.2. 时间复杂度

遍历过程当中的次数就是访问全部节点的所需的次数,而每一个节点最多被访问两次,所以遍历的时间复杂度是跟节点的个数 n 成正比的,即遍历的时间复杂度是 O(n)。

4. 特殊的二叉树

4.1. 满二叉树

满二叉树是一种特殊的二叉树,并且仍是彻底二叉树的一种特殊状况。如上图编号 2 的那棵树所示,叶子节点全在底层,除了叶子节点以外,每一个节点都有左右两个子节点。

4.2. 彻底二叉树

彻底二叉树也是一种特殊的二叉树。如上图编号 3 的那棵树所示,叶子节点都在最底下两层,最后一层的叶子节点都靠左排列,而且除了最后一层,其余层的节点个数都达到最大。


彻底二叉树的特征使得它可使用数组就能够很好地存储数据。彻底二叉树要求最后一层的叶子节点靠左排列也是由于如此。

4.2.1. 彻底二叉树的存储

  • 链式存储

    就是上面提到的那种方式。

  • 数组存储

    彻底二叉树使用数组存储时,以下图所示。咱们发现使用数组来存储彻底二叉树是一种很节省内存的方式。这也是彻底二叉树被做为一种特殊树的缘由,也是彻底二叉树要求最后一层的子节点必须都靠左的缘由。

    在讲解堆或者堆排序的时候,堆其实也是一种彻底二叉树,最经常使用的存储方式就是数组


4.3. 其余特殊的二叉树

其余特殊的二叉树还有二叉查找树、平衡二叉查找树等。由于这两种特殊的树涵盖的知识比较多,因此会将其分开进行单独讲解。

5. 巨人的肩膀

  1. 极客时间专栏,王争老师的《数据结构与算法之美》

6. 附 Github

整个系列的代码可查看 github 仓库 https://github.com/DawnGuoDev/algos ,这个仓库将主要包含经常使用数据结构及其基本操做的手写实现(Java),也会包含经常使用算法思想经典例题的实现(Java)。在接下来一年内,这个仓库将会保持更新状态,在此之间学到的关于数据结构和算法的知识或者实现也都会往里面 commit,因此赶忙来 star 哦。


不甘于「本该如此」,多选参数 值得关注




本文分享自微信公众号 - 多选参数(zhouxintalk)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。