概述
本文讲解二叉搜索树的一些基本概念和性质,实现了二叉搜索树的一些常用方法,包括:添加元素、删除元素、计算给定节点node的前驱节点、计算给定节点node的后继节点。
二叉树的性质
- 任意一个节点的值都大于其左子树所有节点的值
- 任意一个节点的值都小于其右子树所有节点的值
- 二叉搜索树所存储的元素必须具备可比较性,且不能为null
二叉搜索树常用方法代码实现
本文二叉搜索树的代码继承了上一节所讲的二叉树,如果对代码中的部分属性和方法有疑惑可查阅二叉树部分。
注意:代码部分所讲的前驱和后继节点为中序遍历中的前驱和后继。例:中序遍历为 (a,e,d,c,f),则d的前驱为e,后继为c。
添加元素
public class BinarySearchTree<E> extends BinaryTree{
private Comparator<E> comparator;
public BinarySearchTree() {
this(null);
}
public BinarySearchTree(Comparator<E> comparator) {
this.comparator = comparator;
}
private int compare(E e1,E e2) {
if (comparator != null) {
return comparator.compare(e1, e2);
}
return ((Comparable<E>)e1).compareTo(e2);
}
//添加元素
public void add(E element) {
elementNotNullCheck(element);
//添加第一个节点
if (root == null) {
root = new Node<>(element, null);
size++;
return;
}
//添加的不是第一个节点
//找到父节点
Node<E> parent = root;
Node<E> node = root;
int cmp = 0;
while (node != null) {
cmp = compare(element,node.element);
parent = node;
if (cmp > 0) {
node = node.right;
}
else if (cmp < 0) {
node = node.left;
}
else {
node.element = element;
return;
}
}
//看看插入到父节点的哪个位置
Node<E> newNode = new Node<>(element, parent);
if (cmp > 0) {
parent.right = newNode;
}
else {
parent.left = newNode;
}
size ++;
}
}
计算给定节点node的前驱节点
实现思路:
- 分三种种情况:
- node的左子树不为空,则前驱节点为其左子树中的最右边的节点(此节点为子树中的最大值,即比node小的所有节点中的最大值为node的前驱节点)
- node的左子树为空,但有父节点,则从其所有父节点和祖父节点中找到的第一个比node小的节点就是node的前驱节点(即比node小的所有节点中的最大值为node的前驱节点)
- node的左子树为空,所有父节点和祖父节点都比node大,则node没有前驱节点
private Node<E> predecessor(Node<E> node) {
if (node == null) return null;
Node<E> p = node.left;
if (p != null) {
while (p.right != null) {
p = p.right;
}
return p;
}
while(node.parent != null && node == node.parent.left) {
node = node.parent;
}
return node.parent;
}
计算给定节点node的后继节点
思路:与计算前驱节点类似
- 分三种情况:
- node的右子树不为空,则后继节点为其右子树中的最左边的节点(此节点为子树中的最小值,即比node大的所有节点中的最小值为node的后继节点)
- node的右子树为空,但有父节点,则从其所有父节点和祖父节点中找到的第一个比node大的节点就是node的后继节点(即比node大的所有节点中的最小值为node的后继节点)
- node的右子树为空,所有父节点和祖父节点都比node小,则node没有后继节点
private Node<E> successor(Node<E> node) {
if (node == null) return null;
Node<E> p = node.right;
if (p != null) {
while (p.left != null) {
p = p.left;
}
return p;
}
while(node.parent != null && node == node.parent.right) {
node = node.parent;
}
return node.parent;
}
删除元素
实现思路:
- 设根节点为root,要删除的节点为node,其子节点为child(node.left或者node.right)。
- 分三种情况:
- 删除度为0的节点:
- 若要删除的节点为其父节点的左子节点,则将其父节点的左子节点赋值为null
- 若要删除的节点为其父节点的右子节点,则将其父节点的右子节点赋值为null
- 若要删除的节点的父节点为null,则要删除的节点为根节点,直接将根节点赋值为null
- 删除度为1的节点:
- 若要删除的节点为其父节点的左子节点,则令child.parent = node.parent,node.parent.left = child
- 若要删除的节点为其父节点的右子节点,则令child.parent = node.parent,node.parent.right = child
- 若要删除的节点为根节点,则令root = child,child.parent = null
- 删除度为2的节点:
- 先用node节点的前驱或者后继节点的值覆盖node节点的值
- 然后删除相应的前驱或者后继节点
- 如果一个节点的度为2,则其前驱和后继节点的度一定为1或者0
- 删除度为0的节点:
//删除元素
public void remove(E element) {
remove(node(element));
}
private void remove(Node<E> node) {
if(node == null) return;
size--;
if(node.hasTwoChild()) { //度为2的节点
//找到后继节点
Node<E> s = successor(node);
//用后继节点的值覆盖度为2的节点的值
node.element = s.element;
//删除后继节点
node = s;
}
//删除node节点(node的度必然是1或者0)
Node<E> replacement = node.left != null ? node.left : node.right;
if (replacement != null) { //node是度为1的节点
//更改parent
replacement.parent = node.parent;
//更改parent的left、right的指向
if (node.parent == null) { //node是度为1的节点并且是根节点
root = replacement;
}else if (node == node.parent.left) {
node.parent.left = replacement;
}else { //node == node.parent.right
node.parent.right = replacement;
}
}else if (node.parent == null) { //node是叶子节点并且是根节点
root = null;
}else { //node是叶子节点,但不是根节点
if(node == node.parent.left) {
node.parent.left = null;
}else { //node == node.parent.right
node.parent.right = null;
}
}
}
//根据element获取节点
private Node<E> node(E element) {
Node<E> node = root;
while(node != null) {
int cmp = compare(element, node.element);
if (cmp == 0) return node;
if (cmp > 0) {
node = node.right;
}else {
node = node.left;
}
}
return null;
}