二叉搜索树
定义
若任意节点的左子树不空,则左子树上的所有节点的值均小于它的根节点的值;
若任意节点的右子树不空,则右子树上的所有节点的值均大于它的根节点的值;
任意节点的左子树,右子树也分别为二叉查找树
节点的值不能相同
- 如果节点的值存的是key,则key是不允许重复的
- 如果节点的值存的是key-value键值对,则key是不允许重复的,value是允许重复的。
- 二叉搜索树的中序遍历是有许的
常见API实现(节点存的是key为例)
insert()
二叉搜索树的插入本质来说是插入到叶子节点上
- 如果是空树,让树的根节点指向要插入的新节点
- 查找合适的位置,插入元素(模拟查找过程),一个引用current标记查找节点,一个引用parentCurrent标记查找节点的父节点
- 用待插入的值和根节点进行比较
- 待插入的值和根节点相等,就插入失败(因为二叉搜索树不允许节点的值重复)
- 待插入的值比根节点的值小,就去根节点左子树找
- 待插入的值比根节点的值大,就去根节点右子树找
- 依次再比较待查找元素和左子树/右子树根节点的值,再去递归找左子树和右子树
- 循环结束current就指向null,当前元素要插入到parentCurrent的子树位置
- 在比较是插入到左子树还是右子树
- 待插入值小于parentCurrent的值,就插入到左子树
- 待插入值大于parentCurrent的值,就插入到右子树
**时间复杂度:**O(n)当整棵树退化为链表
search()
根据二叉搜索树的特性查找,避免了遍历整个树
- 用待查找的值和根节点值进行比较
- 待查找的值和根节点的值相等,说明找到返回true
- 待查找的值小于根节点的值,则去根的左子树去查找
- 待查找的值大于根节点的值,则去根节点的右子树去查找
- 依次再比较待查找元素和左子树/右子树根节点的值,再去递归找左子树和右子树
- 遍历到叶子节点,如果没有找到就返回false
**时间复杂度:**O(n) 当整棵树退化为链表
remove()
- 先查找待删除的值是否存在,存在再删除,不存在就删除失败
- 查找过程
- 用待删除的值和根节点进行比较
- 待删除的值和根节点的值比较
- 待删除的值和根节点的值相等,然后进行删除操作
- 待删除的值小于根节点的值,则去根节点的左子树查找
- 待删除的值大于根节点的值,则去根节点的右子树查找
- 依次在比较待删除值和左子树/右子树根节点的值,迭代下去
- 待删除的值存在,进行删除操作
- 分情况:
- 删除的是叶子节点
1. 只有一个节点的树,直接让让根节点指向null
2. 删的是父节点的左孩子
3. 删的父节点的右孩子- 删除的只有左或右子树的节点
- 删的是只有左子树的节点
- 删除的跟节点
- 删除的是父节点的左孩子
- 删除的父节点的右孩子
- 删的是只有右子树的节点
- 删除的根节点
- 删除的是父节点的左孩子
- 删除的是父节点的右孩子
- 删除的是有左和右子树的节点(替换法删除)
- 方法1-替换最大值
- 找待删除节点左子树的最大值(替罪羊)-左子树的最右端
- 替换-待删除节点值替换为左子树的最大值(替罪羊)
- 删除左子树的最大值(替罪羊)-类似删除只有左子树的节点情况
- 方法2-替换最小值
- 找待删除节点右子树的最小值(替罪羊)-右子树的最左端
- 替换-待删除节点值替换为右子树的最小值(替罪羊)
- 删除右子树的最小值(替罪羊)-类似删除只有右子树的情况
**时间复杂度:**O(n) 当整棵树退化为链表
实现
TreeNode类
public class TreeNode {
Integer key;
TreeNode left;
TreeNode right;
public TreeNode(Integer key){
this.key=key;
}
}
BinarySearchTree类
public class BinarySearchTree {
TreeNode root;
public BinarySearchTree(){
this.root=null;
}
//中序遍历作为打印验证
public void inOrder(TreeNode root){
if (root==null){
return;
}
inOrder(root.left);
System.out.print(root.key+" ");
inOrder(root.right);
}
public void insert(Integer key){
if(this.root==null){
this.root=new TreeNode(key);
return;
}
//标识current的父节点
TreeNode parentCurrent=null;
//current寻找插入位置
TreeNode current=this.root;
int cmp=0;
while (current!=null){
cmp=key.compareTo(current.key);
if (cmp==0){
throw new RuntimeException("二叉搜索树不允许相同的key值");
}else if (cmp<0){//往左插
parentCurrent=current;
current=current.left;
}else{
parentCurrent=current;
current=current.right;
}
}
TreeNode node=new TreeNode(key);
if (cmp<0){
parentCurrent.left=node;
}else{
parentCurrent.right=node;
}
}
public boolean search(Integer key){
if (this.root==null){
return false;
}
TreeNode current=this.root;
while (current!=null){
int cmp=key.compareTo(current.key);
if (cmp==0){
return true;
}else if(cmp<0){
current=current.left;
}else{
current=current.right;
}
}
return false;
}
/*
* 1.删除的是叶子节点
* 2.删除的是有一个孩子的节点
* 3.删除的是有两个孩子的节点(替换删除)
* 选择作左子树中最大的或者右子树中最小的
*
* */
public boolean remove(Integer key) {
if(this.root==null){
return false;
}
//查找
TreeNode parent=null;
TreeNode current=this.root;
while (current!=null){
int cmp=key.compareTo(current.key);
if (cmp==0){
//删除
removeInternal(current,parent);
return true;
}else if(cmp<0){
parent=current;
current=current.left;
}else{
parent=current;
current=current.right;
}
}
return false;
}
private void removeInternal(TreeNode node, TreeNode parent) {
//删除的是叶子节点
if(node.left==null&&node.right==null){
if(this.root==node){//说明只有一个节点的树
this.root=null;
}else if (parent.left==node){ //删左孩子则去除
parent.left=null;
}else{//删有孩子则去除
parent.right=null;
}
}else if(node.left!=null&&node.right==null){//删除只有左子树的节点
if(this.root==node){
this.root=node.left;
}else if (node==parent.left){
parent.left=node.left;
}else{
parent.right=node.left;
}
}else if(node.left==null&&node.right!=null){//删除只有右子树的节点
if(root==node){
root=node.right;
}else if(node==parent.left){
parent.left=node.right;
}else{
parent.right=node.right;
}
}else if(node.left!=null&&node.right!=null){
//方法1-替换最大值
//找待删除节点左子树中的最大值(替罪羊)-左子树的最右端
/*
* 为什么找左子树的最大值?
* 左子树的最大值和待删除节点一替换,仍然满足二叉搜索树的性质
* 左孩子小于根节点,右孩子大于根节点
* */
//从左孩子开始一直向右走到尽头
TreeNode maxParent=node;
TreeNode max=node.left;
while (max.right!=null){
maxParent=max;
max=max.right;
}
//替换-把替罪羊的值赋给待删除的节点
node.key=max.key;
//删除替罪羊节点max(替罪羊节点一定没有右子树)
//类似删除的只有左孩子的情况
if (maxParent.left==max){
maxParent.left=max.left;
}else{
maxParent.right=max.left;
}
//方法2-替换最小值(和替换最大值同理)
//找右子树的最小值(替罪羊)-右子树的最左端
// TreeNode minParent=node;
// TreeNode min=node.right;
//
// while (min.left!=null){
// minParent=min;
// min=min.left;
// }
//
// //替换
// node.key=min.key;
// //删除替罪羊(替罪羊节点没有左子树)
// //类似于删除只右子树的节点
//
//
// if (minParent.right==min){
// minParent.right=min.right;
// }else {
// minParent.left=min.right;
// }
}
}
}
测试类Test
public class Test {
public static void main(String[] args) {
BinarySearchTree bst=new BinarySearchTree();
bst.insert(1);
bst.insert(3);
bst.insert(5);
bst.insert(7);
bst.insert(9);
bst.insert(10);
bst.insert(2);
bst.insert(4);
//中序遍历
bst.inOrder(bst.root);
System.out.println();
System.out.println(bst.search(7));
System.out.println(bst.search(6));
System.out.println(bst.remove(1));
bst.inOrder(bst.root);
System.out.println();
System.out.println(bst.remove(9));
bst.inOrder(bst.root);
System.out.println();
}
}