二叉树-java

树的基本术语

父节点,子节点: A 和B
兄弟节点:D E J
根节点:A
叶节点:G I

https://www.processon.com/mindmap/6073b23b7d9c081712e0b885

  • 节点高度:节点到叶节点的最长路径(边数)
  • 节点深度:根节点到这节点所经历的边的个数
  • 节点的层数:节点的深度 + 1
  • 树高度:根节点的高度

https://www.processon.com/mindmap/6073b23b7d9c081712e0b885

  • 节点的高度:一个人量身体各部位长度,从脚下拉尺到各部位(腿长)
  • 节点深度:量水中物品的深度,从水面拉尺到水中的宝藏
  • 节点的层数:尺子从 1 开始计数
  • 树高度:头到脚的高度

二叉树

最多有两个叉的树。

满二叉树:叶子节点全在最底层,除了叶子节点之外,每个节点都有左右两个子节点。

二叉树结构 (1)

完全二叉树:叶子节点都在最底下两层,最后一层的叶子节点都靠左排列,并且除了最后一层,其他层的节点个数都要达到最大。

二叉树结构 (2)

满二叉树和完全二叉树的意义是什么?

  • 节点 X 在数组下标 i
  • 左子节点:2 * i
  • 右子节点:2 * i + 1
  • 父节点:i/2

二叉树的遍历

二叉树结构 (1)

根据节点打印的顺序。比如:

  • 前序遍历:节点 → 左子树 → 右子树。A->B->D->E->C->F->G
  • 中序遍历:左子树 → 节点 → 右子树。D->B->E->A->F->C->G
  • 后序遍历:左子树 → 右子树 → 节点。D->E->B->F->G->C->A

递推关系式:


前序遍历的递推公式:
preOrder(r) = print r->preOrder(r->left)->preOrder(r->right)

中序遍历的递推公式:
inOrder(r) = inOrder(r->left)->print r->inOrder(r->right)

后序遍历的递推公式:
postOrder(r) = postOrder(r->left)->postOrder(r->right)->print r

代码:


void preOrder(Node* root) {
  if (root == null) return;
  print root // 此处为伪代码,表示打印root节点
  preOrder(root->left);
  preOrder(root->right);
}

void inOrder(Node* root) {
  if (root == null) return;
  inOrder(root->left);
  print root // 此处为伪代码,表示打印root节点
  inOrder(root->right);
}

void postOrder(Node* root) {
  if (root == null) return;
  postOrder(root->left);
  postOrder(root->right);
  print root // 此处为伪代码,表示打印root节点
}

复杂度:

O(n):遍历操作的时间复杂度,跟节点的个数 n 成正比

二叉查找树

左子树每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。

二叉树结构 (4)|330x275

查找

根节点
-比之小> 左子树中递归查找。
-比之大> 右子树中递归查找。

插入

  • 从根节点开始比较,如果比之大,并且节点右子树为空,就将新数据插到右子节点;
  • 如果不为空,就再递归遍历右子树,查找插入位置。
  • 如果要插入的数据比节点数值小,并且节点的左子树为空,就将新数据插入到左子节点的位置。
  • 如果不为空,就再递归遍历左子树,查找插入位置。

二叉树结构 (4)

删除

情况一:要删除的节点没有子节点

只需要直接将父节点中,指向要删除节点的指针置为 null。比如图中的删除节点 55。

情况二:如果要删除的节点只有一个子节点(只有左子节点或者右子节点),

只需要更新父节点中,指向要删除节点的指针,让它指向要删除节点的子节点就可以了。比如图中的删除节点 13。

情况三:如果要删除的节点有两个子节点

  • 找到这个节点的右子树中的最小节点,把它替换到要删除的节点上。
  • 然后再删除掉这个最小节点,因为最小节点肯定没有左子节点(如果有左子结点,那就不是最小节点了),所以,我们可以应用上面两条规则来删除这个最小节点。比如图中的删除节点 18。

取巧方法:将要删除的节点标记为“已删除”。

重要特性

中序遍历二叉查找树,可以得到有序的数据序列,时间复杂度是 O(n)。因此,二叉查找树又称二叉排序树。

import org.graalvm.compiler.nodes.calc.RightShiftNode;

public class BinarySearchTree {
    Node tree;

    public void insert(int value) {
        if (tree == null) {
            tree = new Node(value);
            return;
        }
        Node p = tree;
        while (p != null) {
            if (value > p.data) {
                if (p.right == null) {
                    p.right = new Node(value);
                    return;
                }
                p = p.right;
            } else if (value < p.data) {
                if (p.left == null) {
                    p.left = new Node(value);
                    return;
                }
                p = p.left;
            }
        }
    }

    public Node find(int value) {
        Node p = tree;
        while (p != null) {
            if (value > p.data)
                p = p.right;
            else if (value < p.data)
                p = p.left;
            else
                return p;
        }

        return null;

    }

    public void delete(int value) {
        // 要删除的结点
        Node p = tree;
        // 要删除结点的父结点
        Node pp = null;

        while (p != null && p.data != value) {
            if (value > p.data) {
                pp = p;
                p = p.right;
            }
            else if (value < p.data) {
                pp = p;
                p = p.left;
            }
        }
        // 没找到
        if (p == null)
            return;

        if (p.left != null && p.right != null) {
            Node tmpP = p.right;
            Node tmpPP = p;
            while (tmpP.left != null) {
                tmpPP = tmpP;
                tmpP = tmpP.left;
            }
            p.data = tmpP.data;
            p = tmpP;
            pp = tmpPP;
        }

        // 如果 p 结点下面有一个或没有孩子时
        Node child;
        if (p.left != null)
            child = p.left;
        else if (p.right != null)
            child = p.right;
        else
            child = null;

        // 把孩子接到 p 的父结点上 pp
        
        if (pp == null)
            tree = child;
        else if (pp.right == p)
            pp.right = child;
        else if (pp.left == p)
            pp.left = child;

    }

    public void preOrder(Node node) {
        if (node == null) return;
        System.out.println(node.data);
        preOrder(node.left);
        preOrder(node.right);
    }

    public void inOrder(Node node) {
        if (node == null) return;
        inOrder(node.left);
        System.out.println(node.data);
        inOrder(node.right);
    }

    public void postOrder(Node node) {
        if (node == null) return;
        postOrder(node.left);
        postOrder(node.right);
        System.out.println(node.data);
    }

    public static class Node {
        int data;
        Node left;
        Node right;

        public Node(int data) {
            this.data = data;

        }
    }

    public static void main(String[] args) {
        BinarySearchTree binarySearchTree = new BinarySearchTree();
        int[] data = new int[] { 1, 10, 20, 40, 13 };
        for (int i : data) {
            binarySearchTree.insert(i);
        }
        System.out.println(20 == binarySearchTree.find(20).data);
        binarySearchTree.delete(20);
        System.out.println(null == binarySearchTree.find(20));
        // 1 10 40 13
        binarySearchTree.preOrder(binarySearchTree.tree);
        System.out.println("-----------------------");
        // 1 10 13 40
        binarySearchTree.inOrder(binarySearchTree.tree);
        System.out.println("-----------------------");
        // 13 40 10 1
        binarySearchTree.postOrder(binarySearchTree.tree);

    }
}