【tree】二叉查找树

本文目录

  • 一、基本概念
    • 二叉查找树的定义
    • 查找节点
    • 插入节点
    • 删除节点
  • 二、递归实现
    • 实现代码
    • 测试案例
  • 三、非递归实现
    • 实现代码
    • 测试案例
  • 四、更多实现:上例是逻辑结构的变化删除节点,还可以不修改逻辑结构而是通过复制key-value、左右孩子来达到删除节点的目的

系列目录


一、基本概念

1.二叉查找树的定义

二叉查找树:首先是一颗二叉树,其次对于树中的任意一个节点都有该节点的值大于左孩子小于右孩子。

二叉树的定义见第一篇博客《树》

需要实现的基本操作:查找结点、插入节点和删除节点。

2.查找节点

从根结点出发:

  • 如果查找key小于节点key,则向左走;
  • 如果查找key大于节点key,则向右走;
  • 如果查找key等于节点key,则返回该节点;
  • 如果查找到叶子节点还没有找到相等key,那么返回null;
    • 查找节点是否为null,是递归实现中递归的终止条件,也是非递归实现中while循环的终止条件;

3.插入节点

从根结点出发:

  • 如果查找key小于节点key,则向左走;
  • 如果查找key大于节点key,则向右走;
  • 如果查找key等于节点key,则说明树种已经存在相同key的节点,修改该节点的value并返回;
  • 如果查找到叶子节点还没有找到相同的key,则将新节点插入为节点的左孩子或右孩子;
    • 查找节点是否为null,是递归实现中递归的终止条件,也是非递归实现中while循环的终止条件;

4.删除节点

从根结点出发:

  • 如果查找key小于节点key,则向左走;
  • 如果查找key大于节点key,则向右走;
  • 如果查找key等于节点key,则说明该节点即为待删除结点,此时有两种情况:
    • 待删除结点有两个孩子:这种情形,为了保证二叉排序树的顺序性(即大于左孩子小于右孩子),需要从左子树或右子树中找一个最接近待删除节点的节点来替换它,即左子树的最大节点或右子树的最小节点
      • 左子树的最大节点,即左子树的最右节点
      • 右子树的最小节点,即右子树的最左节点
      • 注意:本文后续递归和非递归的实现中,选择右子树的最大节点(即右子树的最左节点)来代替删除节点位置,从而保证二叉查找树的有序性
    • 待删除结点最多只有一个孩子:直接让子节点代替删除节点的位置

如下图所示:

  • 58节点只有一个左孩子47节点,删除58节点只需要让47节点顶替其位置,这样既维护了二叉树的逻辑结构,也保证了二叉查找树的顺序性;
  • 47节点既有左孩子也有右孩子:
    • 对于47节点的左子树而言,其左子树的最右节点37的值最接近47节点,用37节点来替换47节点,不会破坏二叉查找树的顺序性,37节点是左子树的最大值,但又小于47节点的右子树的任意一个节点
    • 对于47节点的右子树而言,因为只有51节点,所以可以直接替换,如果有多个节点,那就找右子树的最左节点(即右子树的最小节点)来替换47节点,同样也不会破坏二叉查找树的顺序性;


二、递归实现

1.实现代码

package cn.wxy.blog;

import java.util.Objects;
import cn.wxy.blog.TraversalTreeTool.TreeNode;

/**
 * 递归实现二叉查找树
 * @author 王大锤
 * @date 2021年6月11日
 */
public class BinarySearchTree<K extends Comparable<K>, V> {
	static class Node<K extends Comparable<K>, V> implements TreeNode<K, Node<K, V>> {
		private K key;
		private V value;
		private Node<K, V> parent;
		private Node<K, V> leftChild;
		private Node<K, V> rightChild;

		public Node(K key, V value, Node<K, V> parent) {
			this.key = key;
			this.value = value;
			this.parent = parent;
		}

		public K getKey() {
			return key;
		}

		public void setKey(K key) {
			this.key = key;
		}

		public Node<K, V> getParent() {
			return parent;
		}

		public void setParent(Node<K, V> parent) {
			this.parent = parent;
		}

		public Node<K, V> getLeftChild() {
			return leftChild;
		}

		public void setLeftChild(Node<K, V> leftChild) {
			this.leftChild = leftChild;
		}

		public Node<K, V> getRightChild() {
			return rightChild;
		}

		public void setRightChild(Node<K, V> rightChild) {
			this.rightChild = rightChild;
		}

		@Override
		public String toString() {
			return this.key + ":" + value + " ";
		}
	}

	private Node<K, V> root;

	public BinarySearchTree() {
	}

	public Node<K, V> getRoot() {
		return this.root;
	}

	public void insert(K key, V value) {
		if (Objects.nonNull(key)) {
			// 通过this.root = insert来维护二叉树的根节点
			this.root = insert(key, value, this.root, null);
		}
	}

	/**
	 * 递归实现二叉查找树的节点插入:
	 * 1.如果插入key小于当前查找的节点,则往左走
	 * 2.如果插入key大于当前查找的节点,则往右走
	 * 3.如果插入key等于当前查找的节点,修改当前节点的value
	 * @param key 插入的key
	 * @param value 插入的value
	 * @param node 当前节点
	 * @param parent 当前节点的父节点
	 * @return
	 */
	private Node<K, V> insert(K key, V value, Node<K, V> node, Node<K, V> parent) {
		if (Objects.isNull(node))
			return new Node<K, V>(key, value, parent);
		int compare = key.compareTo(node.key);
		if (compare < 0)
			// 通过node.leftChild = insert在递归中来维护树的逻辑结构
			node.leftChild = insert(key, value, node.leftChild, node);
		else if (compare > 0)
			// 通过node.rightChild = insert在递归中来维护树的逻辑结构
			node.rightChild = insert(key, value, node.rightChild, node);
		else
			node.value = value;
		return node;
	}

	public V select(K key) {
		if (Objects.nonNull(key)) {
			Node<K, V> node = select(key, this.root);
			return Objects.nonNull(node) ? node.value : null;
		}
		return null;
	}

	/**
	 * 递归实现二叉查找树的节点查找:
	 * 1.如果查找key小于当前查找的节点,则往左走
	 * 2.如果查找key大于当前查找的节点,则往右走
	 * 3.如果查找key等于当前查找的节点,则返回该节点
	 * @param key 查找的key
	 * @param node 当前节点
	 * @return
	 */
	private Node<K, V> select(K key, Node<K, V> node) {
		if (Objects.isNull(node))
			return null;
		int compare = key.compareTo(node.key);
		if (compare < 0)
			return select(key, node.leftChild);
		else if (compare > 0)
			return select(key, node.rightChild);
		else
			return node;
	}

	public void remove(K key) {
		if (Objects.nonNull(key))
			// 通过this.root = remove来维护二叉查找树的根节点
			this.root = remove(key, this.root);
	}

	/**
	 * 递归实现二叉查找树的节点删除:
	 * 1.如果删除key小于当前查找的节点,则往左走
	 * 2.如果删除key大于当前查找的节点,则往右走
	 * 3.如果删除key等于当前查找的节点,则删除该节点
	 * 此时,删除节点分为两种情况:
	 * a.删除节点有两个孩子:这种情形,为了保证二叉排序树的顺序性(大于左孩子小于右孩子),需要从左子树或右子树中找一个最接近删除节点节点
	 * 即左子树的最大节点或右子树的最小节点
	 * 左子树的最大节点即左子树的最右节点
	 * 右子树的最小节点即右子树的最左节点
	 * 本例中选择右子树的最大节点(即右子树的最左节点)来代替删除节点位置,从而保证二叉查找树的有序性
	 * b.删除节点最多只有一个孩子:直接让子节点代替删除节点的位置
	 * @param key
	 * @param node
	 * @return
	 */
	private Node<K, V> remove(K key, Node<K, V> node) {
		if (Objects.isNull(node))
			return null;
		int compare = key.compareTo(node.key);
		if (compare < 0)
			// 通过node.leftChild = remove在递归中来维护树的逻辑结构
			node.leftChild = remove(key, node.leftChild);
		else if (compare > 0)
			// 通过node.rightChild = remove在递归中来维护树的逻辑结构
			node.rightChild = remove(key, node.rightChild);
		else {
			// 找到待删除结点,此时有两种情况:
			// a.待删除结点有两个孩子:本例中用待删除节点右子树的最小节点(即右子树的最左节点)来替换其位置
			// b.待删除结点最多有一个孩子:直接用其孩子替换待删除结点的位置
			if (Objects.nonNull(node.leftChild) && Objects.nonNull(node.rightChild)) {
				// 找到右子树最左节点,该节点最多有一个孩子
				// 摘除右子树最左节点
				// 用右子树最左节点替换待删除结点的位置
				// 返回右子树最左节点,用以在递归中保证树的逻辑结构
				Node<K, V> leftmostNodeOfRightSubtree = findMin(node.rightChild);
				// 通过node.rightChild = remove在递归中来维护树的逻辑结构
				node.rightChild = remove(leftmostNodeOfRightSubtree.key, node.rightChild);
				// 递归返回的时候,node.parent会完成与leftMostOfSubtree的左孩子或有孩子关系
				// 三个节点六条边,所以只需要处理五条边
				leftmostNodeOfRightSubtree.parent = node.parent;
				leftmostNodeOfRightSubtree.leftChild = node.leftChild;
				leftmostNodeOfRightSubtree.rightChild = node.rightChild;
				if (Objects.nonNull(node.leftChild))
					node.leftChild.parent = leftmostNodeOfRightSubtree;
				node.leftChild = null;
				if (Objects.nonNull(node.rightChild))
					node.rightChild.parent = leftmostNodeOfRightSubtree;
				node.rightChild = null;
				node = leftmostNodeOfRightSubtree;
			} else {
				// 用待删除结点的孩子替换其位置之后,为了在递归中维护树的逻辑结构,递归返回的是当前该位置的节点,即代替其位置的子节点
				// 所以在这个case中,用returnNode变量来记录替换待删除结点孩子,以便稍后递归返回该节点
				Node<K, V> returnNode = null;
				if (Objects.nonNull(node.leftChild)) {
					returnNode = node.leftChild;
					node.leftChild.parent = node.parent;
					node.leftChild = null;
				} else if (Objects.nonNull(node.rightChild)) {
					returnNode = node.rightChild;
					node.rightChild.parent = node.parent;
					node.rightChild = null;
				}
				node.parent = null;
				node = returnNode;
			}
		}
		return node;
	}

	private Node<K, V> findMin(Node<K, V> node) {
		if (Objects.isNull(node))
			throw new NullPointerException();
		Node<K, V> find = node;
		while (Objects.nonNull(find)) {
			node = find;
			find = find.leftChild;
		}
		return node;
	}
}

2.测试用例

  • 通过insert构建一个二叉查找树;
  • 对于查找操作,需要测试根节点、叶节点、普通节点和不存在的节点;
  • 对于删除操作,需要测试删除根节点、叶节点、两个孩子的节点、一个孩子的节点和不存在的节点;

insert构建的二叉查找树如下图所示:

插入测试:

		BinarySearchTree<Integer, String> binaryTree = new BinarySearchTree<Integer, String>();
		binaryTree.insert(62, "62");
		binaryTree.insert(58, "58");
		binaryTree.insert(88, "88");
		binaryTree.insert(47, "47");
		binaryTree.insert(73, "73");
		binaryTree.insert(99, "99");
		binaryTree.insert(35, "35");
		binaryTree.insert(51, "51");
		binaryTree.insert(93, "93");
		binaryTree.insert(37, "37");
		
		System.out.print("先序遍历:");
		TraversalTreeTool.preorderTraversalByRecursion(binaryTree.getRoot());
		System.out.println();
		System.out.print("中序遍历:");
		TraversalTreeTool.inorderTraversalByRecursion(binaryTree.getRoot());
		System.out.println();
		System.out.print("后序遍历:");
		TraversalTreeTool.postorderTraversalByRecursion(binaryTree.getRoot());
		System.out.println();
		System.out.print("层序遍历:");
		TraversalTreeTool.levelTraversal(binaryTree.getRoot());

先序遍历:62:62  58:58  47:47  35:35  37:37  51:51  88:88  73:73  99:99  93:93  
中序遍历:35:35  37:37  47:47  51:51  58:58  62:62  73:73  88:88  93:93  99:99  
后序遍历:37:37  35:35  51:51  47:47  58:58  73:73  93:93  99:99  88:88  62:62  
层序遍历:62:62  58:58  88:88  47:47  73:73  99:99  35:35  51:51  93:93  37:37  

查找测试:

		BinarySearchTree<Integer, String> binaryTree = new BinarySearchTree<Integer, String>();
		binaryTree.insert(62, "62");
		binaryTree.insert(58, "58");
		binaryTree.insert(88, "88");
		binaryTree.insert(47, "47");
		binaryTree.insert(73, "73");
		binaryTree.insert(99, "99");
		binaryTree.insert(35, "35");
		binaryTree.insert(51, "51");
		binaryTree.insert(93, "93");
		binaryTree.insert(37, "37");
		
		System.out.println(binaryTree.getRoot());
		System.out.println(binaryTree.select(62));
		System.out.println(binaryTree.select(58));
		System.out.println(binaryTree.select(88));
		System.out.println(binaryTree.select(93));
        System.out.println("查询不存在的节点:" + binaryTree.select(-1));

62:62 
62
58
88
93
查询不存在的节点:null

删除测试:

		BinarySearchTree<Integer, String> binaryTree = new BinarySearchTree<Integer, String>();
		binaryTree.insert(62, "62");
		binaryTree.insert(58, "58");
		binaryTree.insert(88, "88");
		binaryTree.insert(47, "47");
		binaryTree.insert(73, "73");
		binaryTree.insert(99, "99");
		binaryTree.insert(35, "35");
		binaryTree.insert(51, "51");
		binaryTree.insert(93, "93");
		binaryTree.insert(37, "37");
		
		System.out.print("删除不存在的节点-1:  ");
		binaryTree.remove(-1);
		TraversalTreeTool.levelTraversal(binaryTree.getRoot());
		System.out.println();
		System.out.print("删除叶节点37: ");
		binaryTree.remove(37);
		TraversalTreeTool.levelTraversal(binaryTree.getRoot());
		System.out.println();
		System.out.print("删除一个孩子节点58: ");
		binaryTree.remove(58);
		TraversalTreeTool.levelTraversal(binaryTree.getRoot());
		System.out.println();
		System.out.print("删除两个孩子节点88: ");
		binaryTree.remove(88);
		TraversalTreeTool.levelTraversal(binaryTree.getRoot());
		System.out.println();
		System.out.print("删除根节点62: ");
		binaryTree.remove(62);
		TraversalTreeTool.levelTraversal(binaryTree.getRoot());

删除不存在的节点-1:  62:62  58:58  88:88  47:47  73:73  99:99  35:35  51:51  93:93  37:37  
删除叶节点37: 62:62  58:58  88:88  47:47  73:73  99:99  35:35  51:51  93:93  
删除一个孩子节点58: 62:62  47:47  88:88  35:35  51:51  73:73  99:99  93:93  
删除两个孩子节点88: 62:62  47:47  93:93  35:35  51:51  73:73  99:99  
删除根节点62: 73:73  47:47  93:93  35:35  51:51  99:99  


三、非递归实现

1.实现代码

package cn.wxy.blog;

import java.util.Objects;
import cn.wxy.blog.TraversalTreeTool.TreeNode;

/**
 * 二叉查找树的非递归实现
 * 和递归实现的最大区别是逻辑结构的维护:
 * a.递归实现是在每次递归的代码部分来维护树的逻辑结构;
 * b.非递归实现需要在增、删操作中来维护树的逻辑结构和根节点;
 * @author 王大锤
 * @date 2021年6月12日
 */
public class BinarySearchTreeNonRecursive<K extends Comparable<K>, V> {
	static class Node<K extends Comparable<K>, V> implements TreeNode<K, Node<K, V>> {
		private K key;
		private V value;
		private Node<K, V> parent;
		private Node<K, V> leftChild;
		private Node<K, V> rightChild;

		public Node(K key, V value, Node<K, V> parent) {
			this.key = key;
			this.value = value;
			this.parent = parent;
		}

		public K getKey() {
			return key;
		}

		public void setKey(K key) {
			this.key = key;
		}

		public Node<K, V> getParent() {
			return parent;
		}

		public void setParent(Node<K, V> parent) {
			this.parent = parent;
		}

		public Node<K, V> getLeftChild() {
			return leftChild;
		}

		public void setLeftChild(Node<K, V> leftChild) {
			this.leftChild = leftChild;
		}

		public Node<K, V> getRightChild() {
			return rightChild;
		}

		public void setRightChild(Node<K, V> rightChild) {
			this.rightChild = rightChild;
		}

		@Override
		public String toString() {
			return this.key + ":" + value + " ";
		}
	}

	private Node<K, V> root;

	public BinarySearchTreeNonRecursive() {
	}

	public Node<K, V> getRoot() {
		return this.root;
	}

	public Node<K, V> select(K key) {
		Node<K, V> node = selectNode(key);
		if (Objects.nonNull(node) && key.compareTo(node.key) == 0)
			return node;
		return null;
	}

	/**
	 * select和insert中都需要查找结点,但是又有一些差异,所以抽取相同部分到selectNode函数中
	 * select: 从根节点开始往下找,找到返回节点,否则返回null
	 * insert: 从根节点开始往下找,找到节点更新其value,否则找到最后一个叶节点,新节点挂到叶节点的左孩子或右孩子
	 * 所以selectNode函数的逻辑:
	 * 1.从根节点开始往下找,找到则返回节点;
	 * 2.否则返回查找时最后一个叶节点;
	 * 3.如果key为null或者查找的是一颗空树,则返回null;
	 * @param key
	 * @return
	 */
	private Node<K, V> selectNode(K key) {
		if (Objects.isNull(key))
			return null;
		Node<K, V> current = null;
		Node<K, V> find = this.root;
		while (Objects.nonNull(find)) {
			// 当循环到叶子节点的时候,current等于叶子节点
			// find在接下来的判断中变为叶子节点的左孩子或右孩子,此时find一定为null,从而结束循环
			current = find;
			int compare = key.compareTo(find.key);
			if (compare < 0)
				find = find.leftChild;
			else if (compare > 0)
				find = find.rightChild;
			else
				return current;
		}
		return current;
	}

	/**
	 * 1.如果是空树,则插入的KV作为根节点;
	 * 2.如果树非空,分为两种情况
	 * a.树中已经存在key的节点,那么更新该节点的value并返回
	 * b.树中不存在key的节点,将新节点插入最后一次查找时的节点,作为其左孩子或右孩子
	 * @param key
	 * @param value
	 * @return
	 */
	public Node<K, V> insert(K key, V value) {
		if (Objects.isNull(key))
			throw new NullPointerException();
		// 根节点null,说明是空树
		if (Objects.isNull(this.root)) {
			this.root = new Node<K, V>(key, value, null);
			return this.root;
		}
		// 如果树非空,那selectNode的返回值要么是最后一次查找的节点,要么是树中已经存在同key的节点
		Node<K, V> current = selectNode(key);
		int compare = key.compareTo(current.key);
		// 如果树中已经存在同key的节点,那么修改该节点的value并返回
		if (compare == 0) {
			current.value = value;
			return current;
		}
		// 否则,current就是最后一次查找的节点,新节点作为该节点的左孩子或右孩子
		Node<K, V> node = new Node<K, V>(key, value, current);
		if (compare < 0)
			current.leftChild = node;
		else
			current.rightChild = node;
		return node;
	}

	/**
	 * 分成两种情况:
	 * 1.待删除节点有两个孩子,选取右子树最左节点替换该节点
	 * 2.待删除节点最多只有一个孩子,直接将子节点替换该节点
	 * 最后还需要判断待删除结点是不是根节点,如果是,则要将this.root指向替换节点
	 * @param key
	 * @return
	 */
	public Node<K, V> remove(K key) {
		if (Objects.isNull(key))
			return null;
		Node<K, V> deleteNode = selectNode(key);
		// 如果树是一棵空树或树中不存在要删除的key,返回null
		if (Objects.isNull(deleteNode) || key.compareTo(deleteNode.key) != 0)
			return null;
		// 到这里,deleteNode确定就是待删除的节点,接下来判断其孩子情况做进一步的处理
		if (Objects.nonNull(deleteNode.leftChild) && Objects.nonNull(deleteNode.rightChild)) {
			// deleteNode有两个孩子
			twoChildren(deleteNode);
		} else {
			// deleteNode最多有一个孩子
			oneChildAtMost(deleteNode);
		}
		return deleteNode;
	}

	private Node<K, V> findMin(Node<K, V> node) {
		if (Objects.isNull(node))
			throw new NullPointerException();
		Node<K, V> find = node;
		while (Objects.nonNull(find)) {
			node = find;
			find = find.leftChild;
		}
		return node;
	}

	/**
	 * 删除节点时,待删除结点最多只有一个孩子的情况
	 * Note:当待删除节点有两个孩子的时候,需要找到其右子树的最左节点来替代它,此时要摘除其右子树最左节点,而右子树最左节点要么是一个叶节点,要么只有一个右孩子
	 * @param deleteNode
	 */
	private void oneChildAtMost(Node<K, V> deleteNode) {
		// 需要关注deleteNode是否是根节点,如果是,那么需要修改this.root的引用指向替换节点
		Node<K, V> replaceNode = null;
		if (Objects.nonNull(deleteNode.leftChild)) {
			deleteNode.leftChild.parent = deleteNode.parent;
			if (Objects.nonNull(deleteNode.parent)) {
				if (deleteNode.equals(deleteNode.parent.leftChild))
					deleteNode.parent.leftChild = deleteNode.leftChild;
				else
					deleteNode.parent.rightChild = deleteNode.leftChild;
			}
			replaceNode = deleteNode.leftChild;
			deleteNode.leftChild = null;
		} else if (Objects.nonNull(deleteNode.rightChild)) {
			deleteNode.rightChild.parent = deleteNode.parent;
			if (Objects.nonNull(deleteNode.parent)) {
				if (deleteNode.equals(deleteNode.parent.leftChild))
					deleteNode.parent.leftChild = deleteNode.rightChild;
				else
					deleteNode.parent.rightChild = deleteNode.rightChild;
			}
			replaceNode = deleteNode.rightChild;
			deleteNode.rightChild = null;
		} else {
			if (Objects.nonNull(deleteNode.parent)) {
				if (deleteNode.equals(deleteNode.parent.leftChild))
					deleteNode.parent.leftChild = null;
				else
					deleteNode.parent.rightChild = null;
			}
		}
		deleteNode.parent = null;
		// 如果删除的是根节点,那么this.root要指向替换节点
		if (this.root.equals(deleteNode))
			this.root = replaceNode;
	}

	private void twoChildren(Node<K, V> deleteNode) {
		// deleteNode有两个孩子,找到其右子树最左节点进行替换,此时leftmostNodeOfRightSubtree要么是叶节点要么只有右孩子
		Node<K, V> leftmostNodeOfRightSubtree = findMin(deleteNode.rightChild);
		// 摘除leftmostNodeOfRightSubtree
		oneChildAtMost(leftmostNodeOfRightSubtree);
		// 将leftmostNodeOfRightSubtree替换deleteNode的位置:
		// 1.处理父节点关系
		leftmostNodeOfRightSubtree.parent = deleteNode.parent;
		if (Objects.nonNull(deleteNode.parent)) {
			if (deleteNode.equals(deleteNode.parent.leftChild))
				deleteNode.parent.leftChild = leftmostNodeOfRightSubtree;
			else
				deleteNode.parent.rightChild = leftmostNodeOfRightSubtree;
		}
		deleteNode.parent = null;
		// 虽然node的左右子树非空,但是如果左右子树都只有一个节点,那么oneChildAtMost方法调用完成之后,其左孩子或右孩子变成了null
		// 2.处理左孩子
		leftmostNodeOfRightSubtree.leftChild = deleteNode.leftChild;
		if (Objects.nonNull(deleteNode.leftChild))
			deleteNode.leftChild.parent = leftmostNodeOfRightSubtree;
		deleteNode.leftChild = null;
		// 3.处理右孩子
		leftmostNodeOfRightSubtree.rightChild = deleteNode.rightChild;
		if (Objects.nonNull(deleteNode.rightChild))
			deleteNode.rightChild.parent = leftmostNodeOfRightSubtree;
		deleteNode.rightChild = null;
		// 如果删除的是根节点,那么this.root要指向替换节点
		if (this.root.equals(deleteNode))
			this.root = leftmostNodeOfRightSubtree;
	}
}

2.测试用例

  • 通过insert构建一个二叉查找树;
  • 对于查找操作,需要测试根节点、叶节点、普通节点和不存在的节点;
  • 对于删除操作,需要测试删除根节点、叶节点、两个孩子的节点、一个孩子的节点和不存在的节点;

insert构建的二叉查找树如下图所示:

插入测试:

		BinarySearchTreeNonRecursive<Integer, String> binaryTree = new BinarySearchTreeNonRecursive<Integer, String>();
		binaryTree.insert(62, "62");
		binaryTree.insert(58, "58");
		binaryTree.insert(88, "88");
		binaryTree.insert(47, "47");
		binaryTree.insert(73, "73");
		binaryTree.insert(99, "99");
		binaryTree.insert(35, "35");
		binaryTree.insert(51, "51");
		binaryTree.insert(93, "93");
		binaryTree.insert(37, "37");

		System.out.print("先序遍历:");
		TraversalTreeTool.preorderTraversalByRecursion(binaryTree.getRoot());
		System.out.println();
		System.out.print("中序遍历:");
		TraversalTreeTool.inorderTraversalByRecursion(binaryTree.getRoot());
		System.out.println();
		System.out.print("后序遍历:");
		TraversalTreeTool.postorderTraversalByRecursion(binaryTree.getRoot());
		System.out.println();
		System.out.print("层序遍历:");
		TraversalTreeTool.levelTraversal(binaryTree.getRoot());

先序遍历:62:62  58:58  47:47  35:35  37:37  51:51  88:88  73:73  99:99  93:93  
中序遍历:35:35  37:37  47:47  51:51  58:58  62:62  73:73  88:88  93:93  99:99  
后序遍历:37:37  35:35  51:51  47:47  58:58  73:73  93:93  99:99  88:88  62:62  
层序遍历:62:62  58:58  88:88  47:47  73:73  99:99  35:35  51:51  93:93  37:37  

查询测试:

		BinarySearchTreeNonRecursive<Integer, String> binaryTree = new BinarySearchTreeNonRecursive<Integer, String>();
		binaryTree.insert(62, "62");
		binaryTree.insert(58, "58");
		binaryTree.insert(88, "88");
		binaryTree.insert(47, "47");
		binaryTree.insert(73, "73");
		binaryTree.insert(99, "99");
		binaryTree.insert(35, "35");
		binaryTree.insert(51, "51");
		binaryTree.insert(93, "93");
		binaryTree.insert(37, "37");

		System.out.println(binaryTree.getRoot());
		System.out.println(binaryTree.select(62));
		System.out.println(binaryTree.select(58));
		System.out.println(binaryTree.select(88));
		System.out.println(binaryTree.select(93));
		System.out.println("查询不存在的节点:" + binaryTree.select(-1));

62:62 
62:62 
58:58 
88:88 
93:93 
查询不存在的节点:null

删除测试:

		BinarySearchTreeNonRecursive<Integer, String> binaryTree = new BinarySearchTreeNonRecursive<Integer, String>();
		binaryTree.insert(62, "62");
		binaryTree.insert(58, "58");
		binaryTree.insert(88, "88");
		binaryTree.insert(47, "47");
		binaryTree.insert(73, "73");
		binaryTree.insert(99, "99");
		binaryTree.insert(35, "35");
		binaryTree.insert(51, "51");
		binaryTree.insert(93, "93");
		binaryTree.insert(37, "37");

		System.out.print("删除不存在的节点-1:  ");
		binaryTree.remove(-1);
		TraversalTreeTool.levelTraversal(binaryTree.getRoot());
		System.out.println();
		System.out.print("删除叶节点37: ");
		binaryTree.remove(37);
		TraversalTreeTool.levelTraversal(binaryTree.getRoot());
		System.out.println();
		System.out.print("删除一个孩子节点58: ");
		binaryTree.remove(58);
		TraversalTreeTool.levelTraversal(binaryTree.getRoot());
		System.out.println();
		System.out.print("删除两个孩子节点88: ");
		binaryTree.remove(88);
		TraversalTreeTool.levelTraversal(binaryTree.getRoot());
		System.out.println();
		System.out.print("删除根节点62: ");
		binaryTree.remove(62);
		TraversalTreeTool.levelTraversal(binaryTree.getRoot());

删除不存在的节点-1:  62:62  58:58  88:88  47:47  73:73  99:99  35:35  51:51  93:93  37:37  
删除叶节点37: 62:62  58:58  88:88  47:47  73:73  99:99  35:35  51:51  93:93  
删除一个孩子节点58: 62:62  47:47  88:88  35:35  51:51  73:73  99:99  93:93  
删除两个孩子节点88: 62:62  47:47  93:93  35:35  51:51  73:73  99:99  
删除根节点62: 73:73  47:47  93:93  35:35  51:51  99:99  


四、更多实现

递归:

package cn.wxy.blog2;

import java.util.Objects;
import cn.wxy.blog2.TraversalTreeTool.TreeNode;

/**
 * 递归实现二叉查找树
 * @author 王大锤
 * @date 2021年6月15日
 */
public class BinarySearchTree<K extends Comparable<K>, V> {
	static class Node<K extends Comparable<K>, V> implements TreeNode<K, Node<K, V>> {
		private K key;
		private V value;
		private Node<K, V> parent;
		private Node<K, V> leftChild;
		private Node<K, V> rightChild;

		public Node() {
		}

		public Node(K key, V value, Node<K, V> parent) {
			this.key = key;
			this.value = value;
			this.parent = parent;
		}

		public K getKey() {
			return key;
		}

		public void setKey(K key) {
			this.key = key;
		}

		public Node<K, V> getParent() {
			return parent;
		}

		public void setParent(Node<K, V> parent) {
			this.parent = parent;
		}

		public Node<K, V> getLeftChild() {
			return leftChild;
		}

		public void setLeftChild(Node<K, V> leftChild) {
			this.leftChild = leftChild;
		}

		public Node<K, V> getRightChild() {
			return rightChild;
		}

		public void setRightChild(Node<K, V> rightChild) {
			this.rightChild = rightChild;
		}

		@Override
		public String toString() {
			return this.key + ":" + value + " ";
		}
	}

	private Node<K, V> root;

	public Node<K, V> getRoot() {
		return this.root;
	}

	public BinarySearchTree() {
	}

	public Node<K, V> select(K key) {
		if (Objects.isNull(key))
			throw new NullPointerException();
		return select(key, this.root);
	}

	private Node<K, V> select(K key, Node<K, V> node) {
		if (Objects.isNull(node))
			return null;
		int compare = key.compareTo(node.key);
		if (compare < 0)
			return node.leftChild = select(key, node.leftChild);
		else if (compare > 0)
			return node.rightChild = select(key, node.rightChild);
		else
			return node;
	}

	public void insert(K key, V value) {
		if (Objects.isNull(key))
			throw new NullPointerException();
		this.root = insert(key, value, this.root, null);
	}

	private Node<K, V> insert(K key, V value, Node<K, V> node, Node<K, V> parent) {
		if (Objects.isNull(node))
			return new Node<K, V>(key, value, parent);
		int compare = key.compareTo(node.key);
		if (compare < 0)
			node.leftChild = insert(key, value, node.leftChild, node);
		else if (compare > 0)
			node.rightChild = insert(key, value, node.rightChild, node);
		else
			node.value = value;
		return node;
	}

	public void remove(K key) {
		if (Objects.isNull(key))
			throw new NullPointerException();
		this.root = remove(key, this.root);
	}

	private Node<K, V> remove(K key, Node<K, V> node) {
		if (Objects.isNull(node))
			return null;
		int compare = key.compareTo(node.key);
		if (compare < 0)
			node.leftChild = remove(key, node.leftChild);
		else if (compare > 0)
			node.rightChild = remove(key, node.rightChild);
		else {
			// 通过复制key-value来删除节点
			if (Objects.nonNull(node.leftChild) && Objects.nonNull(node.rightChild)) {
				// 待删除节点左右孩子非空:
				// 1.找到右子树最左节点;
				// 2.摘除右子树最左节点,右子树最左节点要么是叶节点,要么只有右孩子,摘除逻辑就是else代码
				// 3.将右子树最左节点的key-value拷贝到待删除结点,在节点值上完成删除操作,而不是替换两个node逻辑结构
				Node<K, V> leftOfRightSubtree = findMin(node.rightChild);
				K k = leftOfRightSubtree.key;
				V v = leftOfRightSubtree.value;
				node.rightChild = remove(leftOfRightSubtree.key, node.rightChild);
				node.key = k;
				node.value = v;
			} else {
				// 待删除节点最多有一个孩子:
				// 1.左孩子不空,则将左孩子的key-value、左右孩子拷贝覆盖待删除结点,断开引用
				// 2.右孩子不空,则将右孩子的key-value、左右拷贝覆盖待删除结点,断开引用
				// 3.左右孩子都空,断开待删除节点和父节点之间的引用
				Node<K, V> source = null;
				if (Objects.nonNull(node.leftChild)) {
					source = node.leftChild;
					source.parent = null;
					node.leftChild = null;
					replace(source, node);
				} else if (Objects.nonNull(node.rightChild)) {
					source = node.rightChild;
					source.parent = null;
					node.rightChild = null;
					replace(source, node);
				} else {
					node.parent = null;
					node = null;
				}
			}
		}
		return node;
	}

	/**
	 * 将source的key-value、leftChild、rightChild拷贝到dest中,dest将替换source
	 * @param soruce
	 * @param dest
	 */
	private void replace(Node<K, V> source, Node<K, V> dest) {
		dest.key = source.key;
		dest.value = source.value; 
		dest.leftChild = source.leftChild;
		dest.rightChild = source.rightChild;
		if(Objects.nonNull(source.leftChild))
			source.leftChild.parent = dest;
		if(Objects.nonNull(source.rightChild))
			source.rightChild.parent = dest;
		source.leftChild = null;
		source.rightChild = null;
	}

	private Node<K, V> findMin(Node<K, V> node) {
		if (Objects.isNull(node))
			throw new NullPointerException();
		Node<K, V> min = null;
		while (Objects.nonNull(node)) {
			min = node;
			node = node.leftChild;
		}
		return min;
	}
}

非递归:

package cn.wxy.blog2;

import java.util.Objects;
import cn.wxy.blog2.TraversalTreeTool.TreeNode;

/**
 * 二叉查找树的非递归实现
 * @author 王大锤
 * @date 2021年6月15日
 */
public class BinarySearchTreeNonRecursive<K extends Comparable<K>, V> {
	static class Node<K extends Comparable<K>, V> implements TreeNode<K, Node<K, V>> {
		private K key;
		private V value;
		private Node<K, V> parent;
		private Node<K, V> leftChild;
		private Node<K, V> rightChild;

		public Node() {
		}

		public Node(K key, V value, Node<K, V> parent) {
			this.key = key;
			this.value = value;
			this.parent = parent;
		}

		public K getKey() {
			return key;
		}

		public void setKey(K key) {
			this.key = key;
		}

		public Node<K, V> getParent() {
			return parent;
		}

		public void setParent(Node<K, V> parent) {
			this.parent = parent;
		}

		public Node<K, V> getLeftChild() {
			return leftChild;
		}

		public void setLeftChild(Node<K, V> leftChild) {
			this.leftChild = leftChild;
		}

		public Node<K, V> getRightChild() {
			return rightChild;
		}

		public void setRightChild(Node<K, V> rightChild) {
			this.rightChild = rightChild;
		}

		@Override
		public String toString() {
			return this.key + ":" + value + " ";
		}
	}

	private Node<K, V> root;

	public Node<K, V> getRoot() {
		return this.root;
	}

	public BinarySearchTreeNonRecursive() {
	}

	/**
	 * select/insert/remove都需要用到这个方法
	 * 1.如果是空树,则返回null
	 * 2.如果树中包含key节点,则返回该节点
	 * 3.如果树中没有key节点,则返回最后一次查找的节点
	 * @param key
	 * @return
	 */
	private Node<K, V> selectNode(K key) {
		Node<K, V> current = null;
		Node<K, V> find = this.root;
		while (Objects.nonNull(find)) {
			current = find;
			int compare = key.compareTo(find.key);
			if (compare < 0)
				find = find.leftChild;
			else if (compare > 0)
				find = find.rightChild;
			else
				break;
		}
		return current;
	}

	public Node<K, V> select(K key) {
		if (Objects.isNull(key))
			throw new NullPointerException();
		Node<K, V> node = selectNode(key);
		if (Objects.nonNull(node) && node.key.equals(key))
			return node;
		return null;
	}

	public void insert(K key, V value) {
		if (Objects.isNull(key))
			throw new NullPointerException();
		Node<K, V> current = selectNode(key);
		if (Objects.isNull(current)) {
			this.root = new Node<K, V>(key, value, null);
			return;
		}
		int compare = key.compareTo(current.key);
		if (compare < 0)
			current.leftChild = new Node<K, V>(key, value, current);
		else if (compare > 0)
			current.rightChild = new Node<K, V>(key, value, current);
		else
			current.value = value;
	}

	/**
	 * 两个孩子:结构不会改变,只需要删掉右子树最左节点(这个删除操作属于最多一个孩子情况),然后把右子树最左节点的key-value拷贝到待删除结点即可
	 * 最多一个孩子:结构会改变,所以需要把唯一的孩子的key-value、左右子树都拷贝到待删除节点,然后剔除孩子
	 * @param key
	 */
	public void remove(K key) {
		if (Objects.isNull(key))
			throw new NullPointerException();
		Node<K, V> node = selectNode(key);
		if (Objects.isNull(node) || key.compareTo(node.key) != 0)
			return;
		if (Objects.nonNull(node.leftChild) && Objects.nonNull(node.rightChild)) {
			// 待删除节点左右孩子非空:
			// 1.找到右子树最左节点;
			// 2.摘除右子树最左节点,右子树最左节点要么是叶节点,要么只有右孩子,摘除逻辑就是else代码
			// 3.将右子树最左节点的key-value拷贝到待删除结点,在节点值上完成删除操作,而不是替换两个node逻辑结构
			Node<K, V> leftOfRightSubtree = findMin(node.rightChild);
			K k = leftOfRightSubtree.key;
			V v = leftOfRightSubtree.value;
			remove(leftOfRightSubtree.key);
			node.key = k;
			node.value = v;
		} else {
			// 待删除节点最多有一个孩子:
			// 1.左孩子不空,则将左孩子的key-value、左右孩子拷贝覆盖待删除结点,断开引用
			// 2.右孩子不空,则将右孩子的key-value、左右拷贝覆盖待删除结点,断开引用
			// 3.左右孩子都空,断开待删除节点和父节点之间的引用
			Node<K, V> source = null;
			if (Objects.nonNull(node.leftChild)) {
				source = node.leftChild;
				source.parent = null;
				node.leftChild = null;
				replace(source, node);
			} else if (Objects.nonNull(node.rightChild)) {
				source = node.rightChild;
				source.parent = null;
				node.rightChild = null;
				replace(source, node);
			} else {
				if (Objects.nonNull(node.parent)) {
					if (node.equals(node.parent.leftChild))
						node.parent.leftChild = null;
					else
						node.parent.rightChild = null;
					node.parent = null;
				} else
					this.root = null;
			}
		}
	}

	/**
	 * 将source的key-value、leftChild、rightChild拷贝到dest中,dest将替换source
	 * @param soruce
	 * @param dest
	 */
	private void replace(Node<K, V> source, Node<K, V> dest) {
		dest.key = source.key;
		dest.value = source.value;
		dest.leftChild = source.leftChild;
		dest.rightChild = source.rightChild;
		if (Objects.nonNull(source.leftChild))
			source.leftChild.parent = dest;
		if (Objects.nonNull(source.rightChild))
			source.rightChild.parent = dest;
		source.leftChild = null;
		source.rightChild = null;
	}

	private Node<K, V> findMin(Node<K, V> node) {
		if (Objects.isNull(node))
			throw new NullPointerException();
		Node<K, V> min = null;
		while (Objects.nonNull(node)) {
			min = node;
			node = node.leftChild;
		}
		return min;
	}
}


参考资料:

  • 《大话数据结构》
  • 《数据结构与算法分析:Java语言描述》
  • 《算法导论》
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值