二分搜索树
1、为什么要有树结构
树结构本身是一种天然的组织结构;高效
二分搜索树、平衡二叉树:AVL and 红黑树 、堆、并查集、线段树、Trie
2、二分搜索树基础
二叉树:和链表一样,动态数据结构
class Node{
E e;
Node left,right;
}
二叉树具有唯一根节点,每个节点最多有两个孩子。
二叉树具有天然递归结构:每个节点的左子树和右子树也是二叉树。
二叉树不一定是“满”的,一个节点也是二叉树,空也是二叉树。
二分搜索树:是二叉树且每个节点的值大于其左子树的所有节点的值并小于其右子树所有节点的值。
二分搜索树支持的类型必须具有可比较性
public class BST<E extends Comparable<E>> {
private class Node{
public E e;
public Node left,right;
public Node(E e) {
this.e=e;
left=null;
right=null;
}
}
private Node root;
private int size;
public BST() {
root=null;
size=0;
}
}
3、向二分搜索树添加元素
//add:找位置,放进去。在null的位置添加新的结点。
public void add(E e) {
if(root==null) {
root=new Node(e);
size++;
}
else
add(root,e);
}
private void add(Node node,E e) {
if(e.equals(node.e))
return;
else if(e.compareTo(node.e)<0&&node.left==null) {
node.left=new Node(e);
size++;
return;
}
else if(e.compareTo(node.e)>0&&node.right==null) {
node.right=new Node(e);
size++;
return;
}
if(e.compareTo(node.e)<0)
add(node.left,e);
if(e.compareTo(node.e)>0) {
add(node.right,e);
}
}
改进添加方法:
// 向二分搜索树中添加新的元素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);//node.left为空,即为将插入新元素的位置
else if(e.compareTo(node.e) > 0)
node.right = add(node.right, e);
return node;
}
4、查询
public boolean contains(E e) {
return contains(root, e);
}
private boolean contains(Node node,E e) {
if(node==null)
return false;
if(e.compareTo(node.e)==0)
return true;
if(e.compareTo(root.e)<0)
return contains(node.left,e);
else
return contains(node.right,e);
}
5、遍历
中序遍历是二分搜索树排序的结果。
//前序遍历
public void preOrder() {
preOrder(root);
}
private void preOrder(Node node) {
if(node==null)
return;
System.out.println(node.e);
preOrder(node.left);
preOrder(node.right);
}
//中序和后序遍历同理
二分搜索树的前中后序遍历都是按节点–左子树–右子树来进行访问,每个节点都被访问三次,前中后序分别对应在第一、二、三次访问时进行操作。
6、前序遍历的非递归方法
使用栈,先右子树入栈后左子树入栈。
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);
}
}
}
遗留问题:中序、后序遍历的非递归实现
7、层次遍历
使用队列,访问某个节点并依次将其左右孩子入队。
广度优先遍历的意义:更快的找到问题的解,常用于算法设计中–最短路径。
public void levelOrder() {
if(root==null)
return;
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);
}
}
8、删除
(1)删除最大、最小元素
//寻找最小元素
public E minimum() {
if(size==0) {
throw new IllegalArgumentException("BST is empty");
}
Node minNode=minimum(root);
return minNode.e;
}
public Node minimum(Node node) {
if(node.left==null)//从根节点开始一直向左查找,某个节点的左子树为空,则就是最小的节点
return node;
return minimum(node.left);
}
//删除最小元素
public E removeMin() {
E ret=minimum();
root=removeMin(root);
return ret;
}
private Node removeMin(Node node) {
if(node.left==null) {
Node rightNode=node.right;
size--;
node.right=null;
return rightNode;
}
node.left=removeMin(node.left);
return node;
}
(2)删除任意元素
public Node remove(Node node,E e) {
if(node==null) {
return null;
}
if(e.compareTo(root.e)<0) {
node.left=remove(node.left,e);
return node;
}
else if(e.compareTo(root.e)>0) {
node.right= remove(node.right,e);
return node;
}
else { //e.compareTo(node.e)==0
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.left=node.right=null;
return successor;
}
}
9、其他
-
二分搜索树的floor和ceil
-
二分搜索树的rank和select
-
支持重复元素的二分搜索树