Java–二分搜索树
一、特点
- 二分搜索树是一颗二叉树
- 二分搜素树的每一个节点的值都大于其左子树所有节点的值,小于右子树所有节点的值
二、底层维护
-
root :树根节点
-
size :树中元素个数
三、注意
树中的节点必须具有可比较性
/**
* 二分搜索树:
* 特点:1.每一个节点都比左孩子大,比右孩子小
*
* @author a_apple
* @create 2020-04-20 15:51
*/
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;
}
public int getSize() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
//...具体操作在下面
}
四、搜索二叉树的基本操作
- 向以node为根节点的树中添加元素e,并返回根节点。【递归算法】
注:(除node为null外)元素e最后会和树中一个叶子节点的(左或右)孩子进行比较。叶子节点左右孩子都为空,进入比较后会返回一个的新节点,就是叶子节点的孩子。
private Node add(Node node, E e) {
if (node == null) {
size++;
return new Node(e);
}
//e<当前节点,进入左子树继续比较。把node当做叶子节点
if (e.compareTo(node.e) < 0) {
//插入后node.left 可能会改变,需要重新接受
node.left = add(node.left, e);
} else if (e.compareTo(node.e) > 0) {
//e>当前节点,进入右子树
node.right = add(node.right, e);
}
//不处理相同的情况
return node;
}
- 查看以node为根节点的二分搜索树是否包含元素e
private boolean contains(Node node, E e) {
if (node == null)
return false;
if (e.compareTo(node.e) == 0)
return true;
if (e.compareTo(node.e) < 0)
return contains(node.left, e);
else
return contains(node.right, e);
}
- 前序遍历
private void preOrder(Node node) {
if (node == null) {
System.out.print("#->");
return;
}
System.out.print(node.e + "->");
preOrder(node.left);
preOrder(node.right);
}
- 非递归前序遍历【借助栈
先把根节点压入栈,【1.然后出栈访问。2. 再把左右孩子(非空)压入栈。–>递归进入第一步】
//借助栈实现前序非递归遍历
//深度优先遍历
public void preOrder22() {
Stack<Node> stack = new Stack<>();
//1.把根节点压入栈
stack.push(root);
while (!stack.isEmpty()) {
Node pop = stack.pop();
System.out.print(pop.e + "->");
//压入左右孩子,因为使用的是栈。所以先压入右孩子,再压左孩子
//那么左孩子就先出栈
if (pop.right != null)
stack.push(pop.right);
if (pop.left != null)
stack.push(pop.left);
}
}
- 后序遍历
//后序遍历
private void tailOrder(Node node) {
if (node == null) {
System.out.print("#->");
return;
}
tailOrder(node.left);
tailOrder(node.right);
System.out.print(node.e + "->");
}
- 中序遍历
//中序遍历
private void middleOrder(Node node) {
if (node == null) {
System.out.print("#->");
return;
}
middleOrder(node.left);
System.out.print(node.e + "->");
middleOrder(node.right);
}
- 层次遍历【借助队列
先把根节点入队,【1.出队访问,2.再把左右孩子入队—>递归->1】
//层次遍历,借助队列
//也叫做广度优先遍历
public void levelOrder() {
Queue<Node> queue = new LinkedList<>();
//1.把根节点压入栈
queue.add(root);
//输出这一层(栈内)所有节点
//同时把这一层的所有节点的子孩子压入栈
while (!queue.isEmpty()) {
Node pop = queue.remove();
System.out.print(pop.e + "->");
if (pop.left != null)
queue.add(pop.left);
if (pop.right != null)
queue.add(pop.right);
}
}
- 获取二分搜索树的最大最小值节点
//最小节点
//不断往左子树找,最后没有左子树的那个节点,就是最小节点
private Node miniNode(Node node) {
if (node.left == null) {
return node;
}
return miniNode(node.left);
}
//最大节点:同理
private Node maxNode(Node node) {
if (node.right == null)
return node;
return maxNode(node.right);
}
- 删除二分搜索树最小值,最大值的节点
- 删除最小值节点:【不断往左子树寻找
- node.left = null ,找到最小节点
- 该节点的右子树不一定为空,所以将右子树第一个节点返回作为新的根节点
- 同理:删除最大值节点:【不断往右子树寻找
- node.right = null , 即为最大值节点
- 该节点左子树不一定为空,所以将左子树第一个节点返回作为根节点
private Node removeMin(Node node) {
// node.left==null 即:如果该节点为最小节点
// 则返回该节点右子树的第一个节点
if (node.left == null) {
//如果左子树为空,则该节点右子树第一个为最大的节点
Node retNode = node.right;
//断开与右子树的关系
node.right = null;
size--;
//返回新根节点
return retNode;
}
//该节点不是最小节点,继续进入左子树。
//用node.left 接收删除后的新根节点-->A.left = C
node.left = removeMin(node.left);
return node;
}
//返回删除节点后新的二分搜索树的根
private Node removeMax(Node node) {
// node.right==null 即:当前节点为最大节点
// 返回左子树第一个节点
if (node.right == null) {
//如果右子树为空,则左子树第一个为最大的节点
Node retNode = node.left;
//断开与左子树的关系
node.left = null;
size--;
//返回新根节点
return retNode;
}
//当前节点不是最大节点,继续进入右子树寻找
node.right = removeMax(node.right);
return node;
}
- 删除以node为根的二分搜索树中值为e的节点,并返回删除后的根节点。【递归算法
“待删除节点”有3种情况:
- 左子树为空 =>【删除最小节点
- 右子树为空 =>【删除最大节点
- 左右子树都不为空【如下图,删除d节点
- 1 .找到待删除节点 d 的右子树的最小节点 s
- 2 .用 s 替代 d 的位置
- s = miniNode(d.right);
- s.right = removeMini(d.right);
- s.left = d.left
注:删除节点后依然维持着二分搜索树的性质
//删除值为e的节点,返回新的根节点
private Node remove(Node node, E e) {
if (node == null) {
return null;
}
if (e.compareTo(node.e) < 0) {
//e<当前节点,进入左子树
node.left = remove(node.left, e);
return node;
} else if (e.compareTo(node.e) > 0) {
//e>当前节点,进入右子树
node.right = remove(node.right, e);
return node;
} else {
//找到待删除节点e
//待删除节点左子树为空
if (node.left == null) {
Node rightNode = node.right;
//断开node和右子树的连接
node.right = null;
size--;
//返回删除节点后的(局部)根节点
return rightNode;
}
//待删除节点右子树为空
if (node.right == null) {
Node leftNode = node.left;
//断开node和右子树的连接
node.left = null;
size--;
//返回删除节点后的(局部)根节点
return leftNode;
}
//待删除节点的左右子树均不为空
//1.寻找“待删除节点”右子树的最小节点
Node rightMiniNode = miniNode(node.right);
//2.删除待删节点的右子树的最小值并把新树的作为 “替代节点” 的右子树
rightMiniNode.right = removeMin(node.right);
//3.把待删除节点的左子树接到 “替代节点”的左子树
rightMiniNode.left = node.left;
//4.将删除的节点断开联系
node.left = node.right = null;
return rightMiniNode;
}
}