本教程的内容基本来自于《Java数据结构与算法》
树类型的数据结构是最通用的数据结构之一。
树由节点和边构成,在其顶层只有一个根节点,从根节点向下蔓延到子节点,而子节点又可以继续向下蔓延。
节点用来存放数据,边用来描述两个节点之间的关系,下图就是一种树的结构
二叉树是一种特殊的树形结构,其每个节点最多包含两个子节点,分别称为左子节点和右子节点,含有这样关系的节点称为父节点和子节点。如下图
从图中可以看出,二叉树中的节点可以拥有两个子节点比如A,也可以只有一个子节点比如C,也可以没有子节点比如E(这种情况下称它为叶节点)。
二叉搜索树是一种特殊的二叉树,其中一个节点的左子节点的数据值小于这个节点,右子节点的数据值大于或等于这个节点。如下图
使用链表的思想实现二叉搜索树,每个节点含有两个指针,一个指向左子节点,一个指向右子节点。
1.节点定义
// 定义节点
class Node{
public int iData; //节点数据
public Node leftChild; //左指针
public Node rightChild; //右指针
public void displayNode(){ //打印节点信息
System.out.print("{"+iData+"}");
}
}
2.二叉搜索树初始化
class BinaryTree{
public Node root;
BinaryTree(){
root = null;
}
}
3.查找节点
根据二叉搜索树的特点,查找节点时,只需要判断需要查找节点的数据值与当前节点的数据值之间的大小关系即可。
- 如果相等,表示找到了;
- 如果前者小于后者,说明在当前节点的左边;
- 如果前者大于后者,说明在当前节点的右边。
下图是寻找34这个结点的过程
public Node find(int key){
Node current = root;
while (current != null){
if (current.iData == key)
break;
else if (current.iData > key)
current = current.leftChild;
else
current = current.rightChild;
}
return current;
}
4.插入节点
插入节点和查找节点过程相似,使用两个指针,一个指向当前节点,一个指向当前节点的父节点。
下图是插入节点45的过程。
public void insert(int key){
Node newNode = new Node();
newNode.iData = key;
if (root==null){
root = newNode;
}
else{
Node current = root;
Node parent = root;
while(true){
parent = current;
if (key < current.iData){
current = current.leftChild;
if (current == null){
parent.leftChild = newNode;
return;
}
}
else{
current = current.rightChild;
if (current == null){
parent.rightChild = newNode;
return;
}
}
}
}
}
5.删除节点
删除一个节点比较复杂,因为删除节点后不能破坏二叉搜索树的性质。
删除一个节点需要分三种情况:
- 删除的节点既没有左子节点也没有右子节点
- 删除的节点只有左子节点或者只有右子节点
- 删除的节点既有左子节点又有右子节点
下面分三种情况分别介绍删除节点的操作
5.1 删除的节点既没有左子节点也没有右子节点
对于这种情况,最简单,直接删除该节点即可。下图是删除节点61的过程。
这里61是72的左子节点,所以将72的左指针指向空;如果删除的节点时父节点的右子节点时,将父节点的右指针指向空。
public boolean delete(int key){
Node current = root;
Node parent = root;
boolean isLeftChild = true; //使用标记,判断删除节点是否是父节点的左子节点
// 首先找到需要删除的节点
while (current.iData != key){
parent = current;
if (key < current.iData){
isLeftChild = true;
current = current.leftChild;
}
else{
isLeftChild = false;
current = current.rightChild;
}
if (current==null)
return false;
}
// 既没有左子节点右没有右子节点的情况
if (current.leftChild==null && current.rightChild==null){
if(current==root)
root = null;
else if(isLeftChild)
parent.leftChild = null;
else
parent.rightChild = null;
}
}
5.2 删除的节点只有左子节点或者只有右子节点
这种情况也很简单。
如果删除节点是父节点的左子节点时,只需将父节点的左指针指向当前节点的左子节点或者右子节点即可;
否则,将父节点的右指针指向当前节点的左子节点或者右子节点。
- 下图是删除只有左子节点的节点过程
由于30没有右子节点,且是53的左子节点,所以将53的左指针指向30的左子节点14。
// 只有左子节点的情况
if(current==root)
root = current.leftChild;
else if(isLeftChild)
parent.leftChild = current.leftChild;
else
parent.rightChild = current.leftChild;
- 下图是删除只有右子节点的节点过程
由于72没有左子节点,且是53的右子节点,所以将53的右指针指向72的右子节点84。
// 只有右子节点的情况
if(current==root)
root = current.rightChild;
else if(isLeftChild)
parent.leftChild = current.rightChild;
else
parent.rightChild = current.rightChild;
5.3 删除的节点既有左子节点又有右子节点
这种情况优点复杂,先分析一下,如果把需要删除的节点删除后,那么哪个节点应该继承这个空位呢?
设继承删除节点的节点为 ,删除节点的左边所有节点构成的集合记为 ,右边所有节点构成的集合记为 .
对于集合
中的任意元素
和集合
中的任意元素
必然存在如下关系:
才能使得二叉搜索树的性质不改变。
所以 只能等于 .
再使用下图来说明,比如说删除的节点为30,黄色就是 .
如何找黄色中的最小节点呢?因为左子节点总是最小的,从而以39为根节点一直往左找就可以了。如果39没有左子节点,那么就是最小节点就是39自身。黄色中最小的节点为34,所以
.
寻找后继节点
的代码如下
// 获得删除节点的后继节点
private Node getSuccessor(Node delNode){
Node successorParent = delNode;
Node successor = delNode.rightChild;
Node current = successor;
while (current != null){
successorParent = successor;
successor = current;
current = current.leftChild;
}
if (successor != delNode.rightChild){
successorParent.leftChild = successor.rightChild;
successor.rightChild = delNode.rightChild;
}
return successor;
}
找到后继节点后就简单了
Node successor = getSuccessor(current); //寻找后继节点
if(current==root)
root = successor;
else if(isLeftChild)
parent.leftChild = successor;
else
parent.rightChild = successor;
successor.leftChild = current.leftChild;
6.遍历
遍历树就是根据一种特定的顺序访问树的每一个节点。有三种简单的方法遍历树,分别是前序、中序、后序。
- 前序遍历
- 先访问根节点
- 再遍历左子树
- 最后遍历右子树
public void preOrder(Node localRoot)
{
if(localRoot!=null)
{
System.out.print(localRoot.iData + " ");
preOrder(localRoot.leftChild);
preOrder(localRoot.rightChile);
}
}
- 中序遍历
- 先遍历左子树
- 再访问根节点
- 最后遍历右子树
public void inOrder(Node localRoot)
{
if(localRoot != null)
{
inOrder(localRoot.leftChild);
System.out.print(localRoot.iData + " ");
inOrder(localRoot.rightChild);
}
}
- 后序遍历
- 先遍历左子树
- 在遍历右子树
- 最后访问根节点
public void postOrder(Node localRoot)
{
if(localRoot != null)
{
postOrder(localRoot.leftChild);
postOrder(localRoot.rightChild);
System.out.print(localRoot.iData + " ");
}
}
github完整代码:
https://github.com/gamersover/data_structure_java/blob/master/Tree/BinaryTreeApp.java