二叉搜索树(BST)详解

news2024/11/18 23:44:01

文章目录

  • 性质
  • 二叉搜索树的遍历
    • 遍历伪代码实现
  • 二叉搜索树的查找
    • 伪代码实现
  • 二叉搜索树最大元素
    • 伪代码实现
  • 二叉搜索树最小元素
    • 伪代码实现
  • 二叉搜索树的插入
    • 伪代码实现
  • 二叉搜索树的删除
    • 删除叶子节点(对应上面第一种情况):
    • 删除度为1的节点(对应上面第二种情况):
    • 前驱和后继
      • 寻找后继节点
    • 删除度为2的节点(对应上面第三种情况):
  • 二叉搜索树不平衡
  • 通过随机方式构建二叉搜索树
  • 完整的Java测试代码:
    • BinarySearchTree
    • BinaryTreeInfo
    • BinaryTrees
    • InorderPrinter
    • LevelOrderPrinter
    • Printer
    • Strings
    • 总结

二叉搜索树是一个有序树

性质

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 左、右子树也分别为二叉搜索树;
    在这里插入图片描述
    如图所示,两棵都是二叉排序树;
    在这里插入图片描述
    如图所示,左右两棵树都是是二叉搜索树。

二叉搜索树的遍历

二叉搜索树性质允许我们通过一个简单的递归算法来按序输出二叉搜索树中的所有关键字,这种算法称为中序遍历(遍历方法介绍与实现请看二叉树的遍历方式博文)算法。

遍历伪代码实现

在这里插入图片描述

二叉搜索树的查找

在一棵二叉搜索树中查找一个具有给定关键字的结点。
输人一个指向树根的指针和一个关键字k,如果这个结点存在,TREE-SEARCH返回一个指向关键字为k的结点的指针;否则返回 Null。

伪代码实现

在这里插入图片描述
也可以使用while循环来展开递归去实现,一般情况对于树的问题还是使用递归更简单,但是while循环的效率要高很多,得取舍,如果面试不会做题节约时间可递归简单实现,实际需要专研一下while循环实现。
在这里插入图片描述

二叉搜索树最大元素

一路向右查找

伪代码实现

在这里插入图片描述

二叉搜索树最小元素

一路向左查找

伪代码实现

在这里插入图片描述

二叉搜索树的插入

要将一个新值u插人到一棵二叉搜索树T中,需要调用过程 TREE-INSERT。该过程以结点z作为输人,其中z.key=u,z.left=NIL,z.right=NIL。这个过程要修改 T和z 的某些属性来把z插人到树中的相应位置上。

伪代码实现

在这里插入图片描述

二叉搜索树的删除

从一棵二叉搜索树 T中删除一个结点z 的整个策略分为三种基本情况(如下所述),但只有1种情况有点棘手。二叉搜索树要求删除节点后还是一棵二叉搜索树。

  • 1、如果z没有孩子结点,那么只是简单地将它删除,并修改它的父结点,用 NIL 作为孩子0来替换 z。
  • 2、如果z只有一个孩子,那么将这个孩子提升到树中z的位置上,并修改z的父结点,用z0的孩子来替换 z。
  • 3、如果z有两个孩子,那么找z的后继y(一定在z的右子树中),并让y占据树中z 的位置。z的原来右子树部分成为y 的新的右子树,并且 z 的左子树成为y的新的左子树。这种情况稍显麻烦(如下所述),因为还与 y是否为z 的右孩子相关。(这种情况比较麻烦,得注意)
    注意:这里可以用z的后继或者是用z的前驱两种方式实现删除。

删除叶子节点(对应上面第一种情况):

这种情况可以直接删除节点,假设要删除的节点为node,则
1、当node.parent == null时,即二叉树只有一个根节点且删除的就是根节点的情况
root = null

2、当node == node.parent.left时,即节点是父节点的左孩子,直接把左孩子置为空
node.parent.left = null

3、当node == node.parent.right时,即节点是父节点的右孩子,直接把右孩子置为空
node.parent.right = null

在这里插入图片描述

删除度为1的节点(对应上面第二种情况):

用子节点代替原节点的位置,假设node是原节点,son是子节点
1、如果node是根节点
root = son
son.parent = null
如图所示:
在这里插入图片描述
2、如果node是左子节点
son.parent = node.parent
node.parent.left = son

3、如果node是右子节点
son.parent = node.parent
node.parent.right = son

在这里插入图片描述

前驱和后继

根据中序遍历(左中右)看图说话:
在这里插入图片描述
如图中对于节点10而言,后继是12,前驱是9;
再举一个例子:对于9而言,前驱是5,后继是10。

寻找后继节点

如果后继存在,下面的过程将返回一棵二叉搜索树中的结点x的后继;如果x是这棵树中的最大关键字,则返回 NIL.
在这里插入图片描述
把上图TREE-SUCCESSOR 的伪代码分为两种情况。如果结点x的右子树非空,那么x的后继恰是x 右子树中的最左结点,通过第2行中的 TREE-MINIMUM(x.right)调用可以找到,即返回右子树的最小值。
在这里插入图片描述
如图12节点的后继就是15;

另一方面,如果结点x的右子树非空并有一个后继y,那么y就是x的有左孩子的最底层祖先,并且它也是x的一个祖先。在上面中,关键字为 10的结点的后继是关键字为12的结点。为了找到 y,只需简单地从x开始沿树而上直到遇到一个其双亲有左孩子的结点。TREE-SUCCESSOR 中的第 3~7 行正是处理这种情况。
说简单一点,10->9->5这个过程中,10一直在右子树上面,当10->9->5->12时,10第一次从挂在右子树变成挂在左子树,所以12就是10的后继。
对19而言,就是后继为空的情况。

前驱的情况就是和后继对称的,掌握一种就可以了。

删除度为2的节点(对应上面第三种情况):

假如要删除节点12
1、先用前驱或者后继节点的值覆盖原节点的值
2、然后删除相应的前驱或者后继节点
如果一个节点的度为2,那么,它的前驱、后继节点的度只可能为1或者0,当前驱、后继节点的度为1时,前驱节点只有左孩子,后继节点只有右孩子。
在这里插入图片描述

二叉搜索树不平衡

假如构建二叉搜索树的过程中,数据是高度有序的(包括顺序和逆序),此时构建出的二又搜索树会高度不平衡,甚至退化成链表,增删改查的效率大大降低。
在这里插入图片描述

通过随机方式构建二叉搜索树

随机构建二叉搜索树(randomly built binary search tree)为按随机次序插人这些关键字到一棵初始的空树中而生成的树,一棵有n个不同关键字的随机构建二叉搜索树的期望高度为O(lgn)。

完整的Java测试代码:

BinarySearchTree

package binarytree;

/**
 * @Author hepingfu
 * @Date 2023/05/03/15:37
 * @Version 1.0
 */
public class BinarySearchTree implements BinaryTreeInfo {
    private Node root;

    public Node getRoot() {
        return root;
    }

    public void setRoot(Node root) {
        this.root = root;
    }

    public BinarySearchTree() {
        this.root = null;
    }

    public BinarySearchTree(Node root) {
        this.root = root;
    }

    //前序遍历二叉树
    public void preorderTreeWalk(Node x) {
        if(x != null) {
            System.out.print(x.key + " ");
            preorderTreeWalk(x.left);
            preorderTreeWalk(x.right);
        }
    }

    //中序遍历二叉树
    public void inorderTreeWalk(Node x) {
        if(x != null) {
            inorderTreeWalk(x.left);
            System.out.print(x.key + " ");
            inorderTreeWalk(x.right);
        }
    }

    //后序遍历二叉树
    public void postorderTreeWalk(Node x) {
        if(x != null) {
            postorderTreeWalk(x.left);
            postorderTreeWalk(x.right);
            System.out.print(x.key + " ");
        }
    }

    //在二叉搜索树中查询某个值,递归版本
    public Node treeSearch(Node x, Integer k) {
        if(x == null || k == x.key) {
            return x;
        }

        if(k < x.key) {
            return treeSearch(x.left, k);
        } else {
            return treeSearch(x.right, k);
        }
    }

    //在二叉搜索树中查询某个值,循环版本
    public Node iterativeTreeSearch(Node x, Integer k) {
        while(x != null && k != x.key) {
            if(k < x.key) {
                x = x.left;
            } else {
                x = x.right;
            }
        }

        return x;
    }

    //在二叉搜索树中查找包含最小值的节点
    public Node treeMinimum(Node x) {
        while (x.left != null) {
            x = x.left;
        }

        return x;
    }

    //在二叉搜索树中查找包含最大值的节点
    public Node treeMaximum(Node x) {
        while (x.right != null) {
            x = x.right;
        }

        return x;
    }

    //查找节点x的后继节点
    public Node treeSuccessor(Node x) {
        if(x.right != null) {  //x的右子树不为空,找右子树的最小值
            return treeMinimum(x.right);
        }

        Node y = x.parent;
        while(y != null && x == y.right) {
            x = y;
            y = y.parent;
        }

        return y;
    }

    //查找节点x的前驱节点
    public Node treePredeceessor(Node x) {
        if(x.left != null) {
            return treeMaximum(x.left);
        }

        Node y = x.parent;
        while(y != null && x == y.left) {
            x = y;
            y = y.parent;
        }

        return y;
    }

    //二叉搜索树的插入操作
    public void treeInsert(BinarySearchTree t, Node z) {
        Node y = null;
        Node x = t.root;
        while(x != null) {
            y = x;
            if(z.key < x.key) {
                x = x.left;
            } else {
                x = x.right;
            }
        }

        z.parent = y;
        if(y == null) {  //说明现在是棵空树
            t.root = z;
        } else if (z.key < y.key) {
            y.left = z;
        } else {
            y.right = z;
        }
    }

    public void transplant(BinarySearchTree t, Node u, Node v) {
        if(u.parent == null) {
            t.root = v;
        } else if(u == u.parent.left) {
            u.parent.left = v;
        } else {
            u.parent.right = v;
        }

        if(v != null) {
            v.parent = u.parent;
        }
    }

    //算法导论书上的删除节点操作
    public void treeDelete(BinarySearchTree t, Node z) {

        if (z.left == null) {
            //z的左孩子为空,用z的右孩子替换z,此时z的右孩子可以为空,也可以不为空
            transplant(t, z, z.right);
        } else if (z.right == null) {
            //z仅有一个孩子且其为左孩子,用z的左孩子替换z
            transplant(t, z , z.left);
        } else {
            //z有两个孩子,找z的后继节点
            Node y = treeMinimum(z.right);
            //y作为后继节点,只可能有右孩子,不可能有左孩子
            if(y.parent != z) {
                //用以y的右孩子为根的树代替以y为根的树
                transplant(t, y, y.right);
                y.right = z.right;
                y.right.parent = y;
            }

            //用以y为根的树代替以z为根的树
            transplant(t, z, y);
            y.left = z.left;
            y.left.parent = y;
        }
    }

    //更容易看懂的删除节点操作
    public void remove(Node node) {
        if (node == null) return;

        if (node.left != null && node.right != null) { // 度为2的节点
            // 找到后继节点
            Node s = treeSuccessor(node);
            // 用后继节点的值覆盖度为2的节点的值
            node.key = s.key;
            // 为删除后继节点做准备
            node = s;
        }

        // 删除node节点(node的度必然是1或者0)
        Node replacement = node.left != null ? node.left : node.right;

        if (replacement != null) { // node是度为1的节点
            // 更改parent
            replacement.parent = node.parent;
            // 更改parent的left、right的指向
            if (node.parent == null) { // node是度为1的节点并且是根节点
                root = replacement;
            } else if (node == node.parent.left) {
                node.parent.left = replacement;
            } else { // node == node.parent.right
                node.parent.right = replacement;
            }
        } else if (node.parent == null) { // node是叶子节点并且是根节点
            root = null;
        } else { // node是叶子节点,但不是根节点
            if (node == node.parent.left) {
                node.parent.left = null;
            } else { // node == node.parent.right
                node.parent.right = null;
            }
        }
    }

    //节点类定义
    static class Node {
        private Integer key;  //节点值
        private Node parent;  //父节点
        private Node left;   //左孩子
        private Node right;   //右孩子

        public Node() {

        }

        public Node(Integer key) {
            this.key = key;
        }

        public Node(Integer key, Node parent) {
            this.parent = parent;
            this.key = key;
        }

        public Node(Node parent, Node left, Node right, Integer key) {
            this.parent = parent;
            this.left = left;
            this.right = right;
            this.key = key;
        }

        public Integer getKey() {
            return key;
        }

        public void setKey(Integer key) {
            this.key = key;
        }

        public Node getParent() {
            return parent;
        }

        public void setParent(Node parent) {
            this.parent = parent;
        }

        public Node getLeft() {
            return left;
        }

        public void setLeft(Node left) {
            this.left = left;
        }

        public Node getRight() {
            return right;
        }

        public void setRight(Node right) {
            this.right = right;
        }
    }

    @Override
    public Object root() {
        return root;
    }

    @Override
    public Object left(Object node) {
        return ((Node)node).left;
    }

    @Override
    public Object right(Object node) {
        return ((Node)node).right;
    }

    @Override
    public Object string(Object node) {
        Node myNode = (Node)node;

        return myNode.key + "";
    }

    public static void main(String[] args) {
        BinarySearchTree bst = new BinarySearchTree();

        bst.treeInsert(bst, new Node(12));
        bst.treeInsert(bst, new Node(5));
        bst.treeInsert(bst, new Node(2));
        bst.treeInsert(bst, new Node(9));
        bst.treeInsert(bst, new Node(18));
        bst.treeInsert(bst, new Node(15));
        bst.treeInsert(bst, new Node(19));
        bst.treeInsert(bst, new Node(17));

        bst.inorderTreeWalk(bst.root);

        System.out.println();

        BinaryTrees.print(bst);

//        //两种方式删除根节点12
        bst.treeDelete(bst, bst.treeSearch(bst.root, 12));
//        bst.remove(bst.treeSearch(bst.root, 12));

//        //两种方式删除叶子节点17
        // bst.treeDelete(bst, bst.treeSearch(bst.root, 17));
//        bst.remove(bst.treeSearch(bst.root, 17));

//        //两种方式删除只有一个孩子的节点15
//        bst.treeDelete(bst, bst.treeSearch(bst.root, 15));
//        bst.remove(bst.treeSearch(bst.root, 15));

        System.out.println();

        BinaryTrees.print(bst);
    }
}

BinaryTreeInfo

package binarytree;

/**
 * @Author hepingfu
 * @Date 2023/05/03/15:32
 * @Version 1.0
 */
public interface BinaryTreeInfo {
    /**
     * who is the root node
     */
    Object root();
    /**
     * how to get the left child of the node
     */
    Object left(Object node);
    /**
     * how to get the right child of the node
     */
    Object right(Object node);
    /**
     * how to print the node
     */
    Object string(Object node);
}

BinaryTrees

package binarytree;

/**
 * @Author hepingfu
 * @Date 2023/05/03/15:31
 * @Version 1.0
 */
public class BinaryTrees {
    private BinaryTrees() {
    }

    public static void print(BinaryTreeInfo tree) {
        print(tree, null);
    }

    public static void println(BinaryTreeInfo tree) {
        println(tree, null);
    }

    public static void print(BinaryTreeInfo tree, PrintStyle style) {
        if (tree == null || tree.root() == null) return;
        printer(tree, style).print();
    }

    public static void println(BinaryTreeInfo tree, PrintStyle style) {
        if (tree == null || tree.root() == null) return;
        printer(tree, style).println();
    }

    public static String printString(BinaryTreeInfo tree) {
        return printString(tree, null);
    }

    public static String printString(BinaryTreeInfo tree, PrintStyle style) {
        if (tree == null || tree.root() == null) return null;
        return printer(tree, style).printString();
    }

    private static Printer printer(BinaryTreeInfo tree, PrintStyle style) {
        if (style == PrintStyle.INORDER) return new InorderPrinter(tree);
        return new LevelOrderPrinter(tree);
    }

    public enum PrintStyle {
        LEVEL_ORDER, INORDER
    }
}

InorderPrinter

package binarytree;

/**
 * @Author hepingfu
 * @Date 2023/05/03/15:34
 * @Version 1.0
 */
/**

 ┌──800
 ┌──760
 │   └──600
 ┌──540
 │   └──476
 │       └──445
 ┌──410
 │   └──394
 381
 │     ┌──190
 │     │   └──146
 │  ┌──40
 │  │  └──35
 └──12
 └──9
 */
public class InorderPrinter extends Printer {
    private static String rightAppend;
    private static String leftAppend;
    private static String blankAppend;
    private static String lineAppend;
    static {
        int length = 2;
        rightAppend = "┌" + Strings.repeat("─", length);
        leftAppend = "└" + Strings.repeat("─", length);
        blankAppend = Strings.blank(length + 1);
        lineAppend = "│" + Strings.blank(length);
    }

    public InorderPrinter(BinaryTreeInfo tree) {
        super(tree);
    }

    @Override
    public String printString() {
        StringBuilder string = new StringBuilder(
                printString(tree.root(), "", "", ""));
        string.deleteCharAt(string.length() - 1);
        return string.toString();
    }

    /**
     * 生成node节点的字符串
     * @param nodePrefix node那一行的前缀字符串
     * @param leftPrefix node整棵左子树的前缀字符串
     * @param rightPrefix node整棵右子树的前缀字符串
     * @return
     */
    private String printString(
            Object node,
            String nodePrefix,
            String leftPrefix,
            String rightPrefix) {
        Object left = tree.left(node);
        Object right = tree.right(node);
        String string = tree.string(node).toString();

        int length = string.length();
        if (length % 2 == 0) {
            length--;
        }
        length >>= 1;

        String nodeString = "";
        if (right != null) {
            rightPrefix += Strings.blank(length);
            nodeString += printString(right,
                    rightPrefix + rightAppend,
                    rightPrefix + lineAppend,
                    rightPrefix + blankAppend);
        }
        nodeString += nodePrefix + string + "\n";
        if (left != null) {
            leftPrefix += Strings.blank(length);
            nodeString += printString(left,
                    leftPrefix + leftAppend,
                    leftPrefix + blankAppend,
                    leftPrefix + lineAppend);
        }
        return nodeString;
    }
}

LevelOrderPrinter

package binarytree;

/**
 * @Author hepingfu
 * @Date 2023/05/03/15:36
 * @Version 1.0
 */
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

/**

 ┌───381────┐
 │          │
 ┌─12─┐     ┌─410─┐
 │    │     │     │
 9  ┌─40─┐ 394 ┌─540─┐
 │    │     │     │
 35 ┌─190 ┌─476 ┌─760─┐
 │     │     │     │
 146   445   600   800
*/
public class LevelOrderPrinter extends Printer {
    /**
     * 节点之间允许的最小间距(最小只能填1)
     */
    private static final int MIN_SPACE = 1;
    private Node root;
    private int minX;
    private int maxWidth;
    private List<List<Node>> nodes;

    public LevelOrderPrinter(BinaryTreeInfo tree) {
        super(tree);

        root = new Node(tree.root(), tree);
        maxWidth = root.width;
    }

    @Override
    public String printString() {
        // nodes用来存放所有的节点
        nodes = new ArrayList<>();

        fillNodes();
        cleanNodes();
        compressNodes();
        addLineNodes();

        int rowCount = nodes.size();

        // 构建字符串
        StringBuilder string = new StringBuilder();
        for (int i = 0; i < rowCount; i++) {
            if (i != 0) {
                string.append("\n");
            }

            List<Node> rowNodes = nodes.get(i);
            StringBuilder rowSb = new StringBuilder();
            for (Node node : rowNodes) {
                int leftSpace = node.x - rowSb.length() - minX;
                rowSb.append(Strings.blank(leftSpace));
                rowSb.append(node.string);
            }

            string.append(rowSb);
        }

        return string.toString();
    }

    /**
     * 添加一个元素节点
     */
    private Node addNode(List<Node> nodes, Object btNode) {
        Node node = null;
        if (btNode != null) {
            node = new Node(btNode, tree);
            maxWidth = Math.max(maxWidth, node.width);
            nodes.add(node);
        } else {
            nodes.add(null);
        }
        return node;
    }

    /**
     * 以满二叉树的形式填充节点
     */
    private void fillNodes() {
        // 第一行
        List<Node> firstRowNodes = new ArrayList<>();
        firstRowNodes.add(root);
        nodes.add(firstRowNodes);

        // 其他行
        while (true) {
            List<Node> preRowNodes = nodes.get(nodes.size() - 1);
            List<Node> rowNodes = new ArrayList<>();

            boolean notNull = false;
            for (Node node : preRowNodes) {
                if (node == null) {
                    rowNodes.add(null);
                    rowNodes.add(null);
                } else {
                    Node left = addNode(rowNodes, tree.left(node.btNode));
                    if (left != null) {
                        node.left = left;
                        left.parent = node;
                        notNull = true;
                    }

                    Node right = addNode(rowNodes, tree.right(node.btNode));
                    if (right != null) {
                        node.right = right;
                        right.parent = node;
                        notNull = true;
                    }
                }
            }

            // 全是null,就退出
            if (!notNull) break;
            nodes.add(rowNodes);
        }
    }

    /**
     * 删除全部null、更新节点的坐标
     */
    private void cleanNodes() {
        int rowCount = nodes.size();
        if (rowCount < 2) return;

        // 最后一行的节点数量
        int lastRowNodeCount = nodes.get(rowCount - 1).size();

        // 每个节点之间的间距
        int nodeSpace = maxWidth + 2;

        // 最后一行的长度
        int lastRowLength = lastRowNodeCount * maxWidth
                + nodeSpace * (lastRowNodeCount - 1);

        // 空集合
        Collection<Object> nullSet = Collections.singleton(null);

        for (int i = 0; i < rowCount; i++) {
            List<Node> rowNodes = nodes.get(i);

            int rowNodeCount = rowNodes.size();
            // 节点左右两边的间距
            int allSpace = lastRowLength - (rowNodeCount - 1) * nodeSpace;
            int cornerSpace = allSpace / rowNodeCount - maxWidth;
            cornerSpace >>= 1;

            int rowLength = 0;
            for (int j = 0; j < rowNodeCount; j++) {
                if (j != 0) {
                    // 每个节点之间的间距
                    rowLength += nodeSpace;
                }
                rowLength += cornerSpace;
                Node node = rowNodes.get(j);
                if (node != null) {
                    // 居中(由于奇偶数的问题,可能有1个符号的误差)
                    int deltaX = (maxWidth - node.width) >> 1;
                    node.x = rowLength + deltaX;
                    node.y = i;
                }
                rowLength += maxWidth;
                rowLength += cornerSpace;
            }
            // 删除所有的null
            rowNodes.removeAll(nullSet);
        }
    }

    /**
     * 压缩空格
     */
    private void compressNodes() {
        int rowCount = nodes.size();
        if (rowCount < 2) return;

        for (int i = rowCount - 2; i >= 0; i--) {
            List<Node> rowNodes = nodes.get(i);
            for (Node node : rowNodes) {
                Node left = node.left;
                Node right = node.right;
                if (left == null && right == null) continue;
                if (left != null && right != null) {
                    // 让左右节点对称
                    node.balance(left, right);

                    // left和right之间可以挪动的最小间距
                    int leftEmpty = node.leftBoundEmptyLength();
                    int rightEmpty = node.rightBoundEmptyLength();
                    int empty = Math.min(leftEmpty, rightEmpty);
                    empty = Math.min(empty, (right.x - left.rightX()) >> 1);

                    // left、right的子节点之间可以挪动的最小间距
                    int space = left.minLevelSpaceToRight(right) - MIN_SPACE;
                    space = Math.min(space >> 1, empty);

                    // left、right往中间挪动
                    if (space > 0) {
                        left.translateX(space);
                        right.translateX(-space);
                    }

                    // 继续挪动
                    space = left.minLevelSpaceToRight(right) - MIN_SPACE;
                    if (space < 1) continue;

                    // 可以继续挪动的间距
                    leftEmpty = node.leftBoundEmptyLength();
                    rightEmpty = node.rightBoundEmptyLength();
                    if (leftEmpty < 1 && rightEmpty < 1) continue;

                    if (leftEmpty > rightEmpty) {
                        left.translateX(Math.min(leftEmpty, space));
                    } else {
                        right.translateX(-Math.min(rightEmpty, space));
                    }
                } else if (left != null) {
                    left.translateX(node.leftBoundEmptyLength());
                } else { // right != null
                    right.translateX(-node.rightBoundEmptyLength());
                }
            }
        }
    }

    private void addXLineNode(List<Node> curRow, Node parent, int x) {
        Node line = new Node("─");
        line.x = x;
        line.y = parent.y;
        curRow.add(line);
    }

    private Node addLineNode(List<Node> curRow, List<Node> nextRow, Node parent, Node child) {
        if (child == null) return null;

        Node top = null;
        int topX = child.topLineX();
        if (child == parent.left) {
            top = new Node("┌");
            curRow.add(top);

            for (int x = topX + 1; x < parent.x; x++) {
                addXLineNode(curRow, parent, x);
            }
        } else {
            for (int x = parent.rightX(); x < topX; x++) {
                addXLineNode(curRow, parent, x);
            }

            top = new Node("┐");
            curRow.add(top);
        }

        // 坐标
        top.x = topX;
        top.y = parent.y;
        child.y = parent.y + 2;
        minX = Math.min(minX, child.x);

        // 竖线
        Node bottom = new Node("│");
        bottom.x = topX;
        bottom.y = parent.y + 1;
        nextRow.add(bottom);

        return top;
    }

    private void addLineNodes() {
        List<List<Node>> newNodes = new ArrayList<>();

        int rowCount = nodes.size();
        if (rowCount < 2) return;

        minX = root.x;

        for (int i = 0; i < rowCount; i++) {
            List<Node> rowNodes = nodes.get(i);
            if (i == rowCount - 1) {
                newNodes.add(rowNodes);
                continue;
            }

            List<Node> newRowNodes = new ArrayList<>();
            newNodes.add(newRowNodes);

            List<Node> lineNodes = new ArrayList<>();
            newNodes.add(lineNodes);
            for (Node node : rowNodes) {
                addLineNode(newRowNodes, lineNodes, node, node.left);
                newRowNodes.add(node);
                addLineNode(newRowNodes, lineNodes, node, node.right);
            }
        }

        nodes.clear();
        nodes.addAll(newNodes);
    }

    private static class Node {
        /**
         * 顶部符号距离父节点的最小距离(最小能填0)
         */
        private static final int TOP_LINE_SPACE = 1;

        Object btNode;
        Node left;
        Node right;
        Node parent;
        /**
         * 首字符的位置
         */
        int x;
        int y;
        int treeHeight;
        String string;
        int width;

        private void init(String string) {
            string = (string == null) ? "null" : string;
            string = string.isEmpty() ? " " : string;

            width = string.length();
            this.string = string;
        }

        public Node(String string) {
            init(string);
        }

        public Node(Object btNode, BinaryTreeInfo opetaion) {
            init(opetaion.string(btNode).toString());

            this.btNode = btNode;
        }

        /**
         * 顶部方向字符的X(极其重要)
         *
         * @return
         */
        private int topLineX() {
            // 宽度的一半
            int delta = width;
            if (delta % 2 == 0) {
                delta--;
            }
            delta >>= 1;

            if (parent != null && this == parent.left) {
                return rightX() - 1 - delta;
            } else {
                return x + delta;
            }
        }

        /**
         * 右边界的位置(rightX 或者 右子节点topLineX的下一个位置)(极其重要)
         */
        private int rightBound() {
            if (right == null) return rightX();
            return right.topLineX() + 1;
        }

        /**
         * 左边界的位置(x 或者 左子节点topLineX)(极其重要)
         */
        private int leftBound() {
            if (left == null) return x;
            return left.topLineX();
        }

        /**
         * x ~ 左边界之间的长度(包括左边界字符)
         *
         * @return
         */
        private int leftBoundLength() {
            return x - leftBound();
        }

        /**
         * rightX ~ 右边界之间的长度(包括右边界字符)
         *
         * @return
         */
        private int rightBoundLength() {
            return rightBound() - rightX();
        }

        /**
         * 左边界可以清空的长度
         *
         * @return
         */
        private int leftBoundEmptyLength() {
            return leftBoundLength() - 1 - TOP_LINE_SPACE;
        }

        /**
         * 右边界可以清空的长度
         *
         * @return
         */
        private int rightBoundEmptyLength() {
            return rightBoundLength() - 1 - TOP_LINE_SPACE;
        }

        /**
         * 让left和right基于this对称
         */
        private void balance(Node left, Node right) {
            if (left == null || right == null) return;
            // 【left的尾字符】与【this的首字符】之间的间距
            int deltaLeft = x - left.rightX();
            // 【this的尾字符】与【this的首字符】之间的间距
            int deltaRight = right.x - rightX();

            int delta = Math.max(deltaLeft, deltaRight);
            int newRightX = rightX() + delta;
            right.translateX(newRightX - right.x);

            int newLeftX = x - delta - left.width;
            left.translateX(newLeftX - left.x);
        }

        private int treeHeight(Node node) {
            if (node == null) return 0;
            if (node.treeHeight != 0) return node.treeHeight;
            node.treeHeight = 1 + Math.max(
                    treeHeight(node.left), treeHeight(node.right));
            return node.treeHeight;
        }

        /**
         * 和右节点之间的最小层级距离
         */
        private int minLevelSpaceToRight(Node right) {
            int thisHeight = treeHeight(this);
            int rightHeight = treeHeight(right);
            int minSpace = Integer.MAX_VALUE;
            for (int i = 0; i < thisHeight && i < rightHeight; i++) {
                int space = right.levelInfo(i).leftX
                        - this.levelInfo(i).rightX;
                minSpace = Math.min(minSpace, space);
            }
            return minSpace;
        }

        private LevelInfo levelInfo(int level) {
            if (level < 0) return null;
            int levelY = y + level;
            if (level >= treeHeight(this)) return null;

            List<Node> list = new ArrayList<>();
            Queue<Node> queue = new LinkedList<>();
            queue.offer(this);

            // 层序遍历找出第level行的所有节点
            while (!queue.isEmpty()) {
                Node node = queue.poll();
                if (levelY == node.y) {
                    list.add(node);
                } else if (node.y > levelY) break;

                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }

            Node left = list.get(0);
            Node right = list.get(list.size() - 1);
            return new LevelInfo(left, right);
        }

        /**
         * 尾字符的下一个位置
         */
        public int rightX() {
            return x + width;
        }

        public void translateX(int deltaX) {
            if (deltaX == 0) return;
            x += deltaX;

            // 如果是LineNode
            if (btNode == null) return;

            if (left != null) {
                left.translateX(deltaX);
            }
            if (right != null) {
                right.translateX(deltaX);
            }
        }
    }

    private static class LevelInfo {
        int leftX;
        int rightX;

        public LevelInfo(Node left, Node right) {
            this.leftX = left.leftBound();
            this.rightX = right.rightBound();
        }
    }
}

Printer

package binarytree;

/**
 * @Author hepingfu
 * @Date 2023/05/03/15:35
 * @Version 1.0
 */
public abstract class Printer {
    /**
     * 二叉树的基本信息
     */
    protected BinaryTreeInfo tree;

    public Printer(BinaryTreeInfo tree) {
        this.tree = tree;
    }

    /**
     * 生成打印的字符串
     */
    public abstract String printString();

    /**
     * 打印后换行
     */
    public void println() {
        print();
        System.out.println();
    }

    /**
     * 打印
     */
    public void print() {
        System.out.print(printString());
    }
}

Strings

package binarytree;

/**
 * @Author hepingfu
 * @Date 2023/05/03/15:36
 * @Version 1.0
 */
public class Strings {
    public static String repeat(String string, int count) {
        if (string == null) return null;

        StringBuilder builder = new StringBuilder();
        while (count-- > 0) {
            builder.append(string);
        }
        return builder.toString();
    }

    public static String blank(int length) {
        if (length < 0) return null;
        if (length == 0) return "";
        return String.format("%" + length + "s", "");
    }
}

总结

这个实现过程比较复杂,更多的是在提现一个打印结果的实现,没有必要自己去手敲,可以全部拿下来,去运行,看具体核心实现二叉搜索树对应的那些功能。对于二叉搜索树更细节的去使用,我也还在探索中,之后会更加细节的去记录。

ps:计划每日更新一篇博客,今日2023-05-02,日更第十六天。
昨日更新:(补更)
leetcode100——相同的树

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/485252.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机械硬盘(HDD)与固态硬盘(SSD)

目录 机械硬盘&#xff08;HDD&#xff09; 最小组成单元是扇区 硬盘结构 硬盘工作原理 硬盘上的数据组织 硬盘指标 影响性能的因素 固态硬盘&#xff08;SSD&#xff09; 最小存储单元是Cell SSD的特点 SSD架构 NAND Flash 闪存介质 地址映射管理 FTL闪存转换层 机械硬盘&…

Python之模块和包(九)

1、模块 1、模块概述 模块是一个包含了定义的函数和变量等的文件。模块可以被程序引入&#xff0c;以使用该模块中的函数等功能。通俗讲&#xff1a;模块就好比是工具包&#xff0c;要想使用这个工具包中的工具(就好比函数)&#xff0c;就需要导入这个模块。 2、import 在P…

Redis分布式锁原理之实现秒杀抢优惠卷业务

Redis分布式锁原理之实现秒杀抢优惠卷业务 1. 实现秒杀下单2. 库存超卖问题分析2.1 乐观锁解决超卖问题 3. 优惠券秒杀-一人一单3.1 集群环境下的并发问题 4、分布式锁4.1 基本原理和实现方式对比4.2 Redis分布式锁的实现核心思路4.3 实现分布式锁版本一4.4 Redis分布式锁误删情…

【Java入门合集】第三章面向对象编程(上)

【Java入门合集】第三章面向对象编程&#xff08;上&#xff09; 博主&#xff1a;命运之光 专栏&#xff1a;JAVA入门 理解面向对象三大主要特征&#xff1b; 掌握类与对象的区别与使用&#xff1b; 掌握类中构造方法以及构造方法重载的概念及使用&#xff1b; 掌握包的定义、…

国民技术N32G430开发笔记(14)-IAP升级 usart2接收数据

IAP升级 Usart2接收数据 1、之前有一节我们将PA6 PA7复用成了usart2的功能&#xff0c;这一节我们用usart2接收来自树莓派的升级请求&#xff0c;然后完成N32G430的Iap升级。 2、接线 PA9 PA10 接usb转串口模块A&#xff0c;A模块插入电脑。 PA6 PA7 接usb转串口模块B&#xf…

【移动端网页布局】流式布局案例 ⑥ ( 多排按钮导航栏 | 设置浮动及宽度 | 设置图片样式 | 设置文本 )

文章目录 一、多排按钮导航栏样式及核心要点1、实现效果2、总体布局设计3、设置浮动及宽度4、设置图片样式5、设置文本 二、完整代码实例1、HTML 标签结构2、CSS 样式3、展示效果 一、多排按钮导航栏样式及核心要点 1、实现效果 要实现下面的导航栏效果 ; 2、总体布局设计 该导…

计算机网络笔记:DNS域名解析过程

基本概念 DNS是域名系统&#xff08;Domain Name System&#xff09;的缩写&#xff0c;也是TCP/IP网络中的一个协议。在Internet上域名与IP地址之间是一一对应的&#xff0c;域名虽然便于人们记忆&#xff0c;但计算机之间只能互相认识IP地址&#xff0c;域名和IP地址之间的转…

基于探路者算法的极限学习机(ELM)回归预测-附代码

基于探路者算法的极限学习机(ELM)回归预测 文章目录 基于探路者算法的极限学习机(ELM)回归预测1.极限学习机原理概述2.ELM学习算法3.回归问题数据处理4.基于探路者算法优化的ELM5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;本文利用探路者算法对极限学习机进行优化&…

算法 DAY45 动态规划07 70. 爬楼梯 322. 零钱兑换 279. 完全平方数 139. 单词拆分 多重背包

70. 爬楼梯 和377. 组合总和 Ⅳ (opens new window)基本就是一道题了。本题代码不长&#xff0c;题目也很普通&#xff0c;但稍稍一进阶就可以考察完全背包 class Solution { public:int climbStairs(int n) {vector<int> nums {1,2};vector<int> dp(n1,0);dp[0…

while语句和until语句顺便带点小实验

while语句和until语句 一、while用法二、Until循环语句三、趣味小实验猜价格的游戏&#xff08;价格是随机数&#xff09;写一个计算器脚本闲来无事去购物 一、while用法 for循环语句非常适用于列表对象无规律&#xff0c;且列表来源以固定&#xff08;如某个列表文件&#xf…

Android Studio开发图书管理系统APP

Android Studio开发项目图书管理系统项目视频展示&#xff1a; 点击进入图书管理系统项目视频 引 言 现在是一个信息高度发达的时代&#xff0c;伴随着科技的进步&#xff0c;文化的汲取&#xff0c;人们对于图书信息的了解与掌握也达到了一定的高度。尤其是学生对于知识的渴…

word构建基块:快速插入重复内容的高级剪切板

本文参考自 word录入技巧&#xff1a;如何用自动图文集快速插入重复内容 - 知乎 介绍 构建基块&#xff0c;它就是和剪切板一样&#xff0c;点一下就粘贴一份新的&#xff0c;用于解决大量重复内容的复制粘贴 构建基块包括自动图文集和快速表格&#xff0c;实际上都是构建基块…

Blender启动场景的修改

Blender启动场景的修改 1 使用版本2 现象描述3 解决方法4 启动场景路径5 清理场景资源5.1 空场景大小5.2 清理图片资源5.2.1 断开数据块关联5.2.2 断开伪用户关联5.2.3 断开多用户关联5.2.4 清理数据块5.2.6 文件校验 5.3 使用自建资源库 6 数据块类型 1 使用版本 Blender 3.3…

二叉树建立、遍历、打印(23春教学)

#include<stdio.h> #include <malloc.h> #include <conio.h> typedef char DataType; typedef struct Node {DataType data;struct Node *LChild;struct Node *RChild; }BitNode,*BitTree; void CreatBiTree(BitTree *bt)//用扩展先序遍历序列创建二叉树&am…

《LearnUE——基础指南:上篇—3》——GamePlay架构WorldContext,GameInstance,Engine之间的关系

目录 平行世界是真实存在的吗&#xff1f; 1.3.1 引言 1.3.2 世界管理局&#xff08;WorldContext&#xff09; 1.3.3 司法天神&#xff08;GameInstance&#xff09; 1.3.4 上帝&#xff08;Engine&#xff09; 1.4 总结 平行世界是真实存在的吗&#xff1f; 1.3.1 引言 …

DAY 51 LVS负载均衡——DR模式

数据包流向分析 &#xff08;1&#xff09;客户端发送请求到Director Server (负载均衡器)&#xff0c;请求的数据报文&#xff08;源IP是CIP&#xff0c;目标IP是VIP&#xff09;到达内核空间。 &#xff08;2&#xff09;Director Server 和Real Server 在同一个网络中&…

【五一创作】ERP实施-委外业务-委外采购业务

委外业务主要有两种业务形态&#xff1a;委外采购和工序外协&#xff0c;委外采购主要是在MM模块中实现&#xff0c;工序外协主要由PP模块实现&#xff0c;工序外协中的采购订单创建和采购收货由MM模块实现。 委外采购概念 委外采购&#xff0c;有些企业也称为带料委外或者分包…

【Spring框架全系列】方法注解@Bean的使用

&#x1f4ec;&#x1f4ec;哈喽&#xff0c;大家好&#xff0c;我是小浪。上篇博客我们介绍了五大类注解的使用方法&#xff0c;以及如何解决Spring使用五大类注解生成bean-Name的问题&#xff1b;那么&#xff0c;谈到如何更简单的读取和存储对象&#xff0c;这里我们还需要介…

如何防止系统发生异常时,别人传递过来的关键数据不丢失?(AOP + xxlJob)

需求 在开发中&#xff0c;客户每天需要定时调用我们的api去上传一些数据到数据库中&#xff0c;当数据库发生异常或者系统发生异常&#xff0c;上传的一些数据会丢失不做入库操作&#xff0c;现想防止数据库或系统发生异常&#xff0c;数据能不丢失&#xff0c;同时&#xff…

TryHackMe-AD证书模板

AD Certificate Templates SpecterOps 完成并作为白皮书发布的研究表明&#xff0c;可以利用配置错误的证书模板进行权限提升和横向移动。根据错误配置的严重性&#xff0c;它可能允许 AD 域上的任何低特权用户只需单击几下即可将其权限提升为企业域管理员的权限&#xff01; …