二分搜索树
代码位置:TreeMap.java
文章目录
1.何为二分搜索树
二分搜索树本身就是二叉树,但是又具备一些规则:对于每个节点而言,大于其左边节点的所有值,小于其右边节点的所有值
同样,每个节点的子树也是一个二分搜索树,二分搜索数中的元素要具有可比性,树中的元素不能重复
2.添加元素
不管是当前的操作,还是之后的操作,都会有两种写法吗,迭代and递归
先看一下添加元素
迭代:
这个很好理解,我们只需要从头结点开始比较,小于头结点则想左走,大于则向右走,直到节点的下面为空,我们就将该元素插入即可
public void add(E e) {
//添加的迭代思路
Node node = new Node(e);
if (isEmpty()) { // 如果当前节点为空,直接让当前节点成为root节点
root = node;
size++;
}
Node cur = root; // 定义一个节点,用来迭代
while (true) {
//新元素比当前大 往右走
if (node.e.compareTo(cur.e) > 0) {
if (cur.right == null) { // 如果右边为空,则插入
cur.right = node;
size++;
break;
} else {
cur = cur.right;
}
//新元素比当前小 往左走
} else if (node.e.compareTo(cur.e) < 0) {
if (cur.left == null) { // 如果左边为空,则插入
cur.left = node;
size++;
break;
} else {
cur = cur.left;
}
} else {
break;
}
}
}
递归:
递归这个稍微有一点点抽象,因为和迭代不一样,迭代是直接找到然后进行添加,递归则是我一直递归到我需要添加的位置,然后对该位置进行添加,之后就是一直向上返回,最终是返回一个新的root节点,举个例子,假如现在有一个机器人,他没有腿,现在要装上腿,迭代来讲就是我先找到腿和腰链接的地方,然后以腰部向下进行安装,递归则是先从脚到大腿根部拼好,再返回给腰部
//在以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;
}
3.包含元素
只需要依次向下进行迭代即可,找到和目标相同的值
//查看以node为根的二分搜索树中是否包含元素e
private boolean contains(Node node, E e) {
if (node == null) {
return false;
}
if (e.compareTo(node.e) < 0) {
return contains(node.left, e);
} else if (e.compareTo(node.e) > 0) {
return contains(node.right, e);
} else {
return true;
}
}
4.前序遍历
递归
//前序遍历-递归方式 以node为根节点 前序遍历DLR
private void preOrder(Node node) {
if (node == null) {
return;
}
System.out.println(node.e);
preOrder(node.left);
preOrder(node.right);
}
迭代
前序遍历是【中左右】的方式进行遍历的,先中后左最后右,我们借助栈完成的话,因为栈是先进后出的特性,所以我们应当先将节点 右边进栈,后将节点左边进栈
//前序遍历-迭代方式
public void preOrderNR() {
LinkedList<Node> stack = new LinkedList<>();
if (isEmpty()) {
return;
}
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);
}
}
}
5.中序遍历
递归
//中序遍历-递归方式 以node为根节点 中序遍历LDR
private void inOrder(Node node) {
if (node == null) {
return;
}
inOrder(node.left);
System.out.println(node.e);
inOrder(node.right);
}
迭代
中序表达式是【左中右】,因此我们先一股脑将节点左边-左边-左边-……一直入栈,然后再弹出数值,再判断该节点是否有右节点,如果有右节点,进而判断该右节点是否有左节点,如果有再入,总而言之,就是左节点优先级最高
//中序遍历-迭代方式
public void inOrderNR() {
LinkedList<Node> stack = new LinkedList<>();
Node p = root;
while (p != null) {
stack.push(p);
p = p.left;
}
while (!stack.isEmpty()) {
Node cur = stack.pop();
System.out.println(cur.e);
if (cur.right != null) {
Node n = cur.right;
while (n != null) {
stack.push(n);
n = n.left;
}
}
}
}
6.后序遍历
递归
//后序遍历-递归方式 以node为根节点 后序遍历LRD
private void postOrder(Node node) {
if (node == null) {
return;
}
postOrder(node.left);
postOrder(node.right);
System.out.println(node.e);
}
迭代
后序遍历是左右中,就是先将左右走完,再走中间值,因此我们需要借助一个链表来存储我们遍历的结果,每次将得到的数添加到第一位,或者也可以依次添加,最后进行链表反转
// 后序遍历-迭代
public void postOrderNR() {
LinkedList<Node> stack = new LinkedList<>();
LinkedList<E> list = new LinkedList<>();
if (root == null) {
return;
}
stack.add(root);
while (!stack.isEmpty()) {
Node cur = stack.pop();
list.addFirst(cur.e);
if (cur.left != null) {
stack.add(cur.left);
}
if (cur.right != null) {
stack.add(cur.right);
}
}
System.out.println(list);
}
7.层序遍历
因为需要一层一层的去遍历,所以我们采用队列的方式去对他进行遍历
//层序遍历-迭代方式
public void levelOrder() {
LinkedList<Node> queue = new LinkedList<>();
if (isEmpty()) {
return;
}
queue.offer(root);
while (!queue.isEmpty()) {
Node cur = queue.poll();
System.out.println(cur.e);
if (cur.left != null) {
queue.offer(cur.left);
}
if (cur.right != null) {
queue.offer(cur.right);
}
}
}
8.最小值和最大值
BST的定义是,对于任意一个节点来说,它右节点全部小于它,它的左节点全部大于他
因此我们可以得出对于BST来说,从根节点一直向右可以找出它的最小值,一直向左可以找到它的最大值
//寻找二分搜索树中的最小值
//自己尝试迭代方式实现
public E minimum() {
if (isEmpty()) {
throw new IllegalArgumentException("bst is empty");
}
return minimum(root).e;
}
//以node为根节点 查找最小值所在的结点 - 递归
private Node minimum(Node node) {
if (node.left == null) {
return node;
}
return minimum(node.left);
}
// 迭代
public E minimumNR() {
Node cur = root;
while (cur.left != null) {
cur = cur.left;
if (cur.left == null) {
break;
}
}
return cur.e;
}
//寻找二分搜索树中的最大值
//自己尝试迭代方式实现
public E maximum() {
if (isEmpty()) {
throw new IllegalArgumentException("bst is empty");
}
return maximum(root).e;
}
//以node为根节点 查找最大值所在的结点 - 递归
private Node maximum(Node node) {
if (node.right == null) {
return node;
}
return maximum(node.right);
}
9.删除最小值和最大值
在删除最小值和最大值时,我们选择递归的方式去实现,这个过程可以理解为将一个机甲的腿坎掉,然后返回给它的腰部,这时这个新的腰子(没有腿)再返回给头部
//删除最小值
public E removeMin() {
E ret = minimum();
root = removeMin(root);
return ret;
}
//以node为根 删除其中的最小值结点 返回删除后新树的根
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;
}
//删除最大值
public E removeMax() {
E ret = maximum();
root = removeMax(root);
return ret;
}
//以node为根 删除其中的最大值结点 返回删除后新树的根
private Node removeMax(Node node) {
if (node.right == null) {
Node leftNode = node.left;
node.left = null;
size--;
return leftNode;
}
node.right = removeMax(node.right);
return node;
}
10.删除任意元素
删除任意元素时,我们唯一需要进行考虑的是这个节点如果有子树,那么我们删除后该怎么办
这时候有三种情况
1.左边为空,右边补上,即使右边为空,也依旧可以补上
2.右边为空,在前面的基础上,此时左边肯定不为空,让左边补上
3.都不为空,我们将该节点右子树的最小值补上去 ,当然也可以是左子树的最大值
public void remove(E e) {
root = remove(root,e);
}
//以node为根 删除元素e 并返回删除后新树的根
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.left = node.right = null;
return successor; // 最终将新的节点返回
}
}
11.toString
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[");
if (isEmpty()) {
sb.append(']');
} else {
Iterator<E> it = iterator();
for (int i = 0; i < size; i++) {
sb.append(it.next());
if (i == size - 1) {
sb.append(']');
} else {
sb.append(',');
}
}
// while (it.hasNext()) {
// sb.append(it.next());
// sb.append(',');
// }
// sb.deleteCharAt(sb.length() - 1);
// sb.append(']');
}
return sb.toString();
}
12.迭代器
@Override
public Iterator<E> iterator() {
return new BinarySearchTreeIterator();
}
private class BinarySearchTreeIterator implements Iterator<E> {
private Iterator<E> it;
public BinarySearchTreeIterator() {
LinkedList<E> list = new LinkedList<>();
LinkedList<Node> stack = new LinkedList<>();
Node p = root;
while (p != null) {
stack.push(p);
p = p.left;
}
while (!stack.isEmpty()) {
Node cur = stack.pop();
list.offer(cur.e);
if (cur.right != null) {
Node n = cur.right;
while (n != null) {
stack.push(n);
n = n.left;
}
}
}
it = list.iterator();
}
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public E next() {
return it.next();
}
}