前言
二叉搜索树是树数据结构中比较简单的类型,它通过定义左节点比父节点小,右节点比父节点大,将信息分为两种可能,达到搜索复杂度为O()。关于二叉搜索树的定义细节,网上博文比较多,这方面就不在赘述。该博文侧重点是对二叉搜索树实现插入、删除和搜索的操作。
二叉树类和树节点类的定义
二叉树类只包含一个根节点root和节点数量size,因为左右节点需要与父节点进行比较,因此需要泛型上界。具体代码如下:
public class BinarySearchTree<T extends Comparable<T>>{
private Node<T> root;
private int size;
public BinarySearchTree(){
}
.
.
.
private static class Node<T extends Comparable<T>>{
public T value;
public Node<T> left;
public Node<T> right;
public Node<T> father;
public Node(T value, Node<T> parent){
this.value = value;
this.parent = parent;
}
public String toString(){
return key.toString();
}
}
}
插入
插入节点主要步骤为:1、判断根节点是否为空,为空、则该节点为根节点。
2、根节点不为空,则与其比较,小于就往左子树找,大于就往右子树找。
3、如果找到相等的节点,直接退出,二叉搜索树不能重复。
4、找到其父节点后,按照大小比较,设置其为父节点的左节点还是右节点,size++即可。
具体代码如下:
public boolean insert(T value){
if(value == null){
throw new IllegalArgumentException("The value of e connot be null.");
}
Node<T> node = root;
if(node == null){
root = new Node<>(value, null);
size++;
return true;
}
Node<T> parent = null;
int cmp = 0;
do{
parent = node;
cmp = value.compareTo(node.value);
if(cmp > 0){
node = node.right;
}else if(cmp < 0){
node = node.left;
}else{
return false;
}
}while(node != null);
Node<T> newNode = new Node<>(value, parent);
if(cmp > 0){
parent.right = newNode;
}else{
parent.left = newNode;
}
size++;
return true;
}
删除
删除一个节点,我们必须用新的节点来替换它,并且保证该节点能满足二叉树的要求,即该节点比左节点大,比右节点小。节点删除的步骤为:1、找到需要被删除的节点,如果找不到,则直接返回空。
2、找到以后,就需要在它的左右子树中找到替换它的节点,因为要满足二叉树要求,所以替换节点要么是左子树的最大节点(前驱节点),要么是右子树的最小节点(后继节点)。
3、我们将需要被删除节点的值设置成替换节点的值,就能实现节点删除的功能,因此我们真正删除的,其实是替换节点,因为替换节点有一个特点,就是,它最多只有一个子节点,我们只需要用这个子节点移动到它的位置即可,这样操作就会比较简单。此时,原先的替换节点的变成真正的被删除节点,而它的子节点只是一个移动节点,原先的被删除节点现在是被替换节点。
具体的图示如下:
1、寻找被删除节点的代码如下:
private Node<T> node(Object value){
if(value== null){
return null;
}
Node<T> node = root;
Comparable<? super T> comparator = (Comparable<? super T>) value;
int cmp = 0;
while(node != null){
cmp = comparator.compareTo(node.value);
if(cmp > 0){
node = node.right;
}else if(cmp == 0){
return node;
}else {
node = node.left;
}
}
return null;
}
2、寻找前驱节点代码如下:
private Node<T> predecessor(Node<T> node){
if(node == null){
return null;
}else if(node.left != null){
node = node.left;
while(node.right != null){
node = node.right;
}
return node;
}else{
Node<T> parent = node.parent;
while(parent != null && node == parent.left){
node = parent;
parent = node.parent;
}
return parent;
}
}
寻找后继节点代码如下:
private Node<T> successor(Node<T> node){
if(node == null){
return null;
}else if(node.right != null){
node = node.right;
while(node.left != null){
node = node.left;
}
return node;
}else{
Node<T> parent = node.parent;
while(parent != null && node == parent.right){
node = parent;
parent = node.parent;
}
return parent;
}
}
3、最终删除节点的代码如下:
public boolean remove(Object value){
Node<T> replaced = node(value);
if(replaced == null){
return false;
}
Node<T> removed = replaced;
if(replaced.left != null && replaced.right != null){
removed = successor(replaced);
}
Node<T> moved = removed.left != null ? removed.left : removed.right;
Node<T> parent = removed.parent;
if(moved != null){
moved.parent = parent;
}
if(parent == null){
root = moved;
}else if(removed == parent.left){
parent.left = moved;
}else {
parent.right = moved;
}
if(replaced != removed){
replaced.value = removed.value;
}
size--;
return true;
}
如果被删除的节点最多只有一个子节点,那么就无需使用前驱(或者后继)节点来替换它,只需要使用它的子节点来接替它即可,配合前面的删除步骤以及图示来了解这段代码,会更加清晰。
搜索
搜索主要应用于对集的二叉搜索树结构,我们是单集二叉搜索树结构,因此搜索主要的目的是为了判断是否存在该元素的节点。判断一个节点是否存在,其实可以直接使用前面的node函数。具体代码如下:
public boolean contains(Object value){
return node(value) != null;
}
判断二叉搜索树
利用左子树的节点小于父节点,右子树的节点大于父节点的性质进行判断,具体代码如下:
public boolean isValid(){
Node<T> node = root;
if(node == null){
return true;
}
return isValid(node.left, node) && isValid(node.right, node);
}
private boolean isValid(Node<T> node, Node<T> parent){
if(node == null){
return true;
}
if(node == parent.left && node.value.compareTo(parent.value) >= 0){
return false;
}
if(node == parent.right && node.value.compareTo(parent.value) <= 0){
return false;
}
return isValid(node.left, node) && isValid(node.right, node);
}
后记
二叉搜索树的插入和删除相对来说比较简单,但是它却是理解二叉搜索树的基础,二叉搜索树的遍历是该数据结构的真正核心,我也将会写一篇博文对其进行介绍。