树的基本概念
树是数据结构中非常重要的一个存在。在Java中对树进行了大量的使用,如TreeMap,HashMap等。并且如MySQL、MongoDB也都使用到了树,那么树到底是什么?
树是由N个节点组成的,每个节点中都会进行数据的存储。当N=0时,被称为空树。在任何一颗树中,有且只有一个根节点,在根节点下可以继续进行扩展,形成互不相交的集合。其中每个集合本身可以理解为是一棵树,他们则被称为子树。
| 节点 | 树中的元素 |
|---|---|
| 节点的度 | 节点拥有子树的个数,二叉树的度不能大于2 |
| 高度 | 叶子节点的高度为1,叶子节点的父节点高度为2,依次类推 |
| 根节点 | 树的顶部节点 |
| 子节点 | 父节点的下层节点 |
| 叶子节点 | 没有子节点的节点,也被称为终端节点、度为0的节点。 |
树的种类也非常多:二叉树、平衡二叉树、红黑树、B树、B+树、哈夫曼树、B*树等等。
二叉树
基本概念
二叉树的特点是树的每个节点最多只能有两个子节点。其中一棵树叫做根的左子树,另一根叫根的右子树。
如果节点数量超过2个,则不能叫做二叉树,而叫多路树。
特点
每个节点最多有两颗子树,所以二叉树中不存在度(该节点孩子的个数)大于2的节点。 左子树和右子树是有顺序的。
即使树中某节点只有一颗子树,也要区分它是左子树还是右子树
特殊的二叉树
满二叉树
在一棵二叉树中。如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树被称为满二叉树。其特点如下:
- 假设深度为K,且含有2^k-1个节点的树
- 叶子只能出现在最后一层,出现在其它层就不能达成平衡
- 非叶子节点的度一定是2
- 在同样深度的二叉树中,满二叉树的节点最多,叶子节点最多
完全二叉树
完全二叉树是一颗特殊的二叉树。假设一个二叉树的高度为h,如果说它是一颗完全二叉树的话,需要满足以下规则:
- 叶子节点只能出现在最下层和次下层
- 最下层的叶子节点集中在树的左部
- 倒数第二层若存在叶子节点,一定在右部连续位置
- 如果节点度为1,那么该节点只有左孩子,即没有右子树
斜树
所有的节点都之后左子树的二叉树叫做左斜树,同理存在右斜树,相当于树结构退化为了链表,查找一个节点的时间复杂度为O(n),查询效率严重降低。
存储结构
二叉树的顺序存储结构就是使用一堆数组存储二叉树中的节点,并且节点的存储位置就是数组的索引。
当二叉树为完全二叉树时,节点数刚好填满数组。
当不为完全二叉树时,采用顺序存储又是什么样子的呢?
其中,^表示数组中此位置没有储存节点,此时可以发现,顺序存储结构中出现了空间浪费的问题,在极端的右斜树极端情况下对应的顺序存储结构。
可以看出,对于这种右斜树极端情况下,采用顺序存储的方式是十分浪费空间的,因此,顺序存储一般适用于完全二叉树。
二叉树遍历
定义
二叉树的遍历是指二叉树的根节点出发,按照某种次序访问二叉树中的所有节点,使得每个节点被访问一次,且仅被访问一次。
二叉树的访问次序可以分为四种:
- 前序遍历
- 中序遍历
- 后序遍历
- 层序遍历
前序遍历
从根节点出发,当第一次到达节点时就会输出节点数据,按照先向左在向右的方向访问。简单理解就是:父节点-->左子树-->右子树
流程:
- 从根节点出发,则第一次到达节点A,输出A
- 继续向左访问,第一次访问节点B,输出B
- 按照同样的规则,输出D,输出H
- 当到达叶子节点H,返回D,此时已经是第二次访问D了,则不输出D。继续向右,输出I
- I为叶子节点,返回D。此时D的左右子树已经访问完毕,则返回B,访问B的右子树,输出E
- 向E的左子树,输出J
- 安装同样的规则,继续输出C、F、G。
最终结果为:ABDHIEJCFG
Java实现前序遍历代码:
public class Traverse {
private static Node root;
private static boolean flag = false;
//数据初始化
static {
root = new Node("A");
root.setLeftNode(new Node("B"));
root.setRightNode(new Node("C"));
root.getLeftNode().setLeftNode(new Node("D"));
root.getLeftNode().setRightNode(new Node("E"));
root.getLeftNode().getLeftNode().setLeftNode(new Node("H"));
root.getLeftNode().getLeftNode().setRightNode(new Node("I"));
root.getLeftNode().getRightNode().setLeftNode(new Node("J"));
root.getRightNode().setLeftNode(new Node("F"));
root.getRightNode().setRightNode(new Node("G"));
}
//前序遍历
public static void prologue(Node root){
if(root == null){
return;
}
if (!flag){
System.out.println(root.getData());
flag = true;
}
//遍历左子树
if (root.getLeftNode() != null){
System.out.println(root.getLeftNode().getData());
prologue(root.getLeftNode());
}
// 遍历右子树
if (root.getRightNode() != null){
System.out.println(root.getRightNode().getData());
prologue(root.getRightNode());
}
}
public static void main(String[] args) {
prologue(root);
}
}
中序遍历
从根节点出发,当第二次到达节点时则输出节点数据,按照先向左再向右的方向访问。简单理解就是:左子树->父节点->右子树
流程如下:
- 从根节点出发,则第一次到达A,不能输出A,继续向左访问,第一次到达B,不能输出B,继续到达D、H都不进行输出。
- 到达H,H左子树为空,则返回到H,此时是第二次访问H,则输出H
- H右子树为空,则返回D,此时第二次到达D,则输出D
- D存在右子树,则向右到达I,I的左子树为空,则返回到I,此时是第二次访问I,输出I
- I的右子树为空,则返回D,此时是第三次到达D,不做输出
- 继续向上,到达B,此时是第二次到达B,则输出B
- 按照同样的规则,后续输出J、E、A、F、C、G。
最终结果为:HDIBJEAFCG
//中序遍历 左 中 右
public static void middle(Node root){
if (root == null){
return;
}
if (root.getLeftNode() != null){
middle(root.getLeftNode());
}
System.out.print(root.getData() + " ");
if (root.getRightNode() != null){
middle(root.getRightNode());
}
}
public static void main(String[] args) {
middle(root);
}
后序遍历
从根节点出发,当第三次到达节点时输出,按照先左后右的方式访问,简单理解就是左子树->右子树->父节点
流程如下:
- 从根节点出发,则第一次到达节点A,不能输出A,继续向左访问,到达BDH
- 到达H,H左子树为空,则返回到H,此时第二次访问H,不能做输出
- 到达H,H右子树为空,则返回到H,此时第三次访问H,输出H,
- H返回D,第二次到达D,不能输出
- 继续访问I,同时I的左右子树均为空,第三次时,输出I
- I返回到D,第三次到达D,输出D,
- 按照同样规则输出J E B F G C A 最终结果为:HIDJEBFGCA
//后序遍历 左 右 中
public static void postSequence(Node root){
if (root == null){
return;
}
if (root.getLeftNode() != null){
postSequence(root.getLeftNode());
}
if (root.getRightNode() != null){
postSequence(root.getRightNode());
}
System.out.print(root.getData()+" ");
}
层序遍历
按照树的层次自上而下的遍历二叉树。 最终结果为:ABCDEFGHIJ
//层序遍历
public static void sequence(Node root){
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
Node treeNode = queue.poll();
System.out.print(treeNode.getData() + " ");
if (treeNode.getLeftNode() != null) {
queue.offer(treeNode.getLeftNode());
}
if (treeNode.getRightNode() != null) {
queue.offer(treeNode.getRightNode());
}
}
}