1. 二分搜索树的定义:二分搜索树本身是二叉树,只不过对于二分搜索树的每个结点而言,大于其左子树的所有结点的值,小于其右子树的所有结点的值。同样,其子树也是一颗二分搜索树,那么该树中元素必须要具有可比性,并且不包含重复元素。如图就是一颗二分搜索树:
2. 二分搜索树数据结构的实现:包括二分搜索树中数据的增删改查,二分搜索树的遍历(前中后序遍历,及层序遍历的迭代和递归方式 )等方法。
//二分搜索数的定义
/*
* 二分搜索数的特点:任意根节点的右子树所有节点又要小于根节点 左子树节点所有节点都要大于根节点
* 因此使用中序遍历二分搜索树时 会得到一个有序 递增的数据结构
* 向二分搜索树中添加元素时 都要对当前根节点与要添加节点进行比较
* 因此二分搜索树中的数据必须具有可比性 因此二分搜索树中的数据必须都是Comparable实现子类
*/
public class BinarySearchTree<E extends Comparable<E>> implements Iterable<E>{
//定义二分搜索树节点的信息
//由三部分组成 数据 指向左孩子的指针 指向右孩子的指针
private class Node{
E data; //数据域
Node leftChild; //左孩子指针域 指向当前节点左孩子
Node rightChild; //右孩子指针域 指向当前节点右孩子
public Node(E data) {
this.data = data;
this.leftChild = null;
this.rightChild = null;
}
@Override
public String toString() {
return data.toString();
}
}
private Node root; //根节点的指针 如果二分搜索树为空 则root==null
private int size; //二分搜索树中元素的个数(节点的个数)
public BinarySearchTree() {
this.root = null;
this.size = 0;
}
//获取二分搜索树节点的个数
public int size() {
return size;
}
//判断二分搜索树是否为空
public boolean isEmpty() {
return size == 0 && root == null;
}
//向二分搜索树中添加元素 迭代的方式
/*
* 如果二分搜索树为空 则使根节点指向要添加节点即可
* 如果二分搜索树不为空 就判断要添加元素 与 当前节点的大小
* 若新元素大于当前节点数据 再判断当前节点的右孩子是否为空
* 若为空 则使当前节点的右孩子指针指向要添加元素 终止循环即可
* 若不为空 则更新当前节点cur为当前节点的右孩子节点 继续循环
* 若新元素小于当前节点数据 再判断当前节点的左孩子是否为空
* 若为空 则使当前节点的左孩子指针指向要添加元素 终止循环即可
* 若不为空 则更新当前节点cur为当前节点的左孩子节点 继续循环
* 若新元素等于于当前节点数据 直接终止循环即可 因为二分搜索树中不存储重复元素
*/
public void addIt(E element) {
Node node = new Node(element);
if(isEmpty()) {
root = node;
size++;
}
Node cur = root;
while(true) {
//当新元素大于当前节点数据时
if(node.data.compareTo(cur.data) > 0) {
if(cur.rightChild == null) {
cur.rightChild = node;
size++;
break;
}else {
cur = cur.rightChild;
}
//当新元素小于当前节点数据时
}else if(node.data.compareTo(cur.data) < 0){
if(cur.leftChild == null) {
cur.leftChild = node;
size++;
break;
}else {
cur = cur.leftChild;
}
//当新元素等于当前节点数据时
}else {
break;
}
}
}
//向二分搜索树中添加元素 递归的方式 该方法是向外提供的
public void addRe(E element) {
root = addRe(root, element);
}
//向以node为根节点的二分搜索树中添加元素 并返回添加元素后新树的根节点 递归的方式
/*
* 先判断当前节点是否为空 若为空 则创建出要添加元素的节点
* 反之 则判断当前根节点与要添加节点的大小
* 若小于当前根节点 则向当前根节点的左子树递归 并使当前根节点的左孩子指针指向要添加的节点
* 若大于当前根节点 则向当前根节点的右子树递归 并使当前根节点的右孩子指针指向要添加的节点
* 添加结束后 要向上一层返回添加新节点后新树的根
*/
private Node addRe(Node node, E element) {
if(node == null) {
size++;
return new Node(element);
}
if(element.compareTo(node.data) < 0) {
node.leftChild = addRe(node.leftChild, element);
}else if(element.compareTo(node.data) > 0) {
node.rightChild = addRe(node.rightChild, element);
}
return node;
}
//判断二分搜索树中是否有指定元素 迭代的方法
/*
* 定义出一个游标表示当前节点 从根节点开始
* 判断要添加元素与当前节点的大小
* 若添加元素小于当前节点 在判断当前节点的左孩子是否为空
* 若左孩子为空 则表示没有指定元素 返回false即可
* 反之 继续向左循环 更新当前节点为当前节点的左孩子节点
* 若添加元素大于当前节点 在判断当前节点的右孩子是否为空
* 若右孩子为空 则表示没有指定元素 返回false即可
* 反之 继续向右循环 更新当前节点为当前节点的右孩子节点
* 若添加元素等于当前节点 则返回true即可
*/
public boolean containsIt(E element) {
Node cur = root;
while(true) {
if(element.compareTo(cur.data) < 0) {
if(cur.leftChild == null) {
return false;
}
cur = cur.leftChild;
}else if(element.compareTo(cur.data) > 0) {
if(cur.rightChild == null) {
return false;
}
cur = cur.rightChild;
}else {
return true;
}
}
}
//判断二分搜索树中是否有指定元素 递归的方法 向外提供的
public boolean containsRe(E element) {
return containsRe(root, element);
}
//判断以node为根节点的二分搜索树中是否有指定元素 递归的方法
/*
* 先判断递归临界条件 若当前根节点为空 则表示二分搜索树中没有指定元素
* 判断要添加元素与当前根节点的大小
* 若要添加元素小于当前根节点 则向当前根节点的左子树递归 使当前根节点为当前根节点的左孩子节点
* 若要添加元素大于当前根节点 则向当前根节点的右子树递归 使当前根节点为当前根节点的右孩子节点
* 若要添加元素等于当前根节点 则表示二分搜索树中包含指定元素 返回true即可
*/
private boolean containsRe(Node node, E element) {
if(node == null) {
return false;
}
if(element.compareTo(node.data) < 0) {
return containsRe(node.leftChild, element);
}else if(element.compareTo(node.data) > 0) {
return containsRe(node.rightChild, element);
}else {
return true;
}
}
//对二分搜索树进行前序遍历 该方法向外提供
public void preOrder() {
preOrder(root);
}
//对二分搜索树进行前序遍历 使用递归的方法以node为根节点进行前序遍历
/*
* 前序遍历(DLR):D表示当前元素 L表示左孩子或者左子树 R表示右孩子或者右子树
* 判断递归临界条件 若当前根节点为空时 则表示不能在向下递归
* 先打印当前元素
* 再向左边递归 直到当前节点为空 则表示没有左孩子了
* 如果有右子树 再向右边递归 直到当前节点为空 则表示没有右孩子了
* 再向上回溯
*/
private void preOrder(Node node) {
if(node == null) {
return;
}
System.out.println(node.data);
preOrder(node.leftChild);
preOrder(node.rightChild);
}
//前序遍历 迭代的方式
/*
* 迭代的方式进行前序遍历需要借助栈来完成
* 先将根节点进栈
* 弹栈一个元素 输出该元素
* 再判断该元素是否有右孩子 若有则右孩子进栈
* 反之 左孩子进栈
* 直到栈为空为止
*/
public void preOrederNR() {
LinkedList<Node> stack = new LinkedList<Node>();
stack.push(root);
while(!stack.isEmpty()) {
Node cur = stack.pop();
System.out.println(cur.data);
if(cur.rightChild != null) {
stack.push(cur.rightChild);
}
if(cur.leftChild != null) {
stack.push(cur.leftChild);
}
}
}
//对二分搜索树进行中序遍历 该方法向外提供
public void inOrder() {
inOrder(root);
}
//对二分搜索树进行中序遍历 使用递归的方法以node为根节点进行中序遍历
/*
* 二分搜索树中中序遍历得到的结果是一个有序递增的数据结构
* 中序遍历(LDR):L表示左孩子或者左子树 D表示当前元素 R表示右孩子或者右子树
* 判断递归临界条件 若当前根节点为空时 则表示不能在向下递归
* 先向左边递归 直到当前节点为空 则表示没有左孩子了
* 然后打印当前元素 并向上回溯 回溯到当前节点的父节点
* 如果有右子树 再向右边递归 直到当前节点为空 则表示没有右孩子了
* 再向上回溯
*/
private void inOrder(Node node) {
if(node == null) {
return;
}
inOrder(node.leftChild);
System.out.println(node.data);
inOrder(node.rightChild);
}
//中序遍历 迭代的方式
/*
* 迭代的方式进行中序遍历需要借助栈来完成
* 先将根节点的所有左孩子进栈
* 弹栈一个元素 并输出该元素
* 判断该元素的右孩子是否为空
* 若不为空 就循环将以该元素右孩子为根节点的所有左孩子进栈
* 直到栈为空为止
*/
public void inOrderNR() {
LinkedList<Node> stack = new LinkedList<Node>();
Node p = root;
while(p != null) {
stack.push(p);
p = p.leftChild;
}
while(!stack.isEmpty()) {
Node cur = stack.pop();
System.out.println(cur.data);
if(cur.rightChild != null) {
Node n = cur.rightChild;
while(n != null) {
stack.push(n);
n = n.leftChild;
}
}
}
}
//对二分搜索树进行后序遍历 该方法向外提供
public void lastOrder() {
lastOrder(root);
}
//对二分搜索树进行后序遍历 使用递归的方法以node为根节点进行后序遍历
/*
* 判断递归临界条件 若当前根节点为空时 则表示不能在向下递归
* 后序遍历(LRD):L表示左孩子或者左子树 R表示右孩子或者右子树 D表示当前元素
* 先向左边递归 直到当前节点为空 则表示没有左孩子了
* 如果有右子树 再向右边递归 直到当前节点为空 则表示没有右孩子了
* 最后打印当前元素 并向上回溯 回溯到当前节点的父节点
*/
private void lastOrder(Node node) {
if(node == null) {
return;
}
lastOrder(node.leftChild);
lastOrder(node.rightChild);
System.out.println(node.data);
}
//层序遍历 迭代的方式
/*
* 层序遍历需要借助队列来完成
* 先将根节点入队
* 出队一个元素 并打印该元素
* 判断该元素是否有左孩子 若有则将该元素左孩子入队
* 再判断该元素是否有右孩子 若有则将该元素右孩子入队
* 直到队列为空 循环结束
*/
public void levelOrder() {
LinkedList<Node> queue = new LinkedList<Node>();
queue.offer(root);
while(!queue.isEmpty()) {
Node cur = queue.poll();
System.out.println(cur.data);
if(cur.leftChild != null) {
queue.offer(cur.leftChild);
}
if(cur.rightChild != null) {
queue.offer(cur.rightChild);
}
}
}
//获取二分搜索树中的最小值 向外提供的
public E getMinNum() {
if(isEmpty()) {
throw new IllegalArgumentException("BST is null");
}
return getMinNum(root).data;
}
//获取以node为根节点的二分搜索树中的最小值节点
/*
* 若当前根节点的左孩子为空 则当前根节点就是最小值节点 返回即可
* 反之 以当前根节点的左孩子为根节点向下递归
*/
private Node getMinNum(Node node) {
if(node.leftChild == null) {
return node;
}
return getMinNum(node.leftChild);
}
//获取二分搜索树中的最大值 向外提供的
public E getMaxNum() {
if(isEmpty()) {
throw new IllegalArgumentException("BST is null");
}
return getMaxNum(root).data;
}
//获取以node为根节点的二分搜索树中的最大值节点
/*
* 若当前根节点的左孩子为空 则当前根节点就是最小值节点 返回即可
* 反之 以当前根节点的左孩子为根节点向下递归
*/
private Node getMaxNum(Node node) {
if(node.rightChild == null) {
return node;
}
return getMaxNum(node.rightChild);
}
//删除二分搜索树中的最小值 向外提供的
public E removeMin() {
E ret = getMinNum();
root = removeMin(root);
return ret;
}
//删除以node为根节点的二分搜索树中的最小值 并返回删除后新树的根
/*
* 先判断当前根节点是否有左孩子 若没有 则说明当前根节点为最小值的节点
* 获取最小值节点的右子树的根
* 使最小值节点的右孩子指针置空
* 向上一层返回最小值节点的右子树的根(即使最小值节点的父节点的左孩子指针 指向删除后新子树的根)
* 若还有左孩子 则说明还没有找到最小值 就继续向当前根节点的左孩子递归
* 返回删除后新树的根
*/
private Node removeMin(Node node) {
if(node.leftChild == null) {
Node right = node.rightChild;
node.rightChild = null;
size--;
return right;
}
node.leftChild = removeMin(node.leftChild);
return node;
}
//删除二分搜索树中的最大值 向外提供的
public E removeMax() {
E ret = getMaxNum();
root = removeMax(root);
return ret;
}
//删除以node为根节点的二分搜索树中的最大值的节点 并返回删除后新树的根
/*
* 先判断当前根节点是否有右孩子 若没有 则说明当前根节点为最大值的节点
* 获取最大值节点的左子树的根
* 使最大值节点的左孩子指针置空
* 向上一层返回最大值节点的左子树的根(即使最大值节点的父节点的右孩子指针 指向删除后新子树的根)
* 若还有右孩子 则说明还没有找到最大值 就继续向当前根节点的右孩子递归
* 返回删除后新树的根
*/
private Node removeMax(Node node) {
if(node.rightChild == null) {
Node left = node.leftChild;
node.leftChild = null;
size--;
return left;
}
node.rightChild = removeMax(node.rightChild);
return node;
}
//删除二分搜索树中任意的指定元素
public void remove(E e) {
root = remove(root, e);
}
//删除以node为根节点的二分搜索树中任意的指定元素 并返回删除后新树的根
/*
* 当当前根节点为空时 说明二分搜索树中没有要删除的元素
* 判断当前根节点与要删除节点的大小
* 若当前根节点大于要删除节点 就向当前根节点的左子树递归
* 若当前根节点小于要删除节点 就向当前根节点的右子树递归
* 若当前根节点等于要删除节点 说明找到了要删除的元素 就判断当前根节点是否右左右孩子
* 若当前根节点只有右孩子(即当前根节点的左孩子为空 类似删除最小值节点)
* 若当前根节点只有左孩子(即当前根节点的右孩子为空 类似删除最大值节点)
* 若当前根节点既有右孩子也有左孩子
* 我们就取该根节点的左子树中最大值 或 右子树中最小值来代替该根节点的位置
* 先获取该根节点右子树中最小值successor
* 再将该根节点右子树中最小值删除 会返回删除后新树的根
* 使该根节点右子树中最小值successor的右孩子指针指向删除最小值后新树的根
* 将删除节点的左孩子指针的地址赋值给该根节点右子树中最小值successor的左孩子指针
* 使要删除节点的左右孩子指针置空
* 返回删除后新树的根 即为successor
*/
private Node remove(Node node, E e) {
//如果node为空 则表示二分搜索树中没有指定元素 返回null即可
if(node == null) {
return null;
}
if(e.compareTo(node.data) < 0) {
node.leftChild = remove(node.leftChild, e);
return node;
}else if(e.compareTo(node.data) > 0) {
node.rightChild = remove(node.rightChild, e);
return node;
}else {
if(node.leftChild == null) {
Node rightNode = node.rightChild;
node.rightChild = null;
size--;
return rightNode;
}else if(node.rightChild == null) {
Node leftNode = node.leftChild;
node.leftChild = null;
size--;
return leftNode;
}
Node successor = getMinNum(node.rightChild);
successor.rightChild = removeMin(node.rightChild);
successor.leftChild = node.leftChild;
node.leftChild = null;
node.rightChild = null;
return successor;
}
}
//规定二分搜索树的输出格式
/*
* 借助迭代器来遍历元素 每遍历带一个元素
* 就将其拼接到字符串后 再拼接一个","
* 最后将字符串末尾的"," 替换成"]"
*/
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("[");
if(isEmpty()) {
str.append("]");
}
Iterator it = iterator();
while(it.hasNext()) {
str.append(it.next());
str.append(",");
}
str.setCharAt(str.length() - 1, ']');
return str.toString();
}
//迭代二分搜索树
@Override
public Iterator<E> iterator() {
return new BinarySearchTreeeIterator();
}
//定义二分搜索树的迭代器
/*
* 先将二分搜索树中的数据存放在一个线性表中 再对线性表进行迭代
* 可以通过前中后遍历将数据存入线性表中
*/
private class BinarySearchTreeeIterator implements Iterator<E>{
//定义一个线性表
LinkedList<E> list = new LinkedList<E>();
//在构造方法中 将二分搜索树中的数据存入线性表中
public BinarySearchTreeeIterator() {
LinkedList<Node> stack = new LinkedList<Node>();
Node p = root;
while(p != null) {
stack.push(p);
p = p.leftChild;
}
while(!stack.isEmpty()) {
Node cur = stack.pop();
list.offer(cur.data);
if(cur.rightChild != null) {
Node n = cur.rightChild;
while(n != null) {
stack.push(n);
n = n.leftChild;
}
}
}
}
//判断是否有下一个元素
@Override
public boolean hasNext() {
return !list.isEmpty();
}
//获取下一个元素
@Override
public E next() {
return list.poll();
}
}
}
3. 二分搜索树数据结构的测试:
public class TestBinarySearchTree {
public static void main(String[] args) {
BinarySearchTree<Integer> bst = new BinarySearchTree<Integer>();
bst.addRe(1);
bst.addRe(4);
bst.addRe(3);
bst.addRe(5);
bst.addRe(2);
bst.addRe(6);
System.out.println(bst.containsRe(3));
System.out.println(bst.containsRe(7));
System.out.println("======");
bst.preOrder();
System.out.println("======");
bst.inOrder();
System.out.println("======");
bst.lastOrder();
System.out.println("======");
bst.levelOrder();
System.out.println(bst.getMinNum());
System.out.println(bst.getMaxNum());
System.out.println("======");
for(Integer i : bst) {
System.out.print(i + " ");
}
System.out.println();
bst.removeMin();
System.out.println(bst);
bst.removeMax();
System.out.println(bst);
bst.remove(4);
System.out.println(bst);
}
}
4. 运行结果: