二叉搜索树

一、二叉搜索树

1、定义

二叉搜索树(Binary Search Tree,BST)是一种二叉树。又叫二叉查找树,二叉排序树。

定义:设x为二叉查找树中的一个结点,x结点包含关键字key,结点x的key值计为key[x]。如果y是x的左子树中的一个结点,则key[y]<=key[x];如果y是x的右子树的一个结点,则key[y]>=key[x]。

通俗讲就是:

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉搜索树。
  • 没有键值相等的结点。

在这里插入图片描述

为什么又叫做二叉排序树呢?

只要一颗树是二叉搜索树,那么它的中序遍历一定是有序的。左 根(输出) 右。

比如上图这颗二叉搜索树,它的中序遍历为:11 20 29 32 41 50 65 72 91 99。

2、基本操作

二叉搜索树的基本操作演示 - 可参考链接:https://visualgo.net/en/bst

2.1 查找元素

通过递归查找是否存在key。O(logn)。

思路:二叉搜索树的左子树永远是比根节点小的,而它的右子树则都是比根节点大的值。当前节点比要找的大就往左走,当前元素比要找的小就往右走。

2.2 插入元素

因为是二叉搜索树就不能插入重复的元素了,且每次插入都是插入到叶子节点的位置。
原树中不存在key,插入key返回true,否则返回false。
插入n个元素的时间复杂度 O(nlogn)。

思路:如果是空树直接把元素插入root位置就好了。否则,比较插入的元素比当前位置元素小就往左走,比当前位置元素大就往右走,直到为空。最后再判断插入到 parent 的左子树还是右子树位置。

注意:每次插入元素的时候都是要和根结点比较。一直要找到它应该插入的位置。

2.3 删除元素

删除元素是一个比较难的点,要考虑到几种种情况:

  • (1)叶子结点 O(1):直接删除,不影响原。
  • (2)仅仅有左或右子树的结点 O(1):结点删除后,将它的左子树或右子树整个移动到删除结点的位置就可以,子承父业。
  • (3)既有左又有右子树的结点O(logn):找到需要删除结点d 的直接前驱或者直接后继 s,用 s来替换结点d,然后再删除结点s。

3、代码实现

1)基于链表存储的二叉搜索树

@Data
@AllArgsConstructor
@NoArgsConstructor
public class BinarySearchTree {

	public Node root = null;

	/**
	 * 结点
	 */
	public static class Node {
		int data;
		Node left;
		Node right;

		public Node(int data) {
			this.data = data;
		}
	}

	/**
	 * 插入结点:插入其实就是查找,每次插入时都要和根结点比较,一直要找到它应该插入的位置。
	 *
	 * @param root
	 * @param data
	 */
	public boolean insert(Node root, int data) {
		if (this.root == null) {
			return false;
		}

		if (root.data < data) { // 比根节点大 我们要放到右边
			if (root.right == null) {
				root.right = new Node(data);
			} else {
				insert(root.right, data);
			}
		} else { // 比根节点小 我们要放到右边
			if (root.left == null) {
				root.left = new Node(data);
			} else {
				insert(root.left, data);
			}
		}

		return true;
	}

	/**
	 * 查找元素
	 * 
	 * @param key
	 */
	public Node search(int key) {
		if (this.root == null) {
			return null;
		}

		Node cur = this.root;
		while (cur != null) {
			if (cur.data == key) {
				return cur;
			} else if (cur.data > key) {
				cur = cur.left;
			} else {
				cur = cur.right;
			}
		}

		return null;
	}

	/**
	 * 删除元素
	 * 
	 * @param key
	 * @return
	 */
	public boolean remove(int key) {
		if (this.root == null) {
			return false;
		}

		Node parent = null;
		Node cur = this.root;
		while (cur != null) {
			// 找到该节点
			if (cur.data == key) {
				removeKey(parent, cur);
				return true;
			} else if (cur.data < key) {
				parent = cur;
				cur = cur.right;
			} else {
				parent = cur;
				cur = cur.left;
			}
		}
		return false;
	}

	/**
	 * 从父结点中删除当前节点
	 * 
	 * @param parent
	 *            - cur的父结点
	 * @param cur
	 *            - 要删除的当前结点
	 */
	private void removeKey(Node parent, Node cur) {
		/**
		 * 第一种:当前结点的左子树为空 <br/>
		 * 如果删除的当前结点是根结点,则根结点指向根结点的右结点。 <br/>
		 * 如果删除的当前结点是父结点的左孩子,则父结点的左孩子指向当前结点右结点。<br/>
		 * 如果删除的当前结点是父结点的右孩子,则父结点的右孩子指向当前结点右结点。<br/>
		 */
		if (cur.left == null) {
			if (cur == this.root) {
				this.root = this.root.right;
			} else if (cur == parent.left) {
				parent.left = cur.right;
			} else {
				parent.right = cur.right;
			}
		} else if (cur.right == null) { // 第二种:当前结点的右子树为空
			if (cur == this.root) {
				this.root = this.root.left;
			} else if (cur == parent.left) {
				parent.left = cur.left;
			} else {
				parent.right = cur.left;
			}
		} else {
			/**
			 * 第三种:当前结点的左右子树均不为空 <br></>
			 * 1.寻找当前结点的后继结点。注意:其后继结点的左子树一定为空。
			 * 2.将当前结点替换为其后继节点,并移动指针。
			 */
			// 1.寻找当前结点的后继结点
			Node targetParent = cur;
			Node target = cur.right;
			while (target.left != null) {
				targetParent = target;
				target = target.left;
			}
			// 2.替换为后继结点的值,并移动指针
			cur.data = target.data;
			if (targetParent.left == target) {
				targetParent.left = target.right;
			} else {
				targetParent.right = target.right;
			}
		}
	}

}

2)测试

    public static void main(String[] args) {

        BinarySearchTree binarySearchTree = new BinarySearchTree();
        BinarySearchTree.Node root = new BinarySearchTree.Node(10);
        binarySearchTree.setRoot(root);

        // 插入
        binarySearchTree.insert(root, 9);
        binarySearchTree.insert(root, 14);
        binarySearchTree.insert(root, 11);
        binarySearchTree.insert(root, 15);
        binarySearchTree.insert(root, 12);
        binarySearchTree.insert(root, 13);
        binarySearchTree.insert(root, 8);
        System.out.println(binarySearchTree);

        // 查找
        BinarySearchTree.Node search = binarySearchTree.search(80);
        System.out.println(search == null ? "未找到" : search.data);
        search = binarySearchTree.search(11);
        System.out.println(search == null ? "未找到" : search.data);

        // 删除
        binarySearchTree.remove(10);
        System.out.println(binarySearchTree);
    }

在这里插入图片描述

在这里插入图片描述

4、性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

  • 最好情况:二叉搜索树为完全二叉树,其平均比较次数为:O(logn)。
  • 最坏情况:二叉搜索树退化为单支树,其平均比较次数为:O(n)。

结论:二叉树的结构就决定了其搜索的性能,不要让二叉树变成单支树,而是让树的左右子树高度相近,所以就有了 AVL树和红黑树。

– 求知若饥,虚心若愚。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值