查找算法系列文(一)一文入门二叉树

微信公众号:小超说

这是查找算法系列文章的第一篇,助你快速入门二叉树

什么是树(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,则它的两个子节点的下标分别为2i2i+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 送你一份 算法与数据结构思维导图。最后,希望我们一起进步,一起成长!