目录
树
树(tree)是一种抽象数据类型(ADT),用来模拟具有树状结构性质的数据集合。它是由n(n>=0)个有限节点组成一个具有层次关系的集合。节点一般代表一些实体,在java中节点一般代表对象。连接节点的线称为边,一般从一个节点到另一个节点的唯一方法就是沿着一条顺着有边的道路前进,在Java中边通常表示引用。
特点:每个节点有零个或多个子节点;没有父节点的节点称为根节点;每一个非根节点有且只有一个父节点;除了根节点外,每个子节点可以分为多个不相交的子树。
相关术语:
- 路径:顺着节点的边从一个节点走到另一个节点,所经过的节点的顺序排列就称为“路径”。
- 根:树顶端的节点称为根。一棵树只有一个根,如果要把一个节点和边的集合称为树,那么从根到其他任何一个节点都必须有且只有一条路径。A是根节点。
- 父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
- 子节点:一个节点含有的子树的根节点称为该节点的子节点;
- 兄弟节点:具有相同父节点的节点互称为兄弟节点;
- 叶子节点:度为0的节点称为叶节点;
- 树的度:一棵树中,最大的节点的度称为树的度;
- 节点的度:一个节点含有的子树的个数称为该节点的度;
- 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
- 树的高度或深度:树中节点的最大层次;
二叉树
二叉树:每个节点最多含有两个子树的树称为二叉树。
二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树,是指具有下列性质的二叉树:
- 若左子树不空,则左子树上所有节点的值均小于它的根节点的值;
- 若右子树不空,则右子树上所有节点的值均大于它的根节点的值;
- 左、右子树也分别为二叉排序树;
- 没有键值相等的节点。
Node类:
package yrwan09;
/**
* 二叉树结点
*
* @author Wyran
*
*/
public class Node {
public int data;// 节点数据
public Node leftChild;// 左子节点
public Node rightChild;// 右子节点
public Node(int data) {
this.data = data;
}
}
二叉树的方法有:插入节点、查找节点、删除节点、前序遍历、中序遍历、后序遍历等。
查找节点,要从根节点开始遍历,待查找的值比当前节点值大,则搜索右子树;待查找的值小于当前节点值,则搜索左子树;待查找的值等于当前节点值,则停止搜索。
插入节点,要先找到插入的位置,待插入的节点要从根节点开始进行比较,小于根节点则与根节点左子树比较,反之则与右子树比较,直到左子树为空或右子树为空,则插入到相应为空的位置,在比较的过程中要注意保存父节点的信息以及待插入的位置是父节点的左子树还是右子树,才能插入到正确的位置。
删除节点,是二叉搜索树中最复杂的操作,删除的节点有三种情况:1、该节点是叶节点 2、该节点有一个子节点 3、该节点有两个子节点。前两种比较简单,第三种却很复杂。
遍历树,是根据一种特定的顺序访问树的每一个节点。比较常用的有前序遍历,中序遍历和后序遍历。而二叉搜索树最常用的是中序遍历。
二叉树遍历的应用:
- 前序遍历:根节点——》左子树——》右子树
- 中序遍历:左子树——》根节点——》右子树
- 后序遍历:左子树——》右子树——》根节点
(1)前序遍历:可以用来实现目录结构的显示。
(2)中序遍历:可以用来做表达式树,在编译器底层实现的时候用户可以实现基本的加减乘除,比如 a*b+c。
(3)后序遍历可以用来实现计算目录内的文件占用的数据大小。
表达式求值也可以使用后缀表达式。后缀表达式求值比中缀表达式更方便,可以先把中缀表达式变成后缀表达式,然后再根据后缀表达式求值。
1.对于前序遍历,可以用来实现输出某个文件夹下所有文件名称(可以有子文件夹),就是目录结构的显示。
输出文件名称的过程如下:
如果是文件夹,先输出文件夹名,然后再依次输出该文件夹下的所有文件(包括子文件夹),如果有子文件夹,则再进入该子文件夹,输出该子文件夹下的所有文件名。这是一个典型的先序遍历过程。
2.对于后序遍历,可以用来统计某个文件夹的大小(该文件夹下所有文件的大小)
统计文件夹的大小过程如下:
若要知道某文件夹的大小,必须先知道该文件夹下所有文件的大小,如果有子文件夹,若要知道该子文件夹大小,必须先知道子文件夹所有文件的大小。这是一个典型的后序遍历过程。
package yrwan09;
import java.util.ArrayDeque;
import java.util.Queue;
/**
* 二叉树:插入、查找、删除、遍历
*
* @author Wyran
*
*/
public class Tree {
public Node root;// 根节点
/**
* 插入节点:从根节点开始查找相应节点,这个节点作为新插入节点的父节点。
*
* @param value
*/
public void insert(int value) {
Node newNode = new Node(value);
if (root == null) {
root = newNode;
return;
} else {
Node current = root;
Node parentNode = null;
while (true) {
parentNode = current;
if (value < current.data) {
current = current.leftChild;
if (current == null) {
parentNode.leftChild = newNode;
return;
}
} else {
current = current.rightChild;
if (current == null) {
parentNode.rightChild = newNode;
return;
}
}
}
}
}
/**
* 查找结点:从根节点开始遍历
* ①、查找值小于当前节点值,则搜索左子树;
* ②、查找值大于当前节点值,则搜索右子树;
* ③、查找值等于当前节点值,停止搜索(终止条件);
*
* @param value
* @return
*/
public Node find(int value) {
Node current = root;
while (current != null) {
if (current.data > value) {// 当前值比查找值大,搜索左子树
current = current.leftChild;
} else if (current.data < value) {// 当前值比查找值小,搜索右子树
current = current.rightChild;
} else {
return current;
}
}
return null;// 没找到,则返回null
}
/**
* 前序遍历:根节点——>左子树——>右子树
*
* @param node
*/
public void preOrder(Node current) {
if (current != null) {
System.out.print(current.data + " ");
preOrder(current.leftChild);
preOrder(current.rightChild);
}
}
/**
* 中序遍历:左子树——>根节点——>右子树
*
* @param node
*/
public void inOrder(Node current) {
if (current != null) {
inOrder(current.leftChild);
System.out.print(current.data + " ");
inOrder(current.rightChild);
}
}
/**
* 后序遍历:左子树——>右子树——>根节点
*
* @param node
*/
public void postOrder(Node current) {
if (current != null) {
postOrder(current.leftChild);
postOrder(current.rightChild);
System.out.print(current.data + " ");
}
}
/**
* 层序遍历
*
* @param current
*/
public void bfsOrder(Node current) {
if(current != null){
Queue<Node> queue = new ArrayDeque<>();
queue.offer(current);
while(!queue.isEmpty()){
current = queue.poll();
System.out.print(current.data + " ");
if(current.leftChild != null){
queue.offer(current.leftChild);
}
if(current.rightChild != null){
queue.offer(current.rightChild);
}
}
}
}
/**
* 删除结点
*
* @param value
*/
public boolean delete(int value) {
Node current = root;// 待删除节点
Node parentNode = root;
boolean isLeftChild = false;// 判断待删除节点是左孩子还是右孩子
while (current.data != value) {
parentNode = current;
if (current.data > value) {// 当前值比查找值大,搜索左子树
current = current.leftChild;
isLeftChild = true;
} else {// 当前值比查找值小,搜索右子树
current = current.rightChild;
isLeftChild = false;
}
if (current == null) {// 待删除值不存在
return false;
}
}
// ①该节点没有子节点,是叶子结点
if (current.leftChild == null && current.rightChild == null) {
if (current == root) {
root = null;
} else if (isLeftChild) {
parentNode.leftChild = null;
} else {
parentNode.rightChild = null;
}
} else if (current.rightChild == null && current.leftChild != null) {// ②该节点只有一个右子节点
if (current == root) {
root = current.leftChild;
} else if (isLeftChild) {
parentNode.leftChild = current.leftChild;
} else {
parentNode.rightChild = current.leftChild;
}
} else if (current.leftChild == null && current.rightChild != null) {// ②该节点只有一个左子节点
if (current == root) {
root = current.rightChild;
} else if (isLeftChild) {
parentNode.leftChild = current.rightChild;
} else {
parentNode.rightChild = current.rightChild;
}
} else {// ③该节点有两个子节点
Node successor = getSuccessor(current);
if (current == root) {
root = successor;
} else if (isLeftChild) {
parentNode.leftChild = successor;
} else {
parentNode.rightChild = successor;
}
successor.leftChild = current.leftChild;
}
return true;
}
/**
* 查找待删除节点的中序后继节点
*
* @param delNode 待删除节点
* @return 中序后继节点
*/
public Node getSuccessor(Node delNode) {
Node successor = delNode;// 中序后继节点
Node successorParent = delNode;// 中序后继节点的父节点
Node current = delNode.rightChild;// 待删除节点的右子节点开始
while (current != null) {// 向左子树遍历到一个没有左孩子的节点
successorParent = successor;
successor = current;
current = current.leftChild;
}
// 如果中序后继节点不是待删除节点的右孩子 把待移动的子树全部移动完
if (successor != delNode.rightChild) {
successorParent.leftChild = successor.rightChild;
successor.rightChild = delNode.rightChild;
}
return successor;
}
}