二叉搜索树的概述
二叉搜索树(BST,Binary Search Tree),也称二叉排序树 或 二叉查找树。
二叉搜索树要么是棵空树,要么是棵满足下列性质的二叉树:
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树。
- 没有键值相等的节点。
BST的实现
🎶树布局(节点)
这里运用泛型,树节点只接受实现了Comparable接口的对象。包装类、String类、
自身创建的类对象等等。
public class BSTree<T extends Comparable<T>> {
/**
* BST的根节点
*/
private BSTNode<T> root;
......
class BSTNode<T extends Comparable<T>> {
T value;
BSTNode<T> leftNode;
BSTNode<T> rightNode;
BSTNode<T> parentNode;
public BSTNode(T value, BSTNode<T> leftNode, BSTNode<T> rightNode, BSTNode<T> parentNode) {
this.value = value;
this.leftNode = leftNode;
this.rightNode = rightNode;
this.parentNode = parentNode;
}
}
}
🎶插入
向二叉搜索树(BST)中插入结点,插入的节点是叶子节点(根据BST的性质,如果仍存在左右节点的话还会进行比较,直到比较结束最后进行插入)。
(后续的测试代码,树节点都是按这个来初始化的)
代码实现:
public void insert(T v){
BSTNode<T> insertNode = new BSTNode<>(v,null,null,null);
if (insertNode!=null) {
insert(this,insertNode);
}
}
/**
* 插入某个节点
* @param tst 根节点
* @param insertNode 插入的节点
*/
private void insert(BSTree<T> tst, BSTNode<T> insertNode){
int cmpAns;
BSTNode<T> moveNode = tst.root;
BSTNode<T> preNode = null;
while(moveNode!=null){
preNode = moveNode;
cmpAns = insertNode.value.compareTo(moveNode.value);
if(cmpAns<0){
moveNode = moveNode.leftNode;
}else{
moveNode = moveNode.rightNode;
}
}
if(preNode==null){
root = insertNode;
}else{
insertNode.parentNode = preNode;
int comAns = insertNode.value.compareTo(preNode.value);
if (comAns < 0) {
preNode.leftNode = insertNode;
}else{
preNode.rightNode = insertNode;
}
}
}
🎶遍历
由于二叉搜索树也称二叉排序树,这只使用中序遍历,使得最后结果序列是有序的。
利用递归的方式二叉搜索树进行中序遍历:
/**
* 中序遍历
* @return 有序节点序列
*/
public List<T> inOrder(){
List<T> bstNodes = new ArrayList<>();
inOrder(root,bstNodes);
return bstNodes;
}
private void inOrder(BSTNode<T> root, List<T> bstNodes) {
if(root!=null){
inOrder(root.leftNode,bstNodes);
bstNodes.add(root.value);
inOrder(root.rightNode,bstNodes);
}
}
🎶查找
利用递归或者非递归的方式进行查找,没查找到返回null,查找到了返回对应的节点。
(递归和非递归实现也没啥难,区别也不大,这里只用递归实现一下)
递归实现代码:
/**
* 二叉搜索树的查找操作
* @param root 移动节点,被查找对象
* @param v 查找值
* @return
*/
private BSTNode<T> search(BSTNode<T> root, T v) {
if(root!=null) {
int cmp = v.compareTo(root.value);
if(cmp<0){
return search(root.leftNode,v);
}else if(cmp>0){
return search(root.rightNode,v);
}else{
return root;
}
}
return null;
}
测试:
🎶删除
删除BST 的节点,该节点类型无非分为三类:
1. 为叶子节点,无左右子孩子;
2. 有左孩子或者右孩子;
3. 左右孩子都有。
咱逐一击破即可,注意删除节点的父节点和孩子节点的更换,
对于左右子孩子都存在的节点,使用值传递进行假删除。
public BSTNode<T> findMaxNode(BSTNode<T> node){
BSTNode<T> target = node;
while(target.rightNode!=null){
target = target.rightNode;
}
return target;
}
public void remove(T v){
BSTNode<T> s, node;
if((s=this.search(v))!=null) {
if((node=remove(this,s))!=null) {
node = null;
}
}
}
private BSTNode<T> remove(BSTree<T> tbsTree, BSTNode<T> s) {
BSTNode<T> node = null;
if(s.leftNode==null&&s.rightNode==null){// 情况1:s为叶子节点
if(s.parentNode!=null&&s.parentNode.leftNode!=null&&s.parentNode.leftNode.value==s.value){
s.parentNode.leftNode = null;
}else if(s.parentNode!=null&&s.parentNode.rightNode!=null&&s.parentNode.rightNode.value==s.value){
s.parentNode.rightNode = null;
}
return s;
}else if(s.leftNode!=null&&s.rightNode!=null){// 情况2:s有左右子节点
node = findMaxNode(s.leftNode);
remove(this,node);// 这查到的最后是个叶子节点,更新父节点子节点。
s.value = node.value;
}else{// 情况3:s有左或者右子节点
if(s.parentNode==null) {
this.root = (s.leftNode == null) ? s.rightNode : s.leftNode;
}else if(s.rightNode==null){
if (s.parentNode.leftNode.value==s.value) {
s.parentNode.leftNode = s.leftNode;
}else{
s.parentNode.rightNode = s.leftNode;
}
}else if(s.leftNode==null){
if (s.parentNode.leftNode.value==s.value) {
s.parentNode.leftNode = s.rightNode;
}else{
s.parentNode.rightNode = s.rightNode;
}
}
node = s;
}
return node;
}
测试:
BSF相关题目
通过刷算法题,可以对BSF 性质更加了解,对遍历操作更加熟悉。