前面我们介绍了二分查找法,它可以大幅度的提升查找效率,但是前提是必须基于有序数组
而至于无序的一组数,我们可以不采用数组,而是采用另种新的数据结构:二叉搜索树
顾名思义,二叉搜索树就是一种特殊的二叉树,其每个节点都要符合以下两个要求:
下面是二叉搜索树的Java实现:
类介绍:该类为泛型类,且继承了java.lang.Comparable接口,这个接口是 Java 中的一个泛型接口,用于实现对象的自然排序(natural ordering)。它包含一个抽象方法 compareTo(T obj)
,用于比较当前对象与传入对象的大小。这个方法返回一个整数值,表示当前对象与传入对象的关系。通常的规则是:
BSTTree2
类包含两个泛型类型参数 K
和 V
,分别表示二叉搜索树中键(key)和值(value)的类型。类中还包含一个嵌套类 BSTTreeNode
,用于表示二叉搜索树的节点。每个节点包含一个键、一个值、一个左子节点和一个右子节点。
put(final K key, final V value)
方法用于向二叉搜索树中插入一个新节点。如果键已经存在,则更新对应的值。
get(final K key)
方法用于查找给定键对应的值。getMin()
方法用于查找二叉搜索树中的最小值。getMax()
方法用于查找二叉搜索树中的最大值。remove(final K key)
方法用于删除给定键对应的节点。如果键不存在,它将不做任何操作。删除操作包括三种情况:节点没有左子节点、节点没有右子节点、节点既有左子节点又有右子节点。对于有两个子节点的情况,它找到节点的后继节点来进行替换。
getPre(final K key)
方法用于查找给定键的前驱值。getSucc(final K key)
方法用于查找给定键的后继值。代码实现:
public class BSTTree2,V>
{
public static class BSTTreeNode
{
K key;//键
V value;//值
BSTTreeNode left;//左子节点
BSTTreeNode right;//右子节点
/**
* 构造方法
* @param key 键
*/
public BSTTreeNode(final K key)
{
this.key = key;
}
/**
* 构造方法
* @param key 键
* @param value 值
*/
public BSTTreeNode(final K key, final V value)
{
this.key = key;
this.value = value;
}
/**
* 构造方法
* @param key 键
* @param value 值
* @param left 左子节点
* @param right 右子节点
*/
public BSTTreeNode(final K key, final V value, final BSTTreeNode left, final BSTTreeNode right)
{
this.key = key;
this.value = value;
this.left = left;
this.right = right;
}
}
/**
* 设置根节点
* @param root 根节点
*/
public void setRoot(final BSTTreeNode root)
{
this.root = root;
}
private BSTTreeNode root;//根节点
/**
* 查找关键字对应的值
* @param key
* @return 关键字对应的值
*/
public V get(final K key)
{
if (key == null)
{
throw new IllegalArgumentException("key不能为null");
}
BSTTreeNode node = root;
while (node != null)
{
int result = key.compareTo(node.key);//比较传入的key和当前节点的key,compareTo方法是Comparable接口的方法
// 如果相等返回0,如果传入的key大于当前节点的key返回正数,如果传入的key小于当前节点的key返回负数
if (result < 0)//如果传入的key小于当前节点的key
{
node = node.left;//向左子树查找
}
else if (result > 0)//如果传入的key大于当前节点的key
{
node = node.right;//向右子树查找
}
else//如果传入的key等于当前节点的key
{
return node.value;//返回当前节点的值
}
}
return null;//如果没找到返回null
}
/**
* 查找最小关键字对应值
* @return 最小关键字对应值
*/
public V getMin()
{
return getMin(root);
}
private V getMin(BSTTreeNode node)
{
if (node == null)
{
return null;
}
//找最小关键字对应值,只要一直向左子树查找就行了,最后一个左子节点的值就是最小关键字对应值
while (node.left != null)
{
node = node.left;
}
return node.value;
}
/**
* 查找最大关键字对应值
* @return 最大关键字对应值
*/
public V getMax()
{
return getMax(root);
}
private V getMax(BSTTreeNode node)
{
if (node == null)
{
return null;
}
//找最大关键字对应值,只要一直向右子树查找就行了,最后一个右子节点的值就是最大关键字对应值
while (node.right != null)
{
node = node.right;
}
return node.value;
}
/**
* 储存关键字和对应值,如果键已经存在,则更新值,没有就添加新节点
* @param key 关键字
* @return 对应值
*/
public void put(final K key, final V value)
{
BSTTreeNode node = root;
BSTTreeNode parent = null;//旨在记录node的父节点,如果键不存在,则添加新节点时需要用到
while (node != null)
{
parent = node;
int result = key.compareTo(node.key);
if (result < 0)
{
node = node.left;
}
else if (result > 0 )
{
node = node.right;
}
else
{
node.value = value;//如果键已经存在,则更新值
return;
}
}
//如果键不存在,则添加新节点
BSTTreeNode newNode = new BSTTreeNode<>(key, value);//新节点
if (parent == null)//如果树为空,parent为null就意味没有进行过while循环,也就是树为空
{
root = newNode;
return;
}
int result = key.compareTo(parent.key);//比较新节点的key和parent的key
if (result < 0)//如果新节点的key小于parent的key
{
parent.left = newNode;//新节点作为parent的左子节点
}
else //不可能等于,因为前面已经判断过键不存在,直接else就行
{
parent.right = newNode;//新节点作为parent的右子节点
}
}
/**
* 查找关键字的前驱值
* @param key 关键字
* @return 关键字的前驱值
*/
public V getPre(final K key)
{
BSTTreeNode node = root;
BSTTreeNode ancestorFromLeft = null;//自左而来的祖先节点
while (node != null)
{
int result = key.compareTo(node.key);
if (result < 0)
{
node = node.left;
}
else if (result > 0)
{
ancestorFromLeft = node;//更新自左而来的祖先节点
node = node.right;
}
else
{
break;
}
}
if (node == null)
{
return null;
}
else if (node.left != null)//如果有左子节点,就找左子树的最大值
{
return getMax(node.left);
}
else//如果没有左子节点,就返回自左而来的祖先节点的值
{
return ancestorFromLeft == null ? null : ancestorFromLeft.value;
}
}
/**
* 查找关键字的后继值
* @param key 关键字
* @return 关键字的后继值
*/
public V getSucc(final K key)
{
BSTTreeNode node = root;
BSTTreeNode ancestorFromRight = null;//自右而来的祖先节点
while (node != null)
{
int result = key.compareTo(node.key);
if (result < 0)
{
ancestorFromRight = node;//更新自右而来的祖先节点
node = node.left;
}
else if (result > 0)
{
node = node.right;
}
else
{
break;
}
}
if (node == null)
{
return null;
}
else if (node.right != null)//如果有右子节点,就找右子树的最小值
{
return getMin(node.right);
}
else//如果没有右子节点,就返回自右而来的祖先节点的值
{
return ancestorFromRight == null ? null : ancestorFromRight.value;
}
}
/**
* 删除关键字对应的值
* @param key 关键字
* @return 关键字对应的值
*/
public V remove(final K key)
{
BSTTreeNode node = root;
BSTTreeNode parent = null;//旨在记录要删除的节点的父节点,后面托孤方法需要用到
while (node != null)
{
int result = key.compareTo(node.key);
if (result < 0)
{
parent = node;//更新父节点
node = node.left;
}
else if (result > 0)
{
parent = node;
node = node.right;
}
else
{
break;
}
}
//删除操作
if (node.left == null)//如果要删除的节点没有左子节点,直接将右子树托孤给父节点
{
shift(parent,node,node.right);
}
else if (node.right == null)//如果要删除的节点没有右子节点,直接将左子树托孤给父节点
{
shift(parent,node,node.left);
}
else//如果要删除的节点既有左子节点又有右子节点,得找到右子树的最小节点,将其托孤给父节点
{
BSTTreeNode s = node.right;//用于筛选符合条件的节点用于继位
BSTTreeNode sParent = node;//继位者的父节点,如果继位者还有右子节点,需要将其托孤给继位者的父节点
while (s.left != null)
{
sParent = s;
s = s.left;
}
if (s != node.right)//继位者不是嫡长子
{
shift(sParent,s,s.right);//处理上位者的后事
s.right = node.right; //继位者接手先帝的嫡长子的一脉
}
shift(parent,node,s);//继位
s.left = node.left;//接手先帝的次子一脉
}
return null;
}
/**
* 托孤方法
* @param parent 要删除的节点的父节点
* @param removed 要删除的节点
* @param child 要删除的节点的子节点
*/
private void shift(final BSTTreeNode parent,
final BSTTreeNode removed,
final BSTTreeNode child)
{
if (parent == null)//如果要删除的节点是根节点
{
root = child;
}
else if (removed == parent.left)
{
parent.left = child;
}
else if (removed == parent.right)
{
parent.right = child;
}
}
}
使用示例:
public static void main(String[] args)
{
BSTTree2 bst = new BSTTree2<>();
bst.put(5, "五");
bst.put(3, "三");
bst.put(7, "七");
bst.put(2, "二");
bst.put(4, "四");
System.out.println("键为 3 的值是:" + bst.get(3));
System.out.println("最小键是:" + bst.getMin());
System.out.println("最大键是:" + bst.getMax());
bst.remove(3);
System.out.println("移除键为 3 后,键为 3 的值是:" + bst.get(3));
}
在上述示例中,我们创建了一个整型到字符串的二叉搜索树,并演示了如何插入、查找、删除节点以及获取最小值和最大值等操作。