共计 1804 个字符,预计需要花费 5 分钟才能阅读完成。
微信公众号:小超说
这是 查找算法 系列文章的第一篇,助你快速入门二叉树
什么是树(Tree)?
我们首先来看一些图片:
其中,第一、二、四个都是树,第三个不是。树的特点很明显吧!
其中每个元素叫做“节点”;用来连接相邻节点之间的关系,我们称之为“父子关系”。例如在图一中,A 节点是 B 节点的 父节点 ,B 节点是 A 节点的 子结点 , 同时,B 节点和 Q 节点是同一个父节点 A 的子节点,所以它们之间互相成为 兄弟节点 。我们把没有父节点的节点称为 根节点 ,也就是图一中的 A 节点。我们把没有子节点的节点称为 叶子节点,比如图一中的 D、E、F、G 节点。这些概念都是显而易见,但却是最基本的东西。
二叉树
二叉树,自然就是每个节点最多有两个分支,即两个子节点的一种树,两个分支分别称为 左子树 和右子树 。还是那上面那张图举例,图一、图二和图四都是二叉树,因为它们每个节点都最多含有两个子节点。其中,图一又称为 满二叉树 ,图四又称为 完全二叉树 。而之所以出现 完全二叉树 的概念,其实是基于二叉树的物理存储方式。
二叉树的存储方法
- 基于链表的链式存储法
- 基于数组的顺序存储法
链式存储法:
我们为每个节点创建一个Node 对象
:
class Node{
int data;
Node left,right;
}
每个节点都是一个 Node 对象,它包含我们所需要存储的数据,指向左子节点的引用,直向右子节点的引用,就像链表一样将整个树串起来。如果该节点没有左子节点,则 Node.left==null
或者Node.right==null
.
顺序存储法:
我们把根节点储存在下标为 i=1
的位置,那么左子节点储存在 2*i=2
的位置,右子节点储存在下标为 2*i+1=2
的位置。依此类推,完成树的存储。借助下标运算,我们可以很轻松的从父节点跳转到左子节点和右子节点或者从任意一个子节点找到它的父节点。如果 X 的位置为 i,则它的两个子节点的下标分别为 2i
和2i+1
, 它的父节点的位置为i/2
(这里结果向下取整)。
具体如下图所示:可以发现,只有完全二叉树存储的效率才最高,最省内存
二叉树的遍历
二叉树的遍历操作主要有三种
- 前序遍历
- 中序遍历
- 后序遍历
前序遍历是指,对于树中的任意节点来说,先打印这个节点,然后再打印它的左子树,最后打印它的右子树。
中序遍历是指,对于树中的任意节点来说,先打印它的左子树,然后再打印它本身,最后打印它的右子树。
后序遍历是指,对于树中的任意节点来说,先打印它的左子树,然后再打印它的右子树,最后打印这个节点本身。
注意,这其中有点 递归 的味道
还是以刚才的图为例:
- 前序遍历:A->B->D->E->C->F
- 中序遍历:D->B->E->A->F->C
- 后序遍历:D->E->B->F->C->A
具体的代码实现(写出递归即可):
public void preOrder(Node root){if(root==null) return;
System.out.println(root.data);// 打印 root 节点的值
preOrder(root.left);
preOrder(root.right);
}
public void inOrder(Node root){if(root==null) return;
inOrder(root.left);
Systrm.out.println(root.data);
inOrder(root.right);
}
public void inOrder(Node root){if(root==null) return;
inOrder(root.left)
inOrder(root.right);
Systrm.out.println(root.data);
}
二叉树遍历的时间复杂度是 O(n), 这是因为每个节点最多会被访问两次,(递归时函数进栈和出栈),所以遍历操作的访问次数跟节点的个数 n 成正比,也就是说二叉树遍历的时间复杂度是 O(n)。
展望
经过上面的介绍,我们已经大致对二叉树有了一个基本的认识,那么,二叉树存在的意义是什么呢?我们可以基于这种数据结构设计出哪些高效的算法呢?下一次我们将介绍 二叉查找树(Binary Search Tree),我们将定义一种数据结构并维护它的性质。
题外话:对于算法初学者,推荐一本十分 nice 的书籍 《算法第四版》,里面各种配图十分详细。如果你需要电子版文件,后台回复 算法 4 即可获得下载链接。后台回复 算法 01 送你一份 算法与数据结构思维导图。最后,希望我们一起进步,一起成长!