二分搜索树
二分搜索树是二叉树中的一种。
二分搜索树的每个节点的值:
大于其左子树的所有节点的值
小于其右子树的所有节点的值
存储的元素必须有可比较性
传入的E必须具有可比较性所以要继承Comparable
基本数据结构:
public class BST<E extends Comparable<E>> {
private class Node{
public E e;
public Node left, right;
public Node(E e){
this.e = e;
this.left = null;
this.right = null;
}
}
private Node root;
private int size;
public BST(){
root = null;
size = 0;
}
public int size(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
}
添加新元素
/** 向二分搜索树中添加新的元素e*/
public void add(E e){
root = add(root, e);
}
/** 向以node为根的二分搜索树中插入元素E, 递归算法
* 返回插入新结点后二分搜索树的根
* */
private Node add(Node node, E e){
if (node == null){
size++;
return new Node(e);
}
if (e.compareTo(node.e) < 0){
node.left = add(node.left, e);
} else if (e.compareTo(node.e) > 0){
node.right = add(node.right, e);
}
return node;
}
查询
/** 看二分搜索树中是否包含元素e*/
public boolean contains(E e){
return contains(root, e);
}
/** 看以node为根的二分搜索树中是否包含元素e,递归算法*/
private boolean contains(Node node, E e){
if (node == null){
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);
}
}
二分搜索树的遍历
这里的遍历就简单的把结点值输出一下。
1.前序遍历
/** 二分搜索树的前序遍历*/
public void preOrder(){
preOrder(root);
}
/** 前序遍历以node为根的二分搜索树,递归算法*/
private void preOrder(Node node){
//递归终止条件
if (node == null){
return;
}
System.out.println(node.e);
preOrder(node.left);
preOrder(node.right);
}
2.中序遍历
public void inOrder(){
inOrder(root);
}
private void inOrder(Node node){
if (node == null){
return;
}
inOrder(node.left);
System.out.println(node.e);
inOrder(node.right);
}
由于二分搜索树的左子树的结点值都小于父节点,右子树的结点值都大于父节点,所以二分搜索树的中序遍历的结果是顺序的。
3.后序遍历
public void postOrder() {
preOrder(root);
}
private void postOrder(Node node) {
if (node == null) {
return;
}
postOrder(node.left);
postOrder(node.right);
System.out.println(node.e);
}
后序遍历的应用:为二分搜索树释放内存。
前序遍历的非递归写法
具体思路是使用一个栈来模拟系统栈。
/**
* 二分搜索树非递归前序遍历
*/
public void preOrderNR(){
Stack<Node> stack = new Stack<>();
stack.push(root);
while(!stack.isEmpty()) {
Node cur = stack.pop();
System.out.println(cur.e);
if (cur.right != null) {
stack.push(cur.right);
}
if (cur.left != null) {
stack.push(cur.left);
}
}
}
二分搜索树的层序遍历
前面的遍历方式都是深度优先遍历,这里开始是广度优先遍历。
/**
* 二分搜索树的层序遍历
*/
public void levelOrder(){
Queue<Node> q = new LinkedList<>();
q.add(root);
while (!q.isEmpty()){
Node cur = q.remove();
System.out.println(cur.e);
if (cur.left != null){
q.add(cur.left);
}
if (cur.right != null){
q.add(cur.right);
}
}
}
广度优先遍历的意义:
1.更快找到问题的解
2.无权图的最短路径
二分搜索树删除结点
删除任意结点这个操作比较复杂,我们先从最简单的删除二分搜索树中的最大值和最小值开始。
/**
* 删除二分搜索树中最小的节点,返回最小值
* @return
*/
public E removeMin(){
E ret = minimum();
root = removeMin(root);
return ret;
}
/**
* 删除以node为根的二分搜索树中最小节点
* 返回删除节点后的新的二分搜索树的根
* @param node
* @return
*/
private Node removeMin(Node node){
if (node.left == null){
Node rightNode = node.right;
node.right = null;
size--;
return rightNode;
}
node.left = removeMin(node.left);
return node;
}
/**
* 删除二分搜索树中最大的节点,返回最大值
* @return
*/
public E removeMax(){
E ret = maximum();
root = removeMax(root);
return ret;
}
/**
* 删除以node为根的二分搜索树中最大节点
* 返回删除节点后的新的二分搜索树的根
* @param node
* @return
*/
private Node removeMax(Node node){
if (node.right == null){
Node leftNode = node.left;
node.left = null;
size--;
return leftNode;
}
node.right = removeMin(node.right);
return node;
}
删除任意元素节点
这里有一个问题:当要删除的节点同时具有左右子树时该如何操作?
根据Hibbard Deletion定理,当删除的节点具有左右子树时用该节点的后继节点取代该节点的位置。
下面举个例子来说
当删除58节点时,我们用59来取代58的位置,这个59的节点就是58节点的后继节点,即右子树中的最小值。
实现代码:
/**
* 从二分搜索树中删除节点
* @param e
*/
public void remove(E e){
root = remove(root, e);
}
/**
* 删除以node为根的二分搜索树中值为e的节点,递归算法
* 返回删除节点后的新二分搜索树的根
* @param node
* @param e
* @return
*/
private Node remove(Node node, E e){
if (node == null){
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 (node.left == null){
Node rightNode = node.right;
node.right = null;
size--;
return rightNode;
}
/**
* 删除节点右子树为空
*/
if (node.right == null){
Node leftNode = node.left;
node.left = null;
size--;
return leftNode;
}
/**
* 删除节点左右子树都不为空
* 思路:找到比待删除节点大的最小节点,即待删除节点右子树最小的节点
* 用这个节点代替删除节点的位置
*/
Node successor = minimum(node.right);
successor.right = removeMin(node.right);
successor.left = node.left;
node.right = node.left = null;
return successor;
}
}
很多二分搜索树的题目都是在这些基本操作的基础上增加一些操作,基础学好以后解决那些题目也是很方便的。
其他更多请关注我的GitHub: https://github.com/leonnor