【数据结构】二叉排序树BST 二叉排序树 相关算法 BST的实现 测试

二叉排序树(Binary Sort Tree)也叫二叉搜索树(Binary Search Tree)

二叉排序树本质上还是一个二叉树,只不过在其上定义了一些规则:一个结点的左子树中所有的结点不大于该结点的值,而其右子树中的所有结点不小于该结点的值。由此规则可得BST中序遍历是有序的。

BST中定义的操作有:

minNode:某个子树中的关键字最小的结点

maxNode:某个子树中的关键字最大的结点

search:搜索某个关键字

predecessor:某个节点的前驱

successor:某个节点的后继

insert:在BST中插入一个元素

delete:在BST中删除一个元素

研究二叉排序树最主要的就是上述操作在BST中的时间复杂度为O(h),h为树高。

相关算法

1.某棵子树中关键字最小的结点
    由BST的性质,沿着该结点的左分支往下走到最后一个结点

public BinaryTreeNode minNode(BinaryTreeNode node) {
		while (node.lchild != null) {
			node = node.lchild;
		}
		return node;
	}

2.某棵子树中关键字最大的结点
    由BST的性质,沿着该结点的右分支往下走到最后一个结点

public BinaryTreeNode maxNode(BinaryTreeNode node) {
		while (node.rchild != null) {
			node = node.rchild;
		}
		return node;
	}

3.搜索
    BST中的搜索类似于折半查找,因为BST的中序遍历就是一个有序数组。

public BinaryTreeNode search(BinaryTreeNode root, int e) {
		if (e == root.data || root == null)
			return root;
		if (e < root.data)
			return search(root.lchild, e);
		else
			return search(root.rchild, e);
	}

4.某个结点的前驱
    如有左子树,那么返回左子树中的最小结点即可。
    无左子树,沿着父节点往上走,知道到达这样的结点:其父节点为空(到达根)或者作为某个结点的右子树。

public BinaryTreeNode predecessor(BinaryTreeNode node) {
		if (node.lchild != null)
			return maxNode(node.lchild);
		BinaryTreeNode parent = node.parent;
		while (parent != null && parent.lchild == node) {
			node = parent;
			parent = node.parent;
		}
		return parent;
	}
    
5.某个结点的后继前驱的算法类似

具体思路如下

【数据结构】二叉排序树BST
二叉排序树
相关算法
BST的实现
测试

6.在BST中插入一个元素
    和查找类似,只是要注意保存好父结点,x从根开始下移,直到x为空,而此时y就是他的父结点。

具体思路如下

【数据结构】二叉排序树BST
二叉排序树
相关算法
BST的实现
测试

具体的实现代码为

public void insert(BinaryTreeNode insertNode) { // O(lgn)
		BinaryTreeNode p = root, parent = null;
		while (p != null) {
			parent = p; // 注意位置
			if (insertNode.data < p.data)
				p = p.lchild;
			else
				p = p.rchild;
		} // 出循环时,p为null,parent为要插入在该节点下面
		insertNode.parent = parent;
		if (root == null) {
			root = insertNode;
		} else if (insertNode.data < parent.data) {
			// 将节点插入到parent的左边
			parent.lchild = insertNode;
		} else {
			parent.rchild = insertNode;
		}
	}

插入结点的递归版本
    这里我将跳出递归的条件设置为root.lchild/rchild == null,好处就是直接在root后面添加孩子就行了,不用再额外保存父亲结点。

public void insertRecur(BinaryTreeNode root, BinaryTreeNode insertNode) { 
		// 递归终止条件
		if (this.root == null) {
			this.root = insertNode;
			return;
		} else if (insertNode.data < root.data && root.lchild == null) {
			root.lchild = insertNode;
			insertNode.parent = root;
			return;
		} else if (insertNode.data >= root.data && root.rchild == null) {
			root.rchild = insertNode;
			insertNode.parent = root;
			return;
		}
		// 递归的往左右孩子插入
		if (insertNode.data < root.data && root.lchild != null)
			insertRecur(root.lchild,insertNode);
		if (insertNode.data >= root.data && root.rchild != null) {
			insertRecur(root.rchild,insertNode);
		}
	}
同时,通过插入方法也可以创建BST。只需循环的往里面插入节点即可。


7.在BST中删除一个元素 

    1.删除叶子结点----->仅需调整其父结点的指针
    2.只有左子树或者右子树
      2.1 只有左孩子:用它的左子树“代替”它。
      2.2 只有右孩子:用右子树“代替”它。
    3.既有左孩子又有右孩子:用它的左或右子树代替它,同时将它的右子树放在它的前驱的右子树位置。

具体情况如下图

【数据结构】二叉排序树BST
二叉排序树
相关算法
BST的实现
测试


注意上面提到的“代替”,代替并不简单的是node = node.lchild这样的一句赋值,实际上要包含以下操作:

    1.如果该结点是它父结点的左孩子,那么,将他的父结点的左孩子赋值为另一个结点

    2.如果该结点是它父结点的右孩子,那么,将他的父结点的右孩子赋值为另一个结点

    3.最后将用来代替的那个结点的父亲设置为被代替节点的父亲

    此外还要注意被替换结点没有父亲节点的情况,也就是删除根。

下面是我最开始写出来的代码,其实看起来非常的混乱

public void deleted(BinaryTreeNode node) {
		if (node.lchild == null) { // 左子树为空,只需要移植右子树
			// -------1.用该结点的右子树代替它-------
			// node = node.rchild;
			if (node.parent == null) {
				root = node.rchild;
			} else if (node == node.parent.lchild) {
				node.parent.lchild = node.rchild;
			} else {
				node.parent.rchild = node.rchild;
			}
			if (node.rchild != null)
				node.rchild.parent = node.parent;
		} else if (node.rchild == null) {
			// -------2.用该结点的左子树代替它-------
			// node = node.lchild;
			if (node.parent == null) {
				root = node.lchild;
			} else if (node == node.parent.lchild) {
				node.parent.lchild = node.lchild;
			} else {
				node.parent.rchild = node.lchild;
			}
			if (node.lchild != null)
				node.lchild.parent = node.parent;
		} else {
			BinaryTreeNode predecessorOfnode = predecessor(node);
			// -------3.用该结点的右子树代替它-------
			if (node.parent == null) {
				root = node.lchild;
			} else if (node.parent.lchild == node) {
				node.parent.lchild = node.lchild;
			} else if (node.parent.rchild == node) {
				node.parent.rchild = node.lchild;
			}
			node.lchild.parent = node.parent;
			node.rchild.parent = predecessorOfnode;


			// -------4.将右子树赋值给它的前驱-------
			predecessorOfnode.rchild = node.rchild;
			node.rchild.parent = predecessorOfnode;
		}
	}
上面的代码存在大量的重复,将其抽出来一个替换的方法
// 用node2来替代node1
private void replace(BinaryTreeNode node1, BinaryTreeNode node2){
		// node2可以为null,这样就将删除叶子结点的情况包含在里面了
		if (node1.parent == null) {
			root = node2;
		} else if (node1 == node1.parent.lchild) {
			node1.parent.lchild = node2;
		} else {
			node1.parent.rchild = node2;
		}
		if (node2 != null)
			node2.parent = node1.parent;
	}
于是删除的方法可以写为
public void delete(BinaryTreeNode node) {
		if (node.lchild == null) { // 左子树为空,只需要移植右子树
			replace(node, node.rchild);
		} else if (node.rchild == null) {
			replace(node, node.lchild);
		} else { 
			BinaryTreeNode predecessorOfnode = predecessor(node);
			replace(node, node.lchild);
			
			predecessorOfnode.rchild = node.rchild;
			node.rchild.parent = predecessorOfnode;
		}
	}	


BST的实现

继承上一篇博客的BinaryTree类,这样就可以调用遍历算法了

public class BinarySearchTree extends BinaryTree {
	// 从数组创建二叉树
	@Override
	public void createTree(int[] array) { // 最多是O(nlgn)
		// 从一个数组创建二叉搜索树
		for (int i : array) {
			insertRecur(root, new BinaryTreeNode(i));
		}
	}
	// 某个子树的最小结点
	public BinaryTreeNode minNode(BinaryTreeNode node) {
		while (node.lchild != null) {
			node = node.lchild;
		}
		return node;
	}
	
	//某个子树的最大结点
	public BinaryTreeNode maxNode(BinaryTreeNode node) {
		while (node.rchild != null) {
			node = node.rchild;
		}
		return node;
	}

	// 搜索关键字为e的结点,并返回该结点的引用
	public BinaryTreeNode search(BinaryTreeNode root, int e) {
		if (root == null || e == root.data) // 注意顺序
			return root;
		if (e < root.data)
			return search(root.lchild, e);
		else
			return search(root.rchild, e);
	}
	
	// 插入结点,递归版本
	public void insertRecur(BinaryTreeNode root, BinaryTreeNode insertNode) { // O(lgn)
		if (this.root == null) {
			this.root = insertNode;
			return;
		} else if (insertNode.data < root.data && root.lchild == null) {
			root.lchild = insertNode;
			insertNode.parent = root;
			return;
		} else if (insertNode.data >= root.data && root.rchild == null) {
			root.rchild = insertNode;
			insertNode.parent = root;
			return;
		}
		
		if (insertNode.data < root.data && root.lchild != null)
			insertRecur(root.lchild,insertNode);
		if (insertNode.data >= root.data && root.rchild != null) {
			insertRecur(root.rchild,insertNode);
		}
	}
	
	// 插入节点,非递归版本
	public void insert(BinaryTreeNode insertNode) { // O(lgn)
		BinaryTreeNode p = root, parent = null;
		while (p != null) {
			parent = p; // 注意位置
			if (insertNode.data < p.data)
				p = p.lchild;
			else
				p = p.rchild;
		} // 出循环时,p为null,parent为要插入在该节点下面
		insertNode.parent = parent;
		if (root == null) {
			root = insertNode;
		} else if (insertNode.data < parent.data) {
			// 将节点插入到parent的左边
			parent.lchild = insertNode;
		} else {
			parent.rchild = insertNode;
		}
	}
	
	// 用node2来替代node1
	private void replace(BinaryTreeNode node1, BinaryTreeNode node2){
		// node2可以为null,这样就将删除叶子结点的情况包含在里面了
		if (node1.parent == null) {
			root = node2;
		} else if (node1 == node1.parent.lchild) {
			node1.parent.lchild = node2;
		} else {
			node1.parent.rchild = node2;
		}
		if (node2 != null)
			node2.parent = node1.parent;
	}
	
	// 删除某个结点
	public void delete(BinaryTreeNode node) {
		if (node == null) return;
		if (node.lchild == null) { // 左子树为空,只需要移植右子树
			replace(node, node.rchild);
		} else if (node.rchild == null) {
			replace(node, node.lchild);
		} else { 
			BinaryTreeNode predecessorOfnode = predecessor(node);
			replace(node, node.lchild);
			
			predecessorOfnode.rchild = node.rchild;
			node.rchild.parent = predecessorOfnode;
		}
	}	
	
	// 删除某个结点
	@Deprecated
	public void deleted(BinaryTreeNode node) {
		if (node == null) return;
		if (node.lchild == null) { // 左子树为空,只需要移植右子树
			// -------1.用该结点的右子树代替它-------
			// node = node.rchild;
			if (node.parent == null) {
				root = node.rchild;
			} else if (node == node.parent.lchild) {
				node.parent.lchild = node.rchild;
			} else {
				node.parent.rchild = node.rchild;
			}
			if (node.rchild != null)
				node.rchild.parent = node.parent;
		} else if (node.rchild == null) {
			// -------2.用该结点的左子树代替它-------
			// node = node.lchild;
			if (node.parent == null) {
				root = node.lchild;
			} else if (node == node.parent.lchild) {
				node.parent.lchild = node.lchild;
			} else {
				node.parent.rchild = node.lchild;
			}
			if (node.lchild != null)
				node.lchild.parent = node.parent;
		} else {
			BinaryTreeNode predecessorOfnode = predecessor(node);
			// -------3.用该结点的右子树代替它-------
			if (node.parent == null) {
				root = node.lchild;
			} else if (node.parent.lchild == node) {
				node.parent.lchild = node.lchild;
			} else if (node.parent.rchild == node) {
				node.parent.rchild = node.lchild;
			}
			node.lchild.parent = node.parent;
			node.rchild.parent = predecessorOfnode;

			// -------4.将右子树赋值给它的前驱-------
			predecessorOfnode.rchild = node.rchild;
			node.rchild.parent = predecessorOfnode;
		}
	}

	// 返回某个节点node的前驱
	public BinaryTreeNode predecessor(BinaryTreeNode node) {
		if (node.lchild != null)
			return maxNode(node.lchild);
		BinaryTreeNode parent = node.parent;
		while (parent != null && parent.lchild == node) {
			node = parent;
			parent = node.parent;
		}
		return parent;
	}

	// 返回某个节点node的后继
	public BinaryTreeNode successor(BinaryTreeNode node) {
		if (node.rchild != null)
			return minNode(node.rchild);
		BinaryTreeNode parent = node.parent;
		while (parent != null && parent.rchild == node) {
			node = parent;
			parent = node.parent;
		}
		return parent;
	}
}


测试

public static void main(String[] args) {
		BinarySearchTree bst = new BinarySearchTree();
		int[] array = {1, 9, 2, 7, 4, 5, 3, 6, 8};
		bst.createTree(array);
		System.out.println("中序递归遍历");
		bst.inOrder(bst.root);
		System.out.println("");
		System.out.println("层序遍历");
		bst.levelOrderH();
		bst.delete(bst.search(bst.root, 4));
		System.out.println("将4删除后在中序递归遍历");
		bst.inOrder(bst.root);
	}

输出

中序递归遍历
1  2  3  4  5  6  7  8  9  
层序遍历
1  
9  
2  
7  
4  8  
3  5  
6  

将4删除后在中序递归遍历
1  2  3  5  6  7  8  9  


上面这棵树的形态大致如下

【数据结构】二叉排序树BST
二叉排序树
相关算法
BST的实现
测试

可以清楚地看到,虽然这棵树严格满足BST的要求,但是这棵树是比较深的,而上述操作的时间复杂度直接与树的深度有关,也就是说如果创建的树高度太高,就会直接影响到BST上的操作的性能,因为上述操作的时间复杂度均为O(h),不要想当然地认为O(h)就是O(lgn)。像上面的这棵树,我们说他是不平衡的,平衡看起来,直观感受是做右子树高度差不多。平衡的二叉排序树即BBST,它的平均深度才为O(lgn),就能将上述操作的时间复杂度控制在O(lgn)以内。