什么是二叉搜索树?
二叉搜索树也叫做二叉排序树、二叉查找树,它有以下性质:
- 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
- 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
- 任意节点的左、右子树也分别为二叉查找树;
- 没有键值相等的节点。
二叉查找树相比于其他数据结构的优势在于查找、插入的时间复杂度较低,为O(log n)。
基本操作
1.定义BST对象
public class BST<E extends Comparable<E>> {
/**
* 定义节点
*/
private class Node {
private E e;
private Node left;
private Node right;
Node(E e) {
this.e = e;
}
}
private Node root;
private int size;
/**
* 获取节点数
*
* @return
*/
public int size() {
return this.size;
}
}
2.添加节点
1.采用递归,终止条件是当前节点为null时,创建新的节点;
2.如果当前节点不为null,则通过比较插入值和当前节点值的大小,递归其左/右子节点。
public void add(E element){
root = add(root, element);
}
private void _add(Node node, E element){
if(node == null){
size++;
return new Node(element);
}
if(element.compareTo(node.e) > 0){
node.right = _add(node.right, element);
}else if(element.compareTo(node.e) < 0){
node.left = _add(node.left, element);
}
return node;
}
3.查询节点
1.通过比较当前节点值的大小,递归查找左/右子节点。
public boolean contains(E element){
return _contains(root, element);
}
private boolean _contains(Node node,E element){
if(node == null) return false;
if(element.compareTo(node.e) == 0){
return true;
}else if(element.compareTo(node.e) > 0){
return _contains(node.right, element);
}else{
return _contains(node.left, element);
}
return false;
}
4.打印二叉树
1.通过递增树的深度dept,层级打印;
2.叶子节点通过##表示。
public String toString(){
StringBuilder builder = new StringBuilder();
_toString(root, 0, builder);
return builder.toString();
}
private void _toString(Node node, int dept, StringBuilder builder){
if(int i=0; i<dept; i++){
builder.append("--");
}
if(node == null){
builder.append("##
");
return;
}
builder.append(node.e).append("
");
_toString(node.left, dept+1, builder);
_toString(right, dept+1, builder);
}
测试运行
public static void main(String[] args) {
BST<Integer> bst = new BST<>();
bst.add(30);
bst.add(10);
bst.add(22);
bst.add(50);
bst.add(18);
bst.add(34);
bst.add(5);
System.out.println(bst);
}
输出结果为:
30
--10
----5
------##
------##
----22
------18
--------##
--------##
------##
--50
----34
------##
------##
----##
遍历操作
二叉树的遍历分为:前序遍历、中序遍历、后序遍历,
这里的前中后指的是父节点的顺序,所以三种遍历的顺序是:
- 前序遍历(父-左-右)
- 中序遍历 (左-父-右)
- 后序遍历 (左-右-父)
1.前序遍历
1.递归遍历,终止条件是当前节点为null;
2.先遍历输出父节点,再遍历左、右子节点
public void preOrder(){
StringBuilder builer = new StringBuilder();
_preOrder(root, builer);
System.out.println(builer.toString);
}
private void _preOrder(Node node,StringBuilder builder){
if(node == null) return;
builder.append(node.e).append(" ");
_preOrder(node.left, builder);
_preOrder(node.right, builder);
}
2.前序遍历2(非递归)
1.借助栈,首先将节点放入栈中,开始遍历栈;
2.遍历取出节点后再将其右子节点和左子节点放入压入栈中;
public void preOrder2(){
StringBuilder builder = new StringBuilder();
Stack<Node> stack = new Stack();
stack.push(root);
while(!stack.isEmpty()){
Node node = stack.pop();
if(node == null) continue;
builder.append(node.e).append(" ");
stack.push(node.right);
stack.push(node.left);
}
System.out.println(builder.toString());
}
3.中序遍历
1.同前序遍历,只是输出的顺序不同
public void inOrder(){
StringBuilder builer = new StringBuilder();
_inOrder(root, builer);
System.out.println(builer.toString);
}
private void _inOrder(Node node,StringBuilder builder){
if(node == null) return;
_inOrder(node.left, builder);
builder.append(node.e).append(" ");
_inOrder(node.right, builder);
}
输入你会发现,二叉搜索树的中序遍历的结果是有序的(从小到大)。
4.后序遍历
1.同前序遍历,只是输出的顺序不同
public void postOrder(){
StringBuilder builer = new StringBuilder();
_postOrder(root, builer);
System.out.println(builer.toString);
}
private void _postOrder(Node node,StringBuilder builder){
if(node == null) return;
_postOrder(node.left, builder);
_postOrder(node.right, builder);
builder.append(node.e).append(" ");
}
5.层序遍历
层序遍历指的是从上到下,从左到右一层层遍历,其遍历方法和上面的前序遍历类似,只是这里借助的是队列;
1.需要借助队列,首选将根节点入队,开始遍历队列;
2.如果当前节点不为null,则将其左子节点和右子节点入队,继续遍历;
public void levelOrder() {
StringBuilder builder = new StringBuilder();
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
Node node = queue.poll();
if (node == null) continue;
builder.append(node.e).append(" ");
queue.offer(node.left);
queue.offer(node.right);
}
System.out.println(builder.toString());
}
删除
1.删除最小/最大节点
以删除最小为例,最小节点一定是树的最左边的那个:
1.因为删除的节点可能是遍历的当前节点,所以需要通过return的形式返回节点;
2.判断左子节点是否为null:
如果不为null则递归遍历左子节点;
如果为null则要删除的是当前节点,要把它的右节点返回;
public Node removeMinimum() {
root = _removeMinimun(root);
return root;
}
private Node _removeMinimun(Node node) {
if (node == null) return null;
if (node.left == null) {
//如果左子节点为null,则把右子节点返回
Node tmp = node.right;
node.right = null;
size--;
return tmp;
}
node.left = _removeMinimun(node.left);
return node;
}
2.删除任意节点
分三种情况:
- 如果该节点没有左子节点:返回其右子节点;
- 如果该节点没有右子节点:返回其左子节点;
- 如果该节点同时拥有左右子节点:
节点的后继节点是大于它并最接近它的那个节点,也就是该节点的右子树中最小的节点,就是其右子树中最左边的那个,如下图中15的后继节点是17。
1.需要找到其后继节点;
2.将后继节点删除;
3.将后继节点的左子树等于该节点的左子树;
4.将后继节点的右子树等于该节点的右子树删除后继节点返回的树。
/**
* 删除任意节点,并返回删除后的根节点
*
* @param e
* @return
*/
public void removeNode(E e) {
root = _removeNode(root, e);
}
private Node _removeNode(Node node, E e) {
if (node == null) return null;
if (e.compareTo(node.e) < 0) {
node.left = _removeNode(node.left, e);
} else if (e.compareTo(node.e) > 0) {
node.right = _removeNode(node.right, e);
} else {
//1.查找到对应的节点
//2.左子树为空的情况,直接把右子树返回
if (node.left == null) {
Node right = node.right;
node.right = null;
return right;
}
//3.右子树为空的情况,直接把左子树放回
if (node.right == null) {
Node left = node.left;
node.left = null;
return left;
}
//4.找到后继节点(右子树中最小的节点)
Node successor = _mininum(node.right);
//5.将右子树中最小的节点删除
successor.right = _removeMinimun(node.right);
//6.将节点的左子树等于后继节点的左子树
successor.left = node.left;
node.left = node.right = null;
//7.返回后继节点
return successor;
}
return node;
}
/**
* 查找最小的节点
*
* @param node
* @return
*/
private Node _mininum(Node node) {
if (node == null) return null;
if (node.left != null) {
return _mininum(node.left);
}
return node;
}
完整代码BST.java
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
/**
* Binary Search Tree
*
* @param <E>
*/
public class BST<E extends Comparable<E>> {
/**
* 定义节点
*/
private class Node {
private E e;
private Node left;
private Node right;
Node(E e) {
this.e = e;
}
}
private Node root;
private int size;
/**
* 获取节点数
*
* @return
*/
public int size() {
return this.size;
}
/**
* 添加节点
*
* @param element
*/
public void add(E element) {
root = add(root, element);
}
private Node add(Node node, E element) {
if (node == null) {
size++;
return new Node(element);
}
if (element.compareTo(node.e) > 0) {
node.right = add(node.right, element);
} else if (element.compareTo(node.e) < 0) {
node.left = add(node.left, element);
}
return node;
}
/**
* 是否含有节点
*
* @param element
* @return
*/
public boolean contains(E element) {
return contains(root, element);
}
private boolean contains(Node node, E element) {
if (node == null) return false;
if (element.equals(node.e)) return true;
if (element.compareTo(node.e) < 0) {
return contains(node.left, element);
} else {
return contains(node.right, element);
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
toString(root, 0, builder);
return builder.toString();
}
private void toString(Node node, int dept, StringBuilder builder) {
for (int i = 0; i < dept; i++) {
builder.append("--");
}
if (node == null) {
builder.append("##
");
return;
}
builder.append(node.e);
builder.append("
");
toString(node.left, dept + 1, builder);
toString(node.right, dept + 1, builder);
}
/**
* 前序遍历
*/
public void preOrder() {
StringBuilder builder = new StringBuilder();
preOrder(root, builder);
System.out.println(builder.toString());
}
private void preOrder(Node root, StringBuilder builder) {
if (root == null) return;
builder.append(root.e).append(" ");
preOrder(root.left, builder);
preOrder(root.right, builder);
}
/**
* 前序遍历(非递归)
*/
public void preOrderTraverse2() {
StringBuilder builder = new StringBuilder();
Stack<Node> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
Node node = stack.pop();
if (node == null) continue;
builder.append(node.e).append(" ");
stack.push(node.right);
stack.push(node.left);
}
System.out.println(builder.toString());
}
/**
* 中序遍历
*/
public void inOrder() {
StringBuilder builder = new StringBuilder();
inOrder(root, builder);
System.out.println(builder.toString());
}
private void inOrder(Node root, StringBuilder builder) {
if (root == null) return;
inOrder(root.left, builder);
builder.append(root.e).append(" ");
inOrder(root.right, builder);
}
/**
* 后序遍历
*/
public void postOrder() {
StringBuilder builder = new StringBuilder();
postOrder(root, builder);
System.out.println(builder.toString());
}
private void postOrder(Node root, StringBuilder builder) {
if (root == null) return;
postOrder(root.left, builder);
postOrder(root.right, builder);
builder.append(root.e).append(" ");
}
/**
* 层序遍历
*/
public void levelOrder() {
StringBuilder builder = new StringBuilder();
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
Node node = queue.poll();
if (node == null) continue;
builder.append(node.e).append(" ");
queue.offer(node.left);
queue.offer(node.right);
}
System.out.println(builder.toString());
}
/**
* 删除最小节点,返回删除后的根节点
*/
public Node removeMinimum() {
root = _removeMinimun(root);
return root;
}
private Node _removeMinimun(Node node) {
if (node == null) return null;
if (node.left == null) {
//如果左子节点为null,则把右子节点返回
Node tmp = node.right;
node.right = null;
size--;
return tmp;
}
node.left = _removeMinimun(node.left);
return node;
}
/**
* 删除最大节点,,返回删除后的根节点
*
* @return
*/
public Node removeMaximum() {
root = _removeMaxinum(root);
return root;
}
private Node _removeMaxinum(Node node) {
if (node == null) return null;
if (node.right == null) {
Node tmp = node.left;
node.left = null;
size--;
return tmp;
}
node.right = _removeMaxinum(node.right);
return node;
}
/**
* 删除任意节点,并返回删除后的根节点
*
* @param e
* @return
*/
public void removeNode(E e) {
root = _removeNode(root, e);
}
private Node _removeNode(Node node, E e) {
if (node == null) return null;
if (e.compareTo(node.e) < 0) {
node.left = _removeNode(node.left, e);
} else if (e.compareTo(node.e) > 0) {
node.right = _removeNode(node.right, e);
} else {
//1.查找到对应的节点
//2.左子树为空的情况,直接把右子树返回
if (node.left == null) {
Node right = node.right;
node.right = null;
return right;
}
//3.右子树为空的情况,直接把左子树放回
if (node.right == null) {
Node left = node.left;
node.left = null;
return left;
}
//4.找到后继节点(右子树中最小的节点)
Node successor = _mininum(node.right);
//5.将右子树中最小的节点删除
successor.right = _removeMinimun(node.right);
//6.将节点的左子树等于后继节点的左子树
successor.left = node.left;
node.left = node.right = null;
//7.返回后继节点
return successor;
}
return node;
}
/**
* 查找最小的节点
*
* @param node
* @return
*/
private Node _mininum(Node node) {
if (node == null) return null;
if (node.left != null) {
return _mininum(node.left);
}
return node;
}
}