树结点的定义:
public class Tree {
public int data;
//左孩子
public Tree lchild;
//右孩子
public Tree rchild;
}
通过下面的方法对这个二叉树进行插入、查询、删除操作。
插入:
通过循环的方式对二叉树进行结点插入。插入也相当于创建一个二叉查找树。
代码示例:
public void insert(int value) {
//当前节点
Tree current;
//父节点
Tree parent;
//创建新节点
Tree node = new Tree(value);
//如果根节点为空,将新创建的节点当作根节点
if (root == null) {
root = node;
return;
} else {
//把当前节点当作根节点
current = root;
while (current != null) {
//遍历左子树
if (value < current.data) {
parent = current;
current = parent.lchild;
//遍历到叶子节点,插入新结点
if (current == null) {
parent.lchild = node;
return;
}
} else { //遍历右子树
parent = current;
current = parent.rchild;
//遍历到叶子节点,插入新结点
if (current == null) {
parent.rchild = node;
return;
}
}
}
}
}
//方法重载
public void insertValues(int[] value) {
for (int i = 0; i < value.length; i++) {
insert(value[i]);
}
}
总结
1.给定一个数组,根据这个数组在二叉树中进行值的插入。
查找:
因为这是一棵二叉搜索树,所以查找类似于二分查找法。
代码示例:
public boolean search(int value) {
Tree parent;
Tree current;
if (root == null) {
return false;
} else {
current = root;
while (current != null) {
if (value == current.data) { //找到结点返回true
return true;
} else if (value < current.data) {
parent = current;
current = parent.lchild;
if (current == null) {
return false;
}
} else {
parent = current;
current = parent.rchild;
if (current == null) {
return false;
}
}
}
}
return false;
}
总结
- 给定一个值,判断这个值在不在这个二叉树中。
删除:
这里重点说一下删除。二叉树的删除操作比较复杂,因为一个结点可能没有孩子结点,也可能有一个孩子结点,也可能有两个孩子结点,所以删除二叉树的结点需要分情况讨论:
1. 删除结点没有孩子结点是叶子结点;
2. 删除结点只有一个孩子结点;
2.1 删除结点没有右孩子结点;
2.2.删除结点没有左孩子结点;
3. 删除结点左右孩子结点都存在;(有两种办法:a.让删除结点的左子树最大值(也可以理解为中序遍历的直接前继)接替删除的位置;b.让删除结点的右子树最小值(也可以理解为中序遍历的直接后继)接替删除的位置;)。这里采用的是b方法。
注意:每种种情况都需要判断是不是根结点;
接下来看一下具体的删除过程:
1.查找到要删除的结点,并且判断是左结点还是右结点;
代码示例:
//遍历查找删除结点
while (current.data != value) {
parent = current;
//走左子树
if (value < current.data) {
current = current.lchild;
isLeftChild = true;
} else { //走右子树
current = current.rchild;
isLeftChild = false;
}
//说明没有找到删除结点
if (current == null) {
return false;
}
}
2.删除结点没有孩子结点是叶子结点;
如下图:
如果删除是左叶子结点,直接让父结点的左孩子为null;否则有孩子为null;
代码示例:
//1. 删除的是叶子结点
if (current.rchild == null && current.lchild == null) {
//若删除的结点为根结点
if (current == root) {
root = null;
} else { //删除结点为非根节点的叶子结点
if (isLeftChild) {
parent.lchild = null;
} else {
parent.rchild = null;
}
}
}
3.删除结点只有一个孩子结点;
3.1 删除结点没有右孩子结点;
可能会有两种情况:
1. 删除结点是左孩子,并且没有右子树;
2.删除结点是右孩子,并且没有右子树;
**结果:**都是让父结点直接指向删除结点的子结点;
代码示例:
//2.1 若删除的结点没有右子树
if (current.rchild == null) {
if (current == root) {
root = current.lchild;
} else {
if (isLeftChild) {
parent.lchild = current.lchild;
} else {
parent.rchild = current.lchild;
}
}
}
3.2 删除结点没有左孩子结点;
可能会存在两种情况:
1.删除结点是左孩子,并且没有左子树;
2.删除结点是右孩子,并且没有左子树;
**结果:**都是让父结点直接指向删除结点的子结点;
代码示例:
//2.2 若删除的结点没有左子树
if (current.lchild == null) {
if (current == root) {
root = current.rchild;
} else {
if (isLeftChild) {
parent.lchild = current.rchild;
} else {
parent.rchild = current.rchild;
}
}
}
4 删除结点左右孩子结点都存在;
如下图:
假设要删除的结点是4,需要找到右子树的最小值(中序遍历后的直接后继结点)接替4结点的位置。
代码示例:
//找到删除结点中序遍历后的直接后继结点并返回
private Tree succeed(Tree delete) {
//父节点
Tree parent = delete;
//表示删除结点的后继结点
Tree succeed = delete;
//当前遍历的结点
Tree current = delete.rchild;
//遍历到删除结点中序遍历的直接后继结点
while (current != null) {
parent = succeed;
succeed = current;
current = current.lchild;
}
//说明删除的结点的右结点存在左子树
if (succeed != delete.rchild) {
//让后继结点的父节点指向后继结点的右子树
parent.lchild = succeed.rchild;
//将后继结点指向删除结点的右子树,完成替换
succeed.rchild = delete.rchild;
}
return succeed;
}
执行完该方法,删除结点的右子树结构会变成这样:
然后把5结点返回;
执行下面的代码:
代码示例:
//3. 若删除的结点左右子树都存在
if{
//找到删除结点中序遍历后的直接后继结点并返回
Tree succeed = succeed(current);
if (current == root) {
root = succeed;
} else {
if (isLeftChild) {
parent.lchild = succeed;
} else {
parent.rchild = succeed;
}
}
//让后继结点连接到删除结点的左子树
succeed.lchild = current.lchild;
}
首先对删除结点判断,
1.如果是根结点:
则后继结点直接设为根结点,并且让删除结点的左子树连接到该后继结点上;
①找到删除结点4的后继结点5;
②调整树结构并返回;
③让删除结点的左子树直接当作后继结点的左子树;
2.不是根结点,如果删除结点是左孩子,让父结点的左孩子直接为后继结点,否则让右孩子直接为后继结点,最后让删除结点的左子树连接到该后继结点上。
③让父结点指向该后继结点,让删除结点的左子树直接当作后继结点的左子树;
下面看一下是删除二叉树结点的具体的代码,里面有详细的注释:
代码示例:
public boolean delete(int value) {
//父结点
Tree parent = root;
//当前结点
Tree current = root;
//判断被删结点是否是左结点
boolean isLeftChild = true;
//若是根结点直接返回
if (root == null) {
return false;
}
//遍历查找删除结点
while (current.data != value) {
parent = current;
//走左子树
if (value < current.data) {
current = current.lchild;
isLeftChild = true;
} else { //走右子树
current = current.rchild;
isLeftChild = false;
}
//说明没有找到删除结点
if (current == null) {
return false;
}
}
//1. 删除的是叶子结点
if (current.rchild == null && current.lchild == null) {
//若删除的结点为根结点
if (current == root) {
root = null;
} else { //删除结点为非根节点的叶子结点
if (isLeftChild) {
parent.lchild = null;
} else {
parent.rchild = null;
}
}
} else if (current.rchild == null) { //2.1 若删除的结点没有右子树
if (current == root) {
root = current.lchild;
} else {
if (isLeftChild) {
parent.lchild = current.lchild;
} else {
parent.rchild = current.lchild;
}
}
} else if (current.lchild == null) { //2.2 若删除的结点没有左子树
if (current == root) {
root = current.rchild;
} else {
if (isLeftChild) {
parent.lchild = current.rchild;
} else {
parent.rchild = current.rchild;
}
}
} else { //3. 若删除的结点左右子树都存在
//找到删除结点中序遍历后的直接后继结点并返回
Tree succeed = succeed(current);
if (current == root) {
root = succeed;
} else {
if (isLeftChild) {
parent.lchild = succeed;
} else {
parent.rchild = succeed;
}
}
//让后继结点连接到删除结点的左子树
succeed.lchild = current.lchild;
}
return true;
}
//找到删除结点中序遍历后的直接后继结点并返回
private Tree succeed(Tree delete) {
//父节点
Tree parent = delete;
//表示删除结点的后继结点
Tree succeed = delete;
//当前遍历的结点
Tree current = delete.rchild;
//遍历到删除结点中序遍历的直接后继结点
while (current != null) {
parent = succeed;
succeed = current;
current = current.lchild;
}
//说明删除的结点的右结点存在左子树
if (succeed != delete.rchild) {
//让后继结点的父节点指向后继结点的右子树
parent.lchild = succeed.rchild;
//将后继结点指向删除结点的右子树,完成替换
succeed.rchild = delete.rchild;
}
return succeed;
}
注意:里面的root为该类的一个属性。