数据结构与算法(第一季):二叉搜索树

2021-11-04  本文已影响0人  萧1帅

一、二叉搜索树

image

<figcaption></figcaption>

二、二叉搜索树的接口设计

// 元素的数量
int size();
// 是否为空
boolean isEmpty();
// 清空所有元素
void clear();
// 添加元素
void add(E element);
// 删除元素
void remove(E element);
// 是否包含某元素
boolean contains(E element);
复制代码

注意: 二叉树的元素没有索引的概念

三、二叉搜索树的实现

1、类的设计

public class BinarySearchTree<E> {
    private int size;
    private Node<E> root;

    private static class Node<E> {
        E element;
        Node<E> left;
        Node<E> right;
        @SuppressWarnings("unused")
        Node<E> parent;
        public Node(E element, Node<E> parent) {
            this.element = element;
            this.parent = parent;
        }
    }
}
复制代码

2、添加节点

// 当element为null时, 抛出异常
private void elementNotNullElement(E element) {
    if (element == null) {
        throw new IllegalArgumentException("element must not be  null");
    }
}
复制代码
public void add(E element) {
    // 当element为null, 抛出异常
    elementNotNullElement(element);
}
复制代码
public void add(E element) {
    // 当element为null, 抛出异常
    elementNotNullElement(element);
    if (root == null) {
        root = new Node<>(element, null);
        size++;
    }
}
复制代码
if (新元素 > 节点元素值) {
    找到节点的右子节点
}else if (新元素 < 节点元素值) {
    找到节点的左子节点
}else {
    新元素覆盖旧元素
}
复制代码
/**
 * @return 0: e1等于e2, 整数: e1大于e2, 负数: e1小于e2
 */
private int compare(E e1, E e2) {
    // 此处判断e1和e2的大小
    return 0;
}
复制代码
Node<E> node = root;
int cmp = compare(element, node.element);
if (cmp > 0) {
    node = node.right;
}else if (cmp < 0) {
    node = node.left;
}else {
    node.element = element;
    return;
}
复制代码
public void add(E element) {
    // 当element为null, 抛出异常
    elementNotNullElement(element);
    if (root == null) {
        root = new Node<>(element, null);
        size++;
    }

    // 记录最终添加新节点的父节点
    Node<E> parent = root;
    // 需要查询的节点
    Node<E> node = root;
    // 记录最后一次比较结果
    int cmp = 0;
    while (node != null) {
        // 0: e1等于e2, 正数: e1大于e2, 负数: e1小于e2
        cmp = compare(element, node.element);
        parent = node;
        // cmp大于0, 说明element的值大于当前节点的值, 所以找到当前节点的右子节点
        if (cmp > 0) {
            node = node.right;
        }else if (cmp < 0) {
            // cmp大于0, 说明element的值小于当前节点的值, 所以找到当前节点的左子节点
            node = node.left;
        }else {  // 相等
            node.element = element;
            return;
        }
    }
    // cmp大于0, 说明element的值大于父节点的值, 需要放到右子节点
    if (cmp > 0) {
        parent.right = new Node<>(element, parent);
    }else {
        // cmp小于0, 说明element的值小于父节点的值, 需要放到左子节点
        parent.left = new Node<>(element, parent);
    }
    size++;
}
复制代码

3、节点值的对比

/**
 * @return 0: e1等于e2, 整数: e1大于e2, 负数: e1小于e2
 */
private int compare(E e1, E e2) {
    return ((Comparable<E>)e1).compareTo(e2);
}
复制代码

public class BinarySearchTree<E> {
    private int size;
    private Node<E> root;
    private Comparator<E> comparator;

    public BinarySearchTree(Comparator<E> comparator) {
        this.comparator = comparator;
    }
}
复制代码
/**
 * @return 0: e1等于e2, 整数: e1大于e2, 负数: e1小于e2
 */
private int compare(E e1, E e2) {
    if (comparator != null) {
        return comparator.compare(e1, e2);
    }
    return ((Comparable<E>)e1).compareTo(e2);
}
复制代码
public class Main {
    public static void main(String[] args) {
        BinarySearchTree<Integer> tree = new BinarySearchTree<>(new Comparator<Integer>() {
            // 两个值的对比结果
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
    }
}
复制代码

四、二叉树的遍历

1、代码准备

public class Main {
    public static void main(String[] args) {
        int[] data = new int[] {7, 4, 9, 2, 5, 8, 11, 1, 3, 10, 12};

        BinarySearchTree<Integer> tree = new BinarySearchTree<>();

        for (int i = 0; i < data.length; i++) {
            tree.add(data[I]);
        }
    }
}
复制代码

2、前序遍历

image

<figcaption></figcaption>

public void preorder() {
    preorder(root);
}
// 通过递归遍历所有的元素
public void preorder(Node<E> node) {
    if (node == null) return;
    // 先查看根节点
    System.out.println(node.element);
    // 再遍历左子树
    preorder(node.left);
    // 最后遍历右子树
    preorder(node.right);
}
复制代码

3、中序遍历

image

<figcaption></figcaption>

遍历顺序: 1、2、3、4、5、8、9、10、11、12

public void inorder() {
    inorder(root);
}

public void inorder(Node<E> node) {
    if (node == null) return;
    // 先中序遍历左子树
    inorder(node.left);
    // 再访问根节点
    System.out.println(node.element);
    // 最后遍历右子树
    inorder(node.right);
}
复制代码

4、后序遍历

image

<figcaption></figcaption>

public void postorder () {
    postorder(root);
}
public void postorder(Node<E> node) {
    if (node == null) return;
    // 先遍历左子树
    postorder(node.left);
    // 再遍历右子树
    postorder(node.right);
    // 最后访问根节点
    System.out.println(node.element);
}
复制代码

5、层序遍历

image

<figcaption></figcaption>

public void levelOrder() {
    if (root == null) return;
    Queue<Node<E>> queue = new LinkedList<>();
    // 根节点入队
    queue.offer(root);
    // 当queue为空时停止遍历
    while (!queue.isEmpty()) {
        // 节点出队
        Node<E> node = queue.poll();
        // 访问节点元素
        System.out.println(node.element);
        // 节点左子节点入队
        if (node.left != null) {
            queue.offer(node.left);
        }
        // 节点右子节点入队
        if (node.right != null) {
            queue.offer(node.right);
        }
    }
}
复制代码

五、外界获取遍历结果

1、定义接口, 用于抛出结果

public static interface Visitor<E> {
    void visit(E element);
}
复制代码

2、将Visitor作为遍历方法的参数

public static interface Visitor<E> {
    void visit(E element);
}
// 前序遍历
public void preorder(Visitor<E> visitor) {
    preorder(root, visitor);
}
// 通过递归遍历所有的元素
public void preorder(Node<E> node, Visitor<E> visitor) {
    if (node == null) return;
    // 先处理根节点
    visitor.visit(node.element);
    // 再遍历左子树
    preorder(node.left, visitor);
    // 最后遍历右子树
    preorder(node.right, visitor);
}

// 中序遍历
public void inorder(Visitor<E> visitor) {
    inorder(root, visitor);
}
public void inorder(Node<E> node, Visitor<E> visitor) {
    if (node == null) return;
    // 先中序遍历左子树
    inorder(node.left, visitor);
    // 再处理根节点
    visitor.visit(node.element);
    // 最后遍历右子树
    inorder(node.right, visitor);
}

// 后序遍历
public void postorder (Visitor<E> visitor) {
    postorder(root, visitor);
}
public void postorder(Node<E> node, Visitor<E> visitor) {
    if (node == null) return;
    // 先遍历左子树
    postorder(node.left, visitor);
    // 再遍历右子树
    postorder(node.right, visitor);
    // 最后处理根节点
    visitor.visit(node.element);
}

// 层序遍历 
public void levelOrder(Visitor<E> visitor) {
    if (root == null) return;
    Queue<Node<E>> queue = new LinkedList<>();
    queue.offer(root);

    while (!queue.isEmpty()) {
        Node<E> node = queue.poll();
        // 处理节点的元素
        visitor.visit(node.element);
        if (node.left != null) {
        queue.offer(node.left);
        }
        if (node.right != null) {
            queue.offer(node.right);
        }
    }
}
复制代码

3、外界使用

public class Main {
    public static void main(String[] args) {
        int[] data = new int[] {7, 4, 9, 2, 5, 8, 11, 1, 3, 10, 12};

        BinarySearchTree<Integer> tree = new BinarySearchTree<>();

        for (int i = 0; i < data.length; i++) {
            tree.add(data[I]);
        }
        // 前序遍历
        tree.preorder(new Visitor<Integer>() {
            public void visit(Integer element) {
                System.out.println(element);
            }
        });
        // 中序遍历
        tree.inorder(new Visitor<Integer>() {
            public void visit(Integer element) {
                System.out.println(element);
            }
        });
        // 后序遍历
        tree.postorder(new Visitor<Integer>() {
            public void visit(Integer element) {
                System.out.println(element);
            }
        });
        // 层序遍历
        tree.levelOrder(new Visitor<Integer>() {
            public void visit(Integer element) {
                System.out.println(element);
            }
        });
    }
}
复制代码

六、树状打印二叉树

public String toString() {
    StringBuffer sb = new StringBuffer();
    stringBufferTree(sb, root, "");
    return sb.toString(); 
}

private void stringBufferTree(StringBuffer sb, Node<E> node, String prefix) {
    if (node == null) return;
    stringBufferTree(sb, node.left, prefix + "L-");
    sb.append(prefix).append(node.element).append("\n");
    stringBufferTree(sb, node.right, prefix +  "R-");
}
复制代码
public class Main {
    public static void main(String[] args) {
        Integer data[] = new Integer[] {
            7, 4, 9, 2, 1, 3, 5, 9, 8, 11, 10, 12
        };
        BinarySearchTree<Integer> tree = new BinarySearchTree<>();
        for (int i = 0; i < data.length; i++) {
            tree.add(data[I]);
        }
        System.out.println(tree);
    }
}
复制代码
L-L-L-1
L-L-2
L-L-R-3
L-4
L-R-5
7
R-L-8
R-9
R-R-L-10
R-R-11
R-R-R-12
复制代码

七、计算二叉树的高度

1、递归

public int height() {
    return height(root);
}

public int height(Node<E> node) {
    if (node == null) return 0;
    // 左子节点或右子节点中高度最高的值 + 1
    return 1 + Math.max(height(node.left), height(node.right));
}
复制代码

2、迭代

/**
 * 使用层序遍历的方式, 计算树的高度
 */
public int height() {
    Queue<Node<E>> queue = new LinkedList<>();
    queue.offer(root);
    // 当前队列中节点个数
    int elementCount = 1;
    // 树的高度
    int height = 0;
    while (!queue.isEmpty()) {
        // 取出节点
        Node<E> node = queue.poll();
        // 队列中节点个数-1
        elementCount--;
        if (node.left != null) {
            queue.offer(node.left);
        }
        if (node.right != null) {
            queue.offer(node.right);
        }
        // 当 elementCount = 0, 说明当前层的节点遍历编程
        if (elementCount == 0) {
            // 记录下一层节点个数
            elementCount = queue.size();
            // 高度+1
            height++;
        }
    }
    return height;
}
复制代码

八、判断一棵树是否为完全二叉树

image

<figcaption></figcaption>

public boolean isComplete() {
    if (root == null) return false;

    Queue<Node<E>> queue = new LinkedList<>();
    queue.offer(root);

    // 记录当前节点开始是否为 最后一个非叶子节点 或 叶子节点
    boolean leaf = false;
    while (!queue.isEmpty()) {
        Node<E> node = queue.poll();

        // 当leaf == true时, 后面的所有节点都必须是叶子节点, 否则返回false
        if (leaf && (node.left != null || node.right != null)) return false;

        if (node.left != null) {
            // node.left != null && node.right == null
            // node.left != null && node.right != null
            queue.offer(node.left);
        }else if (node.right != null) {
            // node.left == null && node.right != null
            return false;
        }

        if (node.right != null) {
            // node.left != null && node.right != null
            queue.offer(node.right);
        }else {
            // node.left == null && node.right == null
            // node.left != null && node.right == null
            leaf = true;
        }
    }
    return true;
}
复制代码

九、翻转二叉树

输入:

     4
   /   \
  2     7
 / \   / \
1   3 6   9

输出:

     4
   /   \
  7     2
 / \   / \
9   6 3   1
复制代码
public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) {
        val = x; 
    }
}
复制代码

1、前序遍历实现翻转二叉树

public TreeNode invertTree(TreeNode root) {
    if (root == null) return root;

    TreeNode temp = root.left;
    root.left = root.right;
    root.right = temp;

    invertTree(root.left);
    invertTree(root.right);
    return root;
}
复制代码

2、中序遍历实现翻转二叉树

public TreeNode invertTree(TreeNode root) {
    if (root == null) return root;

    invertTree(root.left);
    TreeNode temp = root.left;
    root.left = root.right;
    root.right = temp;

    invertTree(root.left);
    return root;
}
复制代码

3、后序遍历实现翻转二叉树

public TreeNode invertTree(TreeNode root) {
    if (root == null) return root;

    invertTree(root.left);
    invertTree(root.right);

    TreeNode temp = root.left;
    root.left = root.right;
    root.right = temp;

    return root;
}
复制代码

4、层序遍历实现翻转二叉树

public TreeNode invertTree(TreeNode root) {
    if (root == null) return root;

    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);

    while (!queue.isEmpty()) {
        TreeNode node = queue.poll();

        TreeNode temp = node.left;
        node.left = node.right;
        node.right = temp;

        if (node.left != null) {
            queue.offer(node.left);
        }
        if (node.right != null) {
            queue.offer(node.right);
        }
    }
    return root;
}
复制代码

十、前驱节点和后继节点

1、前驱节点

image

<figcaption></figcaption>

private Node<E> predecessor(Node<E> node) {
    if (node == null) return node;

    if (node.left != null) {
        Node<E> p = node.left;
        // predecessor = node.left.right.right.right...
        while (p.right != null) {
            p = p.right;
        }
        // p.right == null
        return p;
    }

    // node.left == null
    while (node.parent != null && node == node.parent.left) {
        node = node.parent;
    }

    // node.parent == null, 说明没有前驱节点
    // node == node.parent.right, 说明node的前驱节点是node.parent
    return node.parent;
}
复制代码

2、后继节点

image

<figcaption></figcaption>

private Node<E> successor(Node<E> node) {
    if (node == null) return node;

    if (node.right != null) {
        Node<E> p = node.right;
        // predecessor = node.right.left.left.left...
        while (p.left != null) {
            p = p.left;
        }
        // p.left == null
        return p;
    }

    // node.right == null
    while (node.parent != null && node == node.parent.right) {
        node = node.parent;
    }

    // node.parent == null, 说明没有后继节点
    // node == node.parent.left, 说明node的后继节点是node.parent
    return node.parent;
}
复制代码

十一、删除节点

1、删除节点-叶子节点

image

<figcaption></figcaption>

2、删除节点-度为1的节点

image

<figcaption></figcaption>

3、删除节点-度为2的节点

image

<figcaption></figcaption>

4、删除节点-代码实现

private Node<E> node(E element) {
    Node<E> node = root;
    while (node != null) {
        // 返回值等于0, e1 == e2, 返回正数, e1 > e2, 返回负数, e1 < e2
        int cmp = compare(element, node.element);
        // element == node.element, 返回node
        if (cmp == 0) return node;
        if (cmp > 0) {
            // element > node.element, node = node.right
            node = node.right;
        }else {
            // element > node.element, node = node.left
            node = node.left;
        }
    }
    return null;
}
复制代码
public void remove(E element) {
    // 找到element对应的节点
    Node<E> node = node(element);

    // 数量-1
    size--;

    // 判断node的度, 如果为2, 将node的element赋值为后继节点的element, 并删除后继节点
    // 注意: node的后继节点的度必然是1或者0, 不会是2
    if (node.left != null && node.right != null) {
        // 找到后继节点
        Node<E> s = successor(node);
        // 将node的element赋值为后继节点的element
        node.element = s.element;
        // node指向后继节点, 这样只需要删除node即可
        node = s;
    }
    // 走到这里, node的度只能是1或者是0
    // 找到node的子节点, 左子节点或者右子节点, 或者null
    // 这个子节点会代替node的位置
    Node<E> replacement = node.left != null ? node.left : node.right;
    if (replacement != null) {  // node是度为1的节点
        // 更改replacement的父节点
        replacement.parent = node.parent;

        if (node.parent == null) {  // node是度为1的节点, 并且是根节点
            root = replacement;
        }else if (node == node.parent.left) {   // node是node.parent的左子节点
            node.parent.left = replacement;
        }else { // node == node.parent.right, node是node.parent的右子节点
            node.parent.right = replacement;
        }
    }else if (node.parent == null) {    // node是度为0的节点, 并且是根节点
        root = null;
    }else { // node是度为0的节点, 并且是叶子节点
        if (node == node.parent.left) {
            // node是node.parent的左子节点
            node.parent.left = null;
        }else {
            // node是node.parent的右子节点
            node.parent.right = null;
        }
    }
}
复制代码

十二、清空二叉树

public void clear() {
    root = null;    // 清空root
}
复制代码

十三、查找元素是否存在

public boolean contains(E element) {
    return node(element) != null;
}
复制代码
上一篇下一篇

猜你喜欢

热点阅读