二叉搜索树 —— 算法学习笔记(二)

概念

二叉搜索树(Binary Search Tree,BST),是指一棵空树或者具有下列性质的二叉树:

  1. 某节点的左子树节点值仅包含小于该节点值
  2. 某节点的右子树节点值仅包含大于该节点值
  3. 左右子树每个也必须是二叉查找树
    在这里插入图片描述
    二叉查找树有一个根节点,且每个节点下最多有只能有两个子节点,左子节点的值小于其父节点,右子节点的值大于其父节点。

可以自己创建二叉树感受一下:在线演示BST网址
在这里插入图片描述
参考:
Java数据结构和算法(十)——二叉树数据结构之红黑树-动图演示(上)

创建

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

节点类:

public class Node {
    int data;   //节点数据
    Node leftChild; //左子节点的引用
    Node rightChild; //右子节点的引用
    boolean isDelete;//表示节点是否被删除
     
    public Node(int data){
        this.data = data;
    }
    //打印节点内容
    public void display(){
        System.out.println(data);
    }
}

具体方法:

public interface Tree {
    //查找节点
    public Node find(int key);
    //插入新节点
    public boolean insert(int data);
     
    //中序遍历
    public void infixOrder(Node current);
    //前序遍历
    public void preOrder(Node current);
    //后序遍历
    public void postOrder(Node current);
     
    //查找最大值
    public Node findMax();
    //查找最小值
    public Node findMin();
     
    //删除节点
    public boolean delete(int key);
}

查找

查找某个节点,必须从根节点开始遍历:

  1. 查找值比当前节点值大,则搜索右子树;

  2. 查找值等于当前节点值,停止搜索(终止条件);

  3. 查找值小于当前节点值,则搜索左子树;
    在这里插入图片描述

	@Override
    public Node find(int key) {
    	Node current = root;
    	while(current != null){
    		if(current.data > key) { //当前值比查找值大
    			current = current.leftChild; //搜索左子树
    		}else if(current.data < key){ //当前值比查找值小
				current = current.rightChild; //搜索右子树
			}else {
				return current;
			}
    	}    	    	
		return null;  //遍历完整个树没找到,返回null 	 
    }

树的效率:查找节点的时间取决于这个节点所在的层数,每一层最多有 2 n − 1 2^{n-1} 2n1个节点,总共l层,共有 2 n − 1 2^n-1 2n1个节点,那么时间复杂度为 O ( l o g 2 n ) O(log_2 n) O(log2n)

插入

在这里插入图片描述

	@Override
	public boolean insert(int data) {
		Node newNode = new Node(data);
		if(root == null) { //当前树为空时
			root = newNode;
			return true;
		}else {
			Node current = root;
			Node parentNode = null;
			while(current != null) {
				parentNode = current;
				if(current.data > data) { //当前值比插入值大,搜索左子节点
					current = current.leftChild;
					if(current == null) { //子节点为空,直接将新值插入到该节点
						parentNode.leftChild = newNode;
						return true;
					}
				}else {
					current = current.rightChild;
					if(current == null) { //子节点为空,直接将新值插入到该节点
						parentNode.rightChild = newNode;
						return true;
					}
				}
			}
		}
		return false;
	}

删除

删除节点首先要查找要删除的节点,找到后执行删除操作。

删除节点的节点有如下几种情况:

  1. 删除的节点没有子节点
  2. 删除的节点有一个子节点
  3. 删除的节点有两个子节点

1 删除的节点没有子节点

在没有子节点的情况,其父节点指向空,要删除的节点依然存在,但是它已经不是树的一部分了,由于Java语言的垃圾回收机制,我们不需要非得把节点本身删掉,一旦Java意识到程序不在与该节点有关联,就会自动把它清理出存储器。

如图:删除节点[70]
在这里插入图片描述

    public boolean delete(int key) {
        Node current = root;
        Node parent = root;
        boolean isLeftChild = false;

        while(current.data != key){  //查找删除值,找不到直接返回false
            parent = current;
            if(current.data > key){
                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;
            }
            return true;           
        }
        return false;
	}
	//当前节点有一个子节点,左子节点
    else if(current.leftChild != null && current.rightChild == null){
         if(current == root){
             root = current.leftChild;
         }else if(isLeftChild){
             parent.leftChild = current.leftChild;
         }else{
             parent.rightChild = current.leftChild;
         }
         return true;

2 删除的节点有一个子节点

有一个子节点的情况下,将其父节点指向其子节点,然后删除该节点。

如图:删除节点[200]
在这里插入图片描述

	//当前节点有一个子节点,左子节点
    else if(current.leftChild != null && current.rightChild == null){
         if(current == root){
             root = current.leftChild;
         }else if(isLeftChild){
             parent.leftChild = current.leftChild;
         }else{
             parent.rightChild = current.leftChild;
         }
         return true;

3 删除的节点有两个子节点

该种情况下,涉及到节点的“位置变换”,用右子树中的最小节点替换当前节点。从右子树一直 left 到 NULL。最后会被转换为前两种情况。

所以对于删除有两个孩子的节点,删除的是其右子树的最小节点,最小节点的内容会替换要删除节点的内容。

如图:删除节点[50]
在这里插入图片描述

	else{
            //当前节点存在两个子节点
            Node successor = getSuccessor(current);
            if(current == root){
                root= successor;
            }else if(isLeftChild){
                parent.leftChild = successor;
            }else{
                parent.rightChild = successor;
            }
            successor.leftChild = current.leftChild;
        }
    public Node getSuccessor(Node delNode){
        Node successorParent = delNode;
        Node successor = 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;
    }

遍历

遍历树是根据一种特定的顺序访问树的每一个节点。比较常用的有前序遍历,中序遍历和后序遍历。而二叉搜索树最常用的是中序遍历。

  1. 中序遍历: 左子树——》根节点——》右子树

  2. 前序遍历: 根节点——》左子树——》右子树

  3. 后序遍历: 左子树——》右子树——》根节点

在这里插入图片描述

    //中序遍历
    public void infixOrder(Node current){
        if(current != null){
            infixOrder(current.leftChild);
            System.out.print(current.data+" ");
            infixOrder(current.rightChild);
        }
    }
     
    //前序遍历
    public void preOrder(Node current){
        if(current != null){
            System.out.print(current.data+" ");
            infixOrder(current.leftChild);
            infixOrder(current.rightChild);
        }
    }
     
    //后序遍历
    public void postOrder(Node current){
        if(current != null){
            infixOrder(current.leftChild);
            infixOrder(current.rightChild);
            System.out.print(current.data+" ");
        }
    }

测试:

    public static void main(String[] args) {
        BinaryTree bt = new BinaryTree();
        bt.insert(50);
        bt.insert(20);
        bt.insert(80);
        bt.insert(10);
        bt.insert(30);
        bt.insert(60);
        bt.insert(90);
        bt.insert(25);
        bt.insert(85);
        bt.insert(100);
        bt.delete(10);//删除没有子节点的节点
        bt.delete(30);//删除有一个子节点的节点
        bt.delete(80);//删除有两个子节点的节点
        System.out.println(bt.findMax().data);
        System.out.println(bt.findMin().data);
        System.out.println(bt.find(100));
        System.out.println(bt.find(200));
         
    }

结果:
在这里插入图片描述

该树的优缺点

优:
有序数组删除或插入数据较慢(向数组中插入数据时,涉及到插入位置前后数据移动的操作),但根据索引查找数据很快,可以快速定位到数据,适合查询。而链表正好相反,查找数据比较慢,插入或删除数据较快,只需要引用移动下就可以,适合增删。

而二叉树就是同时具有以上优势的数据结构。

缺:
上面的树是非平衡树,由于插入数据顺序原因,多个节点可能会倾向根的一侧。极限情况下所有元素都在一侧,此时就变成了一个相当于链表的结构。

比如:依次插入节点[100,150,170,300,450,520 …]

这种不平衡将会使树的层级增多(树的高度增加),查找或插入元素效率变低。
那么只要当插入元素或删除元素时还能维持树的平衡,使元素不至于向一端严重倾斜,就可以避免这个问题。

到此,红黑树闪亮登场, 红黑树就是一种平衡二叉树。

后续:
红黑树 —— 算法学习笔记(三)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值