目录
树结构
树结构顾名思义,是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看 起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下特点:
- 有一个特殊的节点,称为根节点,根节点没有前驱节点
- 除根节点外,其余节点被分成M(M > 0)个互不相交的集合T1、T2、......、Tm,其中每一个集合 Ti (1 <= i <= m) 又是一棵与树类似的子树。每棵子树的根节点有且只有一个前驱,可以有0个或多个后继
- 树是递归定义的。
树的相关术语
- 节点:树中每个单元称为节点,如上图:A、B都是节点
- 节点的度:该节点含有的子树的个数就称为节点的度;如上图A节点的度为6,J节点的度为2
- 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
- 叶子节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I...等节点为叶节点
- 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
- 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
- 根结点:一棵树中,没有双亲结点的结点;如上图:A
- 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
- 树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
为何要有树结构呢?在现实生活中,树形结构比比皆是,例如图书馆中查找书籍时会根据书籍类型来检索;电脑中的文件系统也是一个树结构,查找时根据文件夹去查找特定文件,时间复杂度实际上就是文件树的深度logN,那如果所有文件按照逻辑上连续,成一条直线排列,检索时就要依次遍历了。因为,使用树结构是因为它可以高效的进行查找和搜索,它是天然的查找语句。
二叉树
概念:一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉 树组成。
二叉树的特点: 1. 每个结点最多有两棵子树,即二叉树不存在度大于 2 的结点。 2. 二叉树的子树有左右之分,其子树的次序不能颠倒,因此二叉树是有序树。
以下是几种特殊的二叉树形态:
两种特殊的二叉树
1. 满二叉树: 一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果 一个二叉树的层数为K,且结点总数是 2^k -1,则它就是满二叉树。
2. 完全二叉树: 完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n 个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全 二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
二叉树的性质:
- 一个二叉树高度为k时,它最多有 2^k - 1个节点(最多的情况就是满二叉树)
- 在层次为 k 层时,该层最多有 2^ (k-1)个节点
- 边长 = 节点个数 - 1
- 对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1
- 具有n个结点的完全二叉树的深度k为 log2 ^ ( n+1 )上取整
- 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为k 的结点有:父节点编号为k,则左子树为2k +1,右子树为2k+2;
- 若父节点编号从1开始编号,对于节点k来说,其左子树为2k,右子树为2k+1
- 无论编号是从0开始还是1开始,k节点的父节点都是 k/2
二叉树的存储
叉树的存储结构分为:
- 顺序存储:将二叉树采用数组方式存储,这种结构只能存储完全二叉树,将在之后的堆来介绍
- 类似于链表的链式存储,也就是引用的方式存储
下面是两种引用方式存储的示例:
//左右孩子表示法,通常使用这种方法来存储二叉树的节点
public class Node<E> {
E val;
Node lrft;
Node right;
}
//平衡二叉树常用
public class Node<E> {
E val;
Node lrft;
Node right;
//父节点
Node parent;
}
二叉树的遍历
1. 前序遍历preOrder : 根左右
先访问根节点,再递归左子树,递归右子树
2.中序遍历inOrder:左根右
先递归访问左子树,返回访问根节点,最后递归访问右子树
3.后序遍历postOrder:左右根
先递归访问左子树,再递归访问右子树,最后访问根节点
4.层序遍历levelOrfer:按照二叉树的层次一层层访问节点,现在再右
写出下图的四种遍历结果:
在写前三种遍历时,借助栈这个结构能确保做到不重不漏,以中序遍历为例:
以此类推,前序遍历第一次访问根节点时输出;中序在第二次访问输出;后序在第三次访问输出
根据遍历的结果我们能推出:前序遍历第一个节点就是根节点,而后序遍历的最后一个节点是根节点,中序遍历时根节点的左边是左子树,右边是右子树
使用递归实现前中后序的遍历
以上图为例实现代码:
//节点类
public class TreeNode{
char val;
TreeNode left;
TreeNode right;
public TreeNode(char val){
this.val = val;
}
}
public class MyBinTree {
//先序遍历
//传入一个二叉树的根节点,就能按照先序遍历的方式输出节点
public static void preOder(TreeNode root){
if(root == null){
return;
}
System.out.print(root.val +" ");
//递归访问左树
preOder(root.left);
//递归访问右树
preOder(root.right);
}
//中序遍历
public static void inOrder(TreeNode root){
if (root == null ){
return;
}
//先递归访问左子树
inOrder(root.left);
System.out.print(root.val+" ");
inOrder(root.right);
}
//后序遍历
public static void postOrder(TreeNode root){
if(root == null){
return;
}
postOrder(root.left);
postOrder(root.right);
System.out.print(root.val+" ");
}
//创建一个二叉树,返回根节点
public static TreeNode build(){
TreeNode nodeA = new TreeNode('A');
TreeNode nodeB = new TreeNode('B');
TreeNode nodeC = new TreeNode('C');
TreeNode nodeD = new TreeNode('D');
TreeNode nodeE = new TreeNode('E');
TreeNode nodeF = new TreeNode('F');
TreeNode nodeG = new TreeNode('G');
TreeNode nodeH = new TreeNode('H');
nodeA.left = nodeB;
nodeA.right = nodeC;
nodeB.left = nodeD;
nodeB.right = nodeE;
nodeE.right = nodeH;
nodeC.left = nodeF;
nodeC.right = nodeG;
return nodeA;
}
public static void main(String[] args) {
TreeNode root = build();
System.out.print("前序遍历的结果为:");
preOder(root);
System.out.println();
System.out.print("中序遍历的结果为:");
inOrder(root);
System.out.println();
System.out.print("后序遍历的结果为:");
postOrder(root);
}
}
//输出:
前序遍历的结果为:A B D E H C F G
中序遍历的结果为:D B E H A F C G
后序遍历的结果为:D H E B F G C A