二叉搜索树
定义
树的概念这里不做说明,在假设我们都知道二叉树的情况下,那么二叉查找树就是符合以下情况的一种特殊的二叉树:对于二叉查找树的某个节点X,他的左子树中所有的节点值都小于X节点的值,右子树所有节点的值都大于X节点的值。例如下面这颗树,就是一个二叉搜索树。该树结构中,16左边的节点都小于16,16右边的节点大于16,其他节点同理。那下面我们分别讨论二叉查找树的增删改查几个重要方法。
二叉查找树的API
查找
因为二叉搜索树的特点,所以我们查找一个节点是不是在二叉搜索树中,一般有两步,以上面的二叉搜索树为例,查找元素9:
- 访问根节点,如果根节点的值正好和查询的元素相等,那么直接返回,这里9不等于16,接续第二步。
- 判断9是否大于16,是则从16的右边开始查,否则,从16的左边查,这里9小于16,从16的左边查,定位到8,发现9大于8,从8的右边查,找到9。很显然,二叉搜索树的查询时间复杂度,是优于链表的。下面给出代码实现。
/**
* 以node为根的二叉搜索树是否包含元素e
* @param node
* @param e
* @return
*/
private boolean contains(Node node, E e) {
// 递归终止条件
if (null == node) {
return false;
}
if (e.compareTo(node.e) == 0) {
return true;
} else if (e.compareTo(node.e) < 0) {
return contains(node.left, e);
} else {
return contains(node.right, e);
}
}
插入
了解了查询的过程,插入就是可以用contains的方法遍历树,如果找到对应的节点,在做一次更新,否则,就插入到遍历的路径上的最后一个空节点。下面给出代码。
/**
* 递归添加元素
* @param node
* @param e
* @return 返回插入新节后二叉搜索树的根
*/
private Node add(Node node, E e) {
if (null == node) {
size ++;
return new Node(e);
}
if (e.compareTo(node.e) < 0) {
// 添加返回值将new出来的节点挂载到树上
node.left = add(node.left, e);
} else if (e.compareTo(node.e) > 0) {
node.right = add(node.right, e);
}
return node;
}
删除
删除相对于增加和查找元素,比较复杂,因为要考虑到这样几种情况。
- case1 以下图为例,删除元素30,那么我们直接删除30就可以
- case2:删除元素8,这个时候8没有左孩子,所以删除元素8以后,我们可以直接让8的右孩子替代8,成为16的左孩子。
- case3:还是删除元素8,这个时候8没有右孩子,那么我们可以让8的左孩子替换8成为16的左孩子。
- case4:还是删除元素8,这次8既有左孩子,也有右孩子。这种情况我们一般的策略是找到元素8右孩子中的最小的元素,也就是9,让他替代8的位置
/**
* 删除指定元素
* @param node 节点
* @param e 待删除的元素
* @return 删除后的根节点
*/
private Node remove(Node node, E e) {
if (null == node) {
return null;
}
if (e.compareTo(node.e) < 0) {
node.left = remove(node.left, e);
return node;
} else if (e.compareTo(node.e) > 0) {
node.right = remove(node.right, e);
return node;
} else {
// 左子树为空
if (null == node.left) {
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
// 右子树为空
if (null == node.right) {
Node leftNode = node.left;
node.left = null;
size --;
return leftNode;
}
// 待删除节点左右子树都不为空
// 找到待删除元素的右子树的最小节点,替换待删除元素
Node successor = minNum(node.right);
// successor现在替换了node的位置,就要将successor从原来的位置删除
successor.right = removeMin(node.right);
successor.left = node.left;
node.left = null;
node.right = null;
return successor;
}
}
其他API
关于二叉搜索树的其他操作,例如深度优先遍历、广度优先遍历、查询最大最小元素等代码,基本都是用了递归的方法。
package com.tree.BST;
import java.util.LinkedList;
import java.util.Queue;
public class BST<E extends Comparable<E>> {
private class Node {
E e;
Node right;
Node left;
public Node(E e) {
this.e = e;
this.right = null;
this.left = null;
}
}
private Node root;
private int size;
public BST() {
this.root = null;
this.size = 0;
}
private int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
/**
* 添加元素
* @param e
*/
public void add(E e) {
root = add(root, e);
}
/**
* 递归添加元素
* @param node
* @param e
* @return 返回插入新节后二叉搜索树的根
*/
private Node add(Node node, E e) {
if (null == node) {
size ++;
return new Node(e);
}
if (e.compareTo(node.e) < 0) {
// 添加返回值将new出来的节点挂载到树上
node.left = add(node.left, e);
} else if (e.compareTo(node.e) > 0) {
node.right = add(node.right, e);
}
return node;
}
/**
* 是否包含元素e
* @param e
* @return
*/
public boolean contains(E e) {
return contains(root, e);
}
/**
* 以node为根的二叉搜索树是否包含元素e
* @param node
* @param e
* @return
*/
private boolean contains(Node node, E e) {
if (null == node) {
return false;
}
if (e.compareTo(node.e) == 0) {
return true;
} else if (e.compareTo(node.e) < 0) {
return contains(node.left, e);
} else {
return contains(node.right, e);
}
}
/**
* 前序遍历
*/
public void preOrder() {
preOrder(root);
}
/**
* 前序遍历 先访问根节点,在访问左右子树, 递归算法
* @param node
*/
private void preOrder(Node node) {
if (null == node) {
return;
}
System.out.println(node.e);
preOrder(node.left);
preOrder(node.right);
}
/**
* 中序遍历
*/
public void inOrder() {
inOrder(root);
}
/**
* 中序遍历 先访问左子树,在访问根节点和右子树, 递归算法
* @param node
*/
private void inOrder(Node node) {
if (null == node) {
return;
}
inOrder(node.left);
//Gson gson = new Gson();
//String json = gson.toJson(node);
System.out.println(node.e);
inOrder(node.right);
}
/**
* 后序遍历
*/
public void postOrder() {
postOrder(root);
}
/**
* 后序遍历 先访问左子树,在访问右子树和根节点, 递归算法
* @param node
*/
private void postOrder(Node node) {
if (null == node) {
return;
}
postOrder(node.left);
postOrder(node.right);
System.out.println(node.e);
}
/**
* 层序遍历
*/
public void levelOrder() {
if (null == root) {
return;
}
Queue<Node> queue = new LinkedList<>();
Queue<Node> levelQueue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
Node cur = queue.remove();
System.out.print(cur.e + " ");
if (null != cur.left) {
queue.add(cur.left);
}
if (null != cur.right) {
queue.add(cur.right);
}
if(queue.isEmpty()){
System.out.println();
queue.addAll(levelQueue);
levelQueue.clear();
}
}
}
/**
* 获取二叉搜索树的最大节点
*/
public E maxNum() {
if (size == 0) {
throw new IllegalArgumentException("BSI is empty");
}
return maxNum(root).e;
}
/**
* 递归获取最大元素,从树的右边遍历
* @param node
* @return
*/
private Node maxNum(Node node) {
if (null == node.right) {
return node;
}
return maxNum(node.right);
}
/**
* 获取二叉搜索树的最小节点
*/
public E minNum() {
if (size == 0) {
throw new IllegalArgumentException("BSI is empty");
}
return minNum(root).e;
}
/**
* 递归获取最小元素,从树的左边遍历
* @param node 根节点
* @return 以node为根的最小节点
*/
private Node minNum(Node node) {
if (null == node.left) {
return node;
}
return minNum(node.left);
}
/**
* 移除最大元素
* @return 最大元素值
*/
public E removeMax() {
E e = maxNum();
removeMax(root);
return e;
}
/**
* 移除最大元素
* @param node 根节点
* @return 移除后根节点
*/
private Node removeMax(Node node) {
// 如果右子树为空,则根节点为最大节点,删除根节点
if (null == node.right) {
Node leftNode = node.left;
node.left = null;
size --;
return leftNode;
}
node.right = removeMax(node.right);
return node;
}
/**
* 删除最小元素
* @return 最小元素的值
*/
public E removeMin() {
E e = minNum();
removeMin(root);
return e;
}
/**
* 删除最小元素
* @param node
* @return 删除最小节点后的新的二叉搜索树
*/
private Node removeMin(Node node) {
// 如果左子树为空,则根节点为最小节点,删除根节点
if (null == node.left) {
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
node.left = removeMin(node.left);
return node;
}
/**
* 删除指定元素
* @param e
*/
public void remove(E e) {
root = remove(root, e);
}
/**
* 删除指定元素
* @param node 节点
* @param e 待删除的元素
* @return 删除后的根节点
*/
private Node remove(Node node, E e) {
if (null == node) {
return null;
}
if (e.compareTo(node.e) < 0) {
node.left = remove(node.left, e);
return node;
} else if (e.compareTo(node.e) > 0) {
node.right = remove(node.right, e);
return node;
} else {
// 左子树为空
if (null == node.left) {
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
// 右子树为空
if (null == node.right) {
Node leftNode = node.left;
node.left = null;
size --;
return leftNode;
}
// 待删除节点左右子树都不为空
// 找到待删除元素的右子树的最小节点,替换待删除元素
Node successor = minNum(node.right);
// successor现在替换了node的位置,就要将successor从原来的位置删除
successor.right = removeMin(node.right);
successor.left = node.left;
node.left = null;
node.right = null;
return successor;
}
}
/**
* 查找key的floor值
* @param key
* @return
*/
public E floor(E key) {
if (key.compareTo(minNum()) < 0) {
return null;
}
return floor(root, key);
}
/**
* 查找key的floor值
* @param node
* @param key
* @return
*/
private E floor(Node node, E key) {
if (null == node) {
return null;
}
// 如果key和node值相等,node就是key对应的floor节点
if (key.compareTo(node.e) == 0) {
return key;
}
// 如果key比node的值小,那么对应的floor节点肯定在node的左子树
if (key.compareTo(node.e) < 0) {
return floor(node.left, key);
}
// 如果key比node的值大,node有可能是key对应的floor节点,也有可能不是
E floor = floor(node.right, key);
if (floor != null) {
return floor;
}
return node.e;
}
/**
* 查找key的ceil值
* @param key
* @return
*/
public E ceil(E key) {
if (key.compareTo(maxNum()) > 0) {
return null;
}
return ceil(root, key);
}
/**
* 查找key的ceil值
* @param node
* @param key
* @return
*/
private E ceil(Node node, E key) {
if (null == node) {
return null;
}
// 如果key和node值相等,node就是key对应的ceil节点
if (key.compareTo(node.e) == 0) {
return key;
}
// 如果key比node的值大,那么对应的ceil节点肯定在node的右子树
if (key.compareTo(node.e) > 0) {
return ceil(node.right, key);
}
// 如果key比node的值小,node有可能是key对应的ceil节点,也有可能不是
E ceil = ceil(node.left, key);
if (ceil != null) {
return ceil;
}
return node.e;
}
/**
* 求树的深度
* @return 树的深度
*/
public int depth() {
return depth(root);
}
/**
* 求树的深度
* @return node为节点的书的树的深度
*/
private int depth(Node node){
if (null == node) {
return 0;
}
int leftDepth = depth(node.left);
int rightDepth = depth(node.right);
return Math.max(leftDepth, rightDepth) + 1;
}
}
文章代码参考《数据结构与算法分析》和慕课网liuyubobobo老师的算法大师带你玩转数据结构的课程。这里作为学习笔记整理。推荐一篇写的很不错的博客:浅谈算法和数据结构: 七 二叉查找树
github代码仓库:二叉搜索树