1 二叉搜索树(BSTree)的概念
(1)二叉搜索树,也称二叉排序树、有序二叉树(Ordered Binary Tree)、排序二叉树(Sorted Binary Tree),是指一棵空树或者具有下列性质的二叉树:
- 左子树上所有结点的值均小于它的根结点的值;
- 右子树上所有结点的值均大于它的根结点的值;
- 以此类推:左、右子树也分别为二叉查找树。 (这就是 重复性!)
(2)中序遍历结果:升序排列;一般来说我们会把树变得有序,这样才有树存在的意义,否则就跟普通链表一样了。普通的没有任何状态的树,查找节点需要遍历整棵树,时间复杂度是O(n)。
2 二叉搜索树的插入
2.1 搜索
插入之前我们先来说说它的搜索,像上图这样的一棵二叉搜索树,我们要查找某一个元素是很简单的。因为它的节点分布是有规律的,所以查找一棵元素只需要如下的步骤就可以了:
2.2 插入
由于二叉搜索树的特殊性质确定了二叉搜索树中每个元素只可能出现一次,所以在插入的过程中如果发现这个元素已经存在于二叉搜索树中,就不进行插入。否则就查找合适的位置进行插入。
2.2.1 第一种情况:root为空
直接插入,return true;
2.2.2 第一种情况:要插入的元素已经存在
如上面所说,如果在二叉搜索树中已经存在该元素,则不再进行插入,直接return false;
2.2.3 第三种情况:能够找到合适位置
3 二叉搜索树的删除
对于二叉搜索树的删除操作,主要是要理解其中的几种情况,写起来还是比较简单的。当然一开始还是需要判断要删除的节点是否存在于我们的树中,如果要删除的元素都不在树中,就直接返回false;否则,再分为以下四种情况来进行分析:
- 要删除的节点无左右孩子;
- 要删除的节点只有左孩子;
- 要删除的节点只有右孩子;
- 要删除的节点有左、右孩子。
3.1 第一种情况:删除没有子节点的节点
对于第一种情况,我们完全可以把它归为第二或者第三种情况,就不用再单独写一部分代码进行处理;
3.2 第二种情况:删除有一个子节点的节点
3.2.1 如果要删除的节点只有左孩子,那么就让该节点的父亲结点指向该节点的左孩子,然后删除该节点,返回true;
3.2.2 如果要删除的节点只有右孩子,那么就让该节点的父亲结点指向该节点的右孩子,然后删除该节点,返回true;
对于上面这两种情况我们还应该在之前进行一个判断,就是判断这个节点是否是根节点,如果是根节点的话,就直接让根节点指向这个节点的左孩子或右孩子,然后删除这个节点。
3.3 第三种情况: 删除有两个子节点的节点,即左右子节点都非空
(1)找到该节点的右子树中的最左孩子(也就是右子树中序遍历的第一个节点,分两种情况)
- 此节点是有右子树:
- 当这个节点没有右子树的情况下,即node.rchild == null,如果这个节点的父节点的左子树与这个节点相同的话,那么就说明这个父节点就是后续节点了
(2)把它的值和要删除的节点的值进行交换;
(3)然后删除这个节点即相当于把我们想删除的节点删除了,返回true。
4 参考链接
5 源码
package Tree;
import java.util.ArrayList;
import java.util.List;
public class BinarySearchTree {
private TreeNode root = null;// 树的根节点
// 用于保存节点的列表
private static List<TreeNode> nodeList = new ArrayList<TreeNode>();
private class TreeNode {
private int key;// 节点关键字
private TreeNode parent;
private TreeNode lchild;
private TreeNode rchild;
public TreeNode(int key, TreeNode parent, TreeNode lchild,
TreeNode rchild) {
this.key = key;
this.parent = parent;
this.lchild = lchild;
this.rchild = rchild;
}
public String toString() {
String lKey = (lchild == null) ? "" : String.valueOf(lchild.key);
String rKey = (rchild == null) ? "" : String.valueOf(rchild.key);
return "(" + lKey + "<-" + key + "->" + rKey + ")";
}
}
/**
* 判断是否为空
*/
public boolean isEmpty() {
if (root == null) {
return true;
} else {
return false;
}
}
/**
* 如果树是空的情况下就抛出异常
*/
public void TreeEmpty() throws Exception {
if (isEmpty()) {
throw new Exception("这棵树是空树!");
}
}
/**
* 插入操作
*
* @param key
*/
public void insert(int key) {
TreeNode parentNode = null;
TreeNode newNode = new TreeNode(key, null, null, null);
TreeNode pNode = root;
if (root == null) {
root = newNode;
return;
}
while (pNode != null) {
parentNode = pNode;
if (key > pNode.key) {
pNode = pNode.rchild;
} else if (key < pNode.key) {
pNode = pNode.lchild;
} else {
// 树中已经存在此值,无需再次插入
return;
}
}
if (key > parentNode.key) {
parentNode.rchild = newNode;
newNode.parent = parentNode;
} else if (key < parentNode.key) {
parentNode.lchild = newNode;
newNode.parent = parentNode;
}
}
/**
* 搜索关键字
*
* @param key
* @return
*/
public TreeNode search(int key) {
TreeNode pNode = root;
while (pNode != null) {
if (key == pNode.key) {
return pNode;
} else if (key > pNode.key) {
pNode = pNode.rchild;
} else if (key < pNode.key) {
pNode = pNode.lchild;
}
}
return null;// 如果没有搜索到结果那么就只能返回空值了
}
/**
* 获取二叉树的最小关键字节点
*
* @param node
* @return
* @throws Exception
*/
public TreeNode minElemNode(TreeNode node) throws Exception {
if (node == null) {
throw new Exception("此树为空树!");
}
TreeNode pNode = node;
while (pNode.lchild != null) {
pNode = pNode.lchild;
}
return pNode;
}
/**
* 获取二叉树的最大关键字节点
*
* @param node
* @return
* @throws Exception
*/
public TreeNode maxElemNode(TreeNode node) throws Exception {
if (node == null) {
throw new Exception("此树为空树!");
}
TreeNode pNode = node;
while (pNode.rchild != null) {
pNode = pNode.rchild;
}
return pNode;
}
/**
* 获取给定节点在中序遍历下的后续第一个节点
*
* @param node
* @return
* @throws Exception
*/
public TreeNode successor(TreeNode node) throws Exception {
if (node == null) {
throw new Exception("此树为空树!");
}
// 分两种情况考虑,此节点是否有右子树
// 当这个节点有右子树的情况下,那么右子树的最小关键字节点就是这个节点的后续节点
if (node.rchild != null) {
return minElemNode(node.rchild);
}
// 当这个节点没有右子树的情况下,即 node.rchild == null
// 如果这个节点的父节点的左子树 与 这个节点相同的话,那么就说明这个父节点就是后续节点了
// 难道这里还需要进行两次if语句吗?不需要了,这里用一个while循环就可以了
TreeNode parentNode = node.parent;
while (parentNode != null && parentNode.rchild == node) {
node = parentNode;
parentNode = parentNode.parent;
}
return parentNode;
}
/**
* 获取给定节点在中序遍历下的前趋结
*
* @param node
* @return
* @throws Exception
*/
public TreeNode precessor(TreeNode node) throws Exception {
// 查找前趋节点也是分两种情况考虑
// 如果这个节点存在左子树,那么这个左子树的最大关键字就是这个节点的前趋节点
if (node.lchild != null) {
return maxElemNode(node.lchild);
}
// 如果这个节点不存在左子树,那么这个节点的父节点
TreeNode parentNode = node.parent;
while (parentNode != null && parentNode.lchild == node) {
node = parentNode;
parentNode = parentNode.lchild;
}
return parentNode;
}
// 从二叉树当中删除指定的节点
public void delete(int key) throws Exception {
TreeNode pNode = search(key);
if (pNode == null) {
throw new Exception("此树中不存在要删除的这个节点!");
}
delete(pNode);
}
/**
* 这个方法可以算是一个递归的方法,适用于 要删除的节点的两个子节点都非空,并且要删除的这个节点的后续节点也有子树的情况下
*
* @param pNode
* @throws Exception
*/
private void delete(TreeNode pNode) throws Exception {
// 第一种情况:删除没有子节点的节点
if (pNode.lchild == null && pNode.rchild == null) {
if (pNode == root) {// 如果是根节点,那么就删除整棵树
root = null;
} else if (pNode == pNode.parent.lchild) {
// 如果这个节点是父节点的左节点,则将父节点的左节点设为空
pNode.parent.lchild = null;
} else if (pNode == pNode.parent.rchild) {
// 如果这个节点是父节点的右节点,则将父节点的右节点设为空
pNode.parent.rchild = null;
}
}
// 第二种情况: (删除有一个子节点的节点)
// 如果要删除的节点只有右节点
if (pNode.lchild == null && pNode.rchild != null) {
if (pNode == root) {
root = pNode.rchild;
} else if (pNode == pNode.parent.lchild) {
pNode.parent.lchild = pNode.rchild;
pNode.rchild.parent = pNode.parent;
} else if (pNode == pNode.parent.rchild) {
pNode.parent.rchild = pNode.rchild;
pNode.rchild.parent = pNode.parent;
}
}
// 如果要删除的节点只有左节点
if (pNode.lchild != null && pNode.rchild == null) {
if (pNode == root) {
root = pNode.lchild;
} else if (pNode == pNode.parent.lchild) {
pNode.parent.lchild = pNode.lchild;
pNode.lchild.parent = pNode.parent;
} else if (pNode == pNode.parent.rchild) {
pNode.parent.rchild = pNode.lchild;
pNode.lchild.parent = pNode.parent;
}
}
// 第三种情况: (删除有两个子节点的节点,即左右子节点都非空)
// 方法是用要删除的节点的后续节点代替要删除的节点,并且删除后续节点(删除后续节点的时候需要递归操作)
// 解析:这里要用到的最多也就会发生两次,即后续节点不会再继续递归的删除下一个后续节点了,
// 因为,要删除的节点的后续节点肯定是:要删除的那个节点的右子树的最小关键字,而这个最小关键字肯定不会有左节点;
// 所以,在删除后续节点的时候肯定不会用到(两个节点都非空的判断 ),如有有子节点,肯定就是有一个右节点。
if (pNode.lchild != null && pNode.rchild != null) {
// 先找出后续节点
TreeNode successorNode = successor(pNode);
if (pNode == root) {
root.key = successorNode.key;
} else {
pNode.key = successorNode.key;// 赋值,将后续节点的值赋给要删除的那个节点
}
delete(successorNode);// 递归的删除后续节点
}
}
/**
* 中序遍历二叉树,并获得节点列表
*
* @return
*/
public List<TreeNode> inOrderTraverseList() {
if (nodeList != null) {
nodeList.clear();
}
inOrderTraverse(root);
return nodeList;
}
/**
* 进行中序遍历
*
* @param node
*/
private void inOrderTraverse(TreeNode node) {
if (node != null) {
inOrderTraverse(node.lchild);
nodeList.add(node);
inOrderTraverse(node.rchild);
}
}
/**
* 获取二叉查找树中关键字的有序列表
*
* @return
*/
public String toStringOfOrderList() {
StringBuilder sb = new StringBuilder("[");
for (TreeNode pNode : nodeList) {
sb.append(pNode.key + " ");
}
sb.append("]");
return sb.toString();
}
public static void main(String[] args) {
BinarySearchTree tree = new BinarySearchTree();
// 添加数据测试
tree.insert(10);
tree.insert(40);
tree.insert(20);
tree.insert(3);
tree.insert(49);
tree.insert(13);
tree.insert(123);
// 中序排序测试
tree.inOrderTraverse(tree.root);
System.out.println(tree.toStringOfOrderList());
// 查找测试
if (tree.search(10) != null) {
System.out.println("找到了");
} else {
System.out.println("没找到");
}
// 删除测试
try {
tree.delete(tree.search(40));
} catch (Exception e) {
e.printStackTrace();
}
// 检测删除节点
if (tree.search(40) != null) {
System.out.println("找到了");
} else {
System.out.println("没找到");
}
// 重新遍历
nodeList.clear();
tree.inOrderTraverse(tree.root);
System.out.println(tree.toStringOfOrderList());
}
}