二叉树结构一直以来都是数据结构课程中的重点和难点。不论是找工作的笔试面试,还是考研的专业课,二叉树所占的比例都是很大的,而在原始的二叉树的基础上,有不断演化出了很多其他基于二叉树的结构,例如哈夫曼树、红黑树,还有线索二叉树、B+树等等
首先来介绍一下树的基本概念
树的定义
树是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。
树具有的特点有:
(1)每个结点有零个或多个子结点
(2)没有父节点的结点称为根节点
(3)每一个非根结点有且只有一个父节点
(4)除了根结点外,每个子结点可以分为多个不相交的子树。
树的一些专业术语
名字 | 含义 |
---|---|
结点的度 | 结点拥有的子树的数目 |
叶子结点 | 度为0的结点 |
分支结点 | 度不为0的结点 |
树的度 | 树中结点的最大的度 |
层次 | 根结点的层次为1,其余结点的层次等于该结点的双亲结点的层次加1 |
树的高度 | 树中结点的最大层次 |
森林 | 0个或多个不相交的树组成。对森林加上一个根,森林即成为树;删去根,树即成为森林 |
了解完树之后,来了解一下二叉树
二叉树
定义:二叉树是每个结点最多有两个子树的树结构
可以有五种形态:可以为空,可以有根(没有左右子树),有根(有左子树),有根(右子树),有根(同时有左右子树)。
二叉树的一些性质和常见公式
性质1:二叉树第i层上的结点数目最多为2i-1(i>=1)
性质2:深度为k的二叉树至多有2k-1个结点(k>=1)
性质3:包含n个结点的二叉树的高度至少为(log2n)+1
性质4:在任意一棵二叉树中,若终端结点的个数为n0,度为2的结点数为n2,则n0=n2+1
所以我们根据性质4可知
在任意一棵二叉树中,这些节点的数量,之间存在如下关系
1.n2 = n0 - 1:即度为2的节点总比度为0的节点少1个,也就是说,在任意二叉树中,同时具有左右孩子的节点,总比叶子结点的数量少一个
2.从上面的公式我们能够推导出来:n总 = n0 + n1 + n2 = 2n0 + n1 - 1 = n1 + 2n2 + 1,且n1 = n总 - n0 - n2 = n总 - 2n0 + 1 = n总 - 2n2 - 1
所以,只要我们知道一个二叉树中总的节点个数以及度为0或者度为2的节点数量,就能够推导出其他度的节点的数量
常见的几种二叉树的类型
满二叉树、完全二叉树和二叉查找树
1、满二叉树
定义:高度为h,并且由2h-1个结点组成的二叉树,称为满二叉树
也就是说除了最后一层,每个结点都有左右子节点
完全二叉树
定义:一棵二叉树中,只有最下面两层结点的度可以小于2,并且最下层的叶结点集中在靠左的若干位置上,这样的二叉树称为完全二叉树。
特点:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。显然,一棵满二叉树必定是一棵完全二叉树,而完全二叉树未必是满二叉树。
二叉查找树
定义:二叉查找树又被称为二叉搜索树。设x为二叉查找树中的一个结点,x结点包含关键字key,结点x的key值计为key[x]。如果y是x的左子树中的一个结点,则key[y]<=key[x];如果y是x的右子树的一个结点,则key[y]>=key[x]
接下来我们来讲一下二叉树的遍历
广度优先遍历
对一棵二叉树进行广度优先遍历,就是对一棵二叉树中所有的节点,按照层次从上到下、每一层中节点从左到右的顺序,将二叉树中的所有节点进行遍历的操作
广度优先遍历方式只有一种,那就是程序遍历,而二叉树的程序遍历操作,需要依赖于队列结构
所以接下来的代码是依赖队列来实现的遍历
/**
* 广度优先breadth-first traversal(BFS)
*
* @author 80769
*
*/
public class Bfs {
public void bfs(Node root) {
public void print(TreeNode node) {
LinkedList<TreeNode> list = new LinkedList<TreeNode>();
// 首先将整个二叉树的根节点入队列
list.offer(node);
while (!list.isEmpty()) {// 如果队列中不再具有节点,说明整个二叉树遍历完成
// 首先将队列头节点出队列
TreeNode node1 = list.pop();
if (node1 != null) {
System.out.print(node1.data);
// 如果队列头结点左孩子不是空,则将左孩子入队列
if (node1.left != null) {
list.offer(node1.left);
}
// 如果队列头节点右孩子不是空,则将右孩子入队列
if (node.right != null) {
list.offer(node1.right);
}
}
}
}
}
深度优先遍历
深度优先遍历是二叉树遍历方式中比较重点的一种遍历方式,可以分为先序遍历、中序遍历和后序遍历3种遍历序列
(1)先序:也就是根左右
/**
* 先序PreorderTraversal 用递归的方式
*
* @author xhh
*
*/
public class PreTraversal {
public void preTraversal(Node root) {
// 首先输出根节点
System.out.println(root.data);
// 如果左孩子不为空,则先序遍历左子树
if (root.leftNode != null) {
preTraversal(root.leftNode);
}
// 如果右孩子不为空,则先序遍历右子树
if (root.rightNode != null) {
preTraversal(root.rightNode);
}
}
}
(2) 中序(左根右)
/**
* 中序InorderTraversal 用递归的方式
*
* @author xhh
*
*/
public class InTraversal {
public void inTraversal(Node root) {
// 如果左孩子不为空,则中序遍历左子树
if (root.leftNode != null) {
inTraversal(root.leftNode);
}
// 访问根节点
System.out.println(root.data);
// 如果右孩子不为空,则中序遍历右子树
if (root.rightNode != null) {
inTraversal(root.rightNode);
}
}
}
(3)后序(左右根)
/**
* 后序PostorderTraversal 用递归的方式
*
* @author xhh
*
*/
public class PostTraversal {
public void postTraversal(Node root) {
// 如果左孩子不为空,则中序遍历左子树
if (root.leftNode != null) {
postTraversal(root.leftNode);
}
// 如果右孩子不为空,则中序遍历右子树
if (root.rightNode != null) {
postTraversal(root.rightNode);
}
// 访问根节点
System.out.println(root.data);
}
}