二叉搜索树(Binary Search Tree)
二叉搜索树
需求分析
假设现在有这么一个需求:在 n 个动态的整数中搜索某个整数? (查看其是否存在)
- 假设使用动态数组存放元素,从第 0 个位置开始遍历搜索,平均时间复杂度: O(n)
- 如果维护一个有序的动态数组,使用二分搜索,最坏时间复杂度: O(logn)
- 但是添加、删除的平均时间复杂度是 O(n)
- 针对这个需求,有没有更好的方案?
- 使用【二又搜索树】,添加、删除、搜索的最坏时间复杂度均可优化至: O(logn)
概念
-
二叉搜索树(Binary Search Tree, BST)是二叉树的一种,是应用非常广泛的一种二叉树
- 又被称为:二叉查找树、二叉排序树
- 任意一个节点的值都大于其左子树所有节点的值
- 任意一个节点的值都小于其右子树所有节点的值
- 它的左右子树也是一棵二叉搜索树
-
二叉搜索树可以大大提高搜索数据的效率
-
二又搜索树存储的元素必须具备可比较性
- 比如
int
、double
等 - 如果是自定义类型(引用类型),需要指定比较方式(比如使用
Comparable
)
- 比如
接口设计
共 6 个基础接口
需要注意的是:
- 不难发现,目前我们使用的二叉树,它的元素没有索引的概念。
- 因为添加的元素和顺序无关,自然也就不需要索引了
代码实现
public class BinarySearchTree<E> implements BinaryTreeInfo { private int size; private Node<E> root; private Comparator<E> comparator; public BinarySearchTree() { this(null); } public BinarySearchTree(Comparator<E> comparator) { this.comparator = comparator; } public int size() { return size; } public boolean isEmpty() { return size == 0; } public void clear() { root = null; size = 0; } public void add(E element) { elementNotNullCheck(element); // 添加第一个节点 if (root == null) { root = new Node<>(element, null); size++; return; } // 添加的不是第一个节点 // 找到父节点 Node<E> parent = root; Node<E> node = root; int cmp = 0; do { cmp = compare(element, node.element); parent = node; if (cmp > 0) { node = node.right; } else if (cmp < 0) { node = node.left; } else { // 相等 node.element = element; return; } } while (node != null); // 看看插入到父节点的哪个位置 Node<E> newNode = new Node<>(element, parent); if (cmp > 0) { parent.right = newNode; } else { parent.left = newNode; } size++; } public void remove(E element) { remove(node(element)); } public boolean contains(E element) { return node(element) != null; } private void remove(Node<E> node) { if (node == null) return; size--; if (node.hasTwoChildren()) { // 度为2的节点 // 找到后继节点 Node<E> s = successor(node); // 用后继节点的值覆盖度为2的节点的值 node.element = s.element; // 删除后继节点 node = s; } // 删除node节点(node的度必然是1或者0) Node<E> 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; } } } private Node<E> node(E element) { Node<E> node = root; while (node != null) { int cmp = compare(element, node.element); if (cmp == 0) return node; if (cmp > 0) { node = node.right; } else { // cmp < 0 node = node.left; } } return null; } public void preorder(Visitor<E> visitor) { if (visitor == null) return; preorder(root, visitor); } private void preorder(Node<E> node, Visitor<E> visitor) { if (node == null || visitor.stop) return; visitor.stop = visitor.visit(node.element); preorder(node.left, visitor); preorder(node.right, visitor); } public void inorder(Visitor<E> visitor) { if (visitor == null) return; inorder(root, visitor); } private void inorder(Node<E> node, Visitor<E> visitor) { if (node == null || visitor.stop) return; inorder(node.left, visitor); if (visitor.stop) return; visitor.stop = visitor.visit(node.element); inorder(node.right, visitor); } public void postorder(Visitor<E> visitor) { if (visitor == null) return; postorder(root, visitor); } private void postorder(Node<E> node, Visitor<E> visitor) { if (node == null || visitor.stop) return; postorder(node.left, visitor); postorder(node.right, visitor); if (visitor.stop) return; visitor.stop = visitor.visit(node.element); } public void levelOrder(Visitor<E> visitor) { if (root == null || visitor == null) return; Queue<Node<E>> queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { Node<E> node = queue.poll(); if (visitor.visit(node.element)) return; if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); } } } 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(); if (leaf && !node.isLeaf()) return false; if (node.left != null) { queue.offer(node.left); } else if (node.right != null) { // node.left == null && node.right != null return false; } if (node.right != null) { queue.offer(node.right); } else { // node.right == null leaf = true; } } return true; } public int height() { if (root == null) return 0; // 树的高度 int height = 0; // 存储着每一层的元素数量 int levelSize = 1; Queue<Node<E>> queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { Node<E> node = queue.poll(); levelSize--; if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); } if (levelSize == 0) { // 意味着即将要访问下一层 levelSize = queue.size(); height++; } } return height; } public int height2() { return height(root); } private int height(Node<E> node) { if (node == null) return 0; return 1 + Math.max(height(node.left), height(node.right)); } @Override public String toString() { StringBuilder sb = new StringBuilder(); toString(root, sb, ""); return sb.toString(); } private void toString(Node<E> node, StringBuilder sb, String prefix) { if (node == null) return; toString(node.left, sb, prefix + "L---"); sb.append(prefix).append(node.element).append("\n"); toString(node.right, sb, prefix + "R---"); } /** * @return 返回值等于0,代表e1和e2相等;返回值大于0,代表e1大于e2;返回值小于于0,代表e1小于e2 */ private int compare(E e1, E e2) { if (comparator != null) { return comparator.compare(e1, e2); } return ((Comparable<E>)e1).compareTo(e2); } private void elementNotNullCheck(E element) { if (element == null) { throw new IllegalArgumentException("element must not be null"); } } @SuppressWarnings("unused") private Node<E> predecessor(Node<E> node) { if (node == null) return null; // 前驱节点在左子树当中(left.right.right.right....) Node<E> p = node.left; if (p != null) { while (p.right != null) { p = p.right; } return p; } // 从父节点、祖父节点中寻找前驱节点 while (node.parent != null && node == node.parent.left) { node = node.parent; } // node.parent == null // node == node.parent.right return node.parent; } private Node<E> successor(Node<E> node) { if (node == null) return null; // 前驱节点在左子树当中(right.left.left.left....) Node<E> p = node.right; if (p != null) { while (p.left != null) { p = p.left; } return p; } // 从父节点、祖父节点中寻找前驱节点 while (node.parent != null && node == node.parent.right) { node = node.parent; } return node.parent; } public static abstract class Visitor<E> { boolean stop; /** * @return 如果返回true,就代表停止遍历 */ public abstract boolean visit(E element); } private static class Node<E> { E element; Node<E> left; Node<E> right; Node<E> parent; public Node(E element, Node<E> parent) { this.element = element; this.parent = parent; } public boolean isLeaf() { return left == null && right == null; } public boolean hasTwoChildren() { return left != null && right != null; } } @Override public Object root() { return root; } @Override public Object left(Object node) { return ((Node<E>)node).left; } @Override public Object right(Object node) { return ((Node<E>)node).right; } @Override public Object string(Object node) { Node<E> myNode = (Node<E>)node; String parentString = "null"; if (myNode.parent != null) { parentString = myNode.parent.element.toString(); } return myNode.element + "_p(" + parentString + ")"; } }
Comparator 和 Comparable 的关系
分几种情况,可在对象内定义比较关系,也可在容器内定义,也可用匿名内部类的方法实现。
也可结合使用,
- 当没有传入比较器的时候,就使用
Comparable
接口里面的compareTo
方法 - new 对象的时候传入了比较器(
Comparator
)的时候,就使用比较器的比较规则(compare
方法)
/** * @return 返回值等于0,代表e1和e2相等;返回值大于0,代表e1大于e2;返回值小于于0,代表e1小于e2 */ private int compare(E e1, E e2) { if (comparator != null) { // 比较器不为空 return comparator.compare(e1, e2); } return ((Comparable<E>)e1).compareTo(e2); }
具体解释:
-
Comparable
接口:Comparable
是 Java 核心库中的接口,其中包含了一个名为compareTo
的方法。- 当一个类实现了
Comparable
接口,它可以比较自身与其他对象的大小关系。 - 通过实现
compareTo
方法,你可以定义对象之间的自然排序规则。自然排序是类的默认排序方式,例如对整数、字符串等对象的排序。 compareTo
方法返回负整数、零或正整数,表示当前对象小于、等于或大于其他对象。这种方法返回值约定了比较的结果。
例如,
String
类实现了Comparable
接口,所以你可以直接使用Collections.sort()
来对字符串列表进行排序。 -
Comparator
接口:Comparator
也是 Java 核心库中的接口,其中包含了一个名为compare
的方法。Comparator
接口的实现类是比较器,你可以创建多个不同的比较器用于对同一类对象进行不同的排序。- 比较器是一种更通用的方式,因为它允许你在不修改类本身的情况下定义多种不同的排序方式。
compare
方法需要接收两个对象作为参数,并返回一个负整数、零或正整数,表示第一个对象小于、等于或大于第二个对象。
例如,你可以创建一个自定义的
Comparator
,用于按对象的某个属性进行排序,而不依赖于对象的自然排序规则。
关系:
Comparable
通常用于定义对象的自然排序规则,即该对象本身如何与其他对象比较。类似于对象自己的默认排序方式。Comparator
用于在不修改对象类本身的情况下,定义多个不同的排序方式,以满足不同的排序需求。比较器可以用于对同一类对象进行不同的排序。
一些标准库类,如 String
和包装类型(Integer
、Double
等),实现了 Comparable
接口,以支持自然排序。同时,Java 提供了许多内置的比较器(如 Comparator.naturalOrder()
和 Comparator.reverseOrder()
)以及用于创建自定义比较器的工具方法,使你能够轻松地实现不同的排序逻辑。
值相等的处理
当新增或插入一个元素时,遇到元素比较值相等的时候,建议覆盖而不是直接返回。(也可定义其他规则)
原因分析:
比如传入的是一个 Person
对象,对象包含【age、name】
两个属性值,比较规则比较的是【age】
,此时二叉搜索树内已有 ["10","张三"]
这个元素,要新增元素 ["10",";李四"]
。比较的时候,两个元素是相等的,但实际上不是同一个对象,所以遇到元素比较值相等的时候,建议覆盖而不是直接返回。
二叉树的遍历
遍历是数据结构中的常见操作,把所有元素都访问一遍。
1、线性数据结构的遍历比较简单
- 正序遍历
- 逆序遍历
2、根据节点访问顺序的不同,二叉树的常见遍历方式有 4 种
- 前序遍历 (Preorder Traversal)
- 中序遍历 (Inorder Traversal)
- 后序遍历 (Postorder Traversal)
- 层序遍历(Level Order Traversal)
前序遍历
访问顺序为:
根节点、前序遍历左子树,前序遍历右子树。(根左右,注意根节点的位置)
❗️ 是遍历子树,不是单纯一个节点
代码实现:
/** * 前序遍历 */ public void preorderTraversal() { preorderTraversal(root); } private void preorderTraversal(Node<E> node) { if (node == null) return; System.out.println(node.element); preorderTraversal(node.left); preorderTraversal(node.right); }
如下图二叉搜索树,遍历结果就是:[7, 4, 2, 1, 3, 5, 9, 8, 11, 10, 12]
中序遍历
访问顺序:
中序遍历左子树、根节点、中序遍历右子树。(左根右)
代码实现:
/** * 中序遍历 */ public void inorderTraversal() { inorderTraversal(root); } private void inorderTraversal(Node<E> node) { if (node == null) return; inorderTraversal(node.left); System.out.println(node.element); inorderTraversal(node.right); }
二叉搜索树的中序遍历结果是升序或者降序的。如下图二叉搜索树的中序遍历结果为:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
后序遍历
访问顺序:
后序遍历左子树、后序遍历右子树、根节点。(左右根)
代码实现:
/** * 后序遍历 */ public void postorderTraversal() { postorderTraversal(root); } private void postorderTraversal(Node<E> node) { if (node == null) return; postorderTraversal(node.left); postorderTraversal(node.right); System.out.println(node.element); }
例如,下面的遍历结果为:[1, 3, 2, 5, 4, 8, 10, 12, 11, 9, 7]
层序遍历(重要)
访问顺序:
从上到下、从左到右依次访问每一个节点。
实现思路:使用队列
- 将根节点入队
- 循环执行以下操作,直到队列为空
- 将队头节点 A 出队,进行访问
- 将 A 的左子节点入队
- 将 A 的右子节点入队
代码实现:
/** * 层序遍历 */ public void levelOrderTraversal() { if (root == null) return; Queue<Node<E>> queue = new LinkedList<>(); queue.offer(root); 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); } } }
例如下图的层序遍历结果为:[7, 4, 9, 2, 5, 8, 11, 1, 3, 10, 12]
设计遍历接口
像以上的遍历代码写法,是写死了输出格式,不太友好(比如我需要将每个元素值加上 3 之后输出)。
可以通过设计一个接口然后作为参数传入方法体内,来指定遍历的输出形式并检测是否停止遍历:
1、设计抽象类
public static abstract class Visitor<E> { // 是否停止,默认为 false boolean stop; /** * @return 如果返回true,就代表停止遍历 */ public abstract boolean visit(E element); }
2、修改遍历代码
/** * 前序遍历 */ public void preorder(Visitor<E> visitor) { if (visitor == null) return; preorder(root, visitor); } private void preorder(Node<E> node, Visitor<E> visitor) { if (node == null || visitor.stop) return; // 第一步空与停止判断 visitor.stop = visitor.visit(node.element); preorder(node.left, visitor); preorder(node.right, visitor); } /** * 中序遍历 */ public void inorder(Visitor<E> visitor) { if (visitor == null) return; inorder(root, visitor); } private void inorder(Node<E> node, Visitor<E> visitor) { if (node == null || visitor.stop) return; inorder(node.left, visitor); if (visitor.stop) return; // 这行代码必须写,停止递归里面的遍历 visitor.stop = visitor.visit(node.element); inorder(node.right, visitor); } /** * 后序遍历 */ public void postorder(Visitor<E> visitor) { if (visitor == null) return; postorder(root, visitor); } private void postorder(Node<E> node, Visitor<E> visitor) { if (node == null || visitor.stop) return; postorder(node.left, visitor); postorder(node.right, visitor); if (visitor.stop) return; // 这行代码必须写,停止递归里面的遍历 visitor.stop = visitor.visit(node.element); } /** * 层序遍历 */ public void levelOrder(Visitor<E> visitor) { if (root == null || visitor == null) return; Queue<Node<E>> queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { Node<E> node = queue.poll(); if (visitor.visit(node.element)) return; // 停止遍历判断,根据需求打印元素 if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); } } }
3、编写测试方法
static void test() { BinarySearchTree<Integer> bst = new BinarySearchTree<>(); for (int i = 0; i < 10; i++) { bst.add((int)(Math.random() * 100)); } bst.preorder(new Visitor<Integer>() { public boolean visit(Integer element) { System.out.print("_" + (element + 3) + "_ "); return element == 4 ? true : false; } }); }
遍历的应用
- 前序遍历
- 树状结构展示(注意左右子树的顺序)
- 中序遍历
- 二叉搜索树的中序遍历按升序或者降序处理节点
- 后序遍历
- 适用于一些先子后父的操作
树状打印二叉树
利用前序遍历树状打印二叉树,需重写 toString
方法:
@Override public String toString() { StringBuilder sb = new StringBuilder(); toString(root, sb, ""); return sb.toString(); } private void toString(Node<E> node, StringBuilder sb, String prefix) { if (node == null) return; sb.append(prefix).append(node.element).append("\n"); toString(node.left, sb, prefix + "L---"); toString(node.right, sb, prefix + "R---"); }
结果如图所示:
练习
计算二叉树的高度
1、递归的方法
public int height() { return height(root); } private int height(Node<E> node) { if (node == null) return 0; return 1 + Math.max(height(node.left), height(node.right)); // 递归细节,每一个节点的高度都是这么算 }
2、非递归方法 - 迭代
借鉴层序遍历的写法
/** * 是否完全 * @return */ public int height() { if (root == null) return 0; // 树的高度 int height = 0; // 存储着每一层的元素数量 int levelSize = 1; Queue<Node<E>> queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { Node<E> node = queue.poll(); levelSize--; if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); } if (levelSize == 0) { // 意味着即将要访问下一层 levelSize = queue.size(); height++; } } return height; }
判断一棵树是否为完全二叉树
完全二叉树:叶子节点只在最后两层,且最后一层的叶子结点向左靠齐
思路:
- 如果树为空,返回 false
- 如果树不为空,开始层序遍历二叉树(用队列)
- 如果
node.left != null
,node.right != null
,将 node.left、node.right 按顺序入队,层序遍历写法。(左右不为空) - 如果
node.left == nul1 && node.right != null
,返回 false。(左为空,右不为空) - 如果
node.left != null && node.right == null
或者node.left == null && node.right == null
(左不为空,右为空 或者 左右都为空)- 那么后面遍历的节点应该都为叶子节点,才是完全二叉树
- 否则返回 false
- 如果
代码实现:
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(); if (leaf && !node.isLeaf()) return false; if (node.left != null) { queue.offer(node.left); } else if (node.right != null) { // node.left == null && node.right != null return false; } if (node.right != null) { queue.offer(node.right); } else { // node.left != null && node.right == null // node.left == null && node.right == null leaf = true; } } return true; } private static class Node<E> { E element; Node<E> left; Node<E> right; Node<E> parent; public Node(E element, Node<E> parent) { this.element = element; this.parent = parent; } public boolean isLeaf() { return left == null && right == null; } public boolean hasTwoChildren() { return left != null && right != null; } }
翻转二叉树
翻转的意思:将所有节点的左右子树都交换。
核心是每个节点都要遍历,上面四种遍历方式都可实现。
题解:
前序遍历的方法:
class Solution { public TreeNode invertTree(TreeNode root) { if (root == null) return root; TreeNode tmp = root.left; root.left = root.right; root.right = tmp; invertTree(root.left); invertTree(root.right); return root; } }
根据遍历结果重构二叉树
以下结果可以保证重构出唯一的一棵二叉树(主要是找出左右子树的范围切割点)
- 前序遍历 + 中序遍历
- 后序遍历 + 中序遍历
- 前序遍历 + 后序遍历(有条件)
- 如果它是一棵【真二叉树】,结果是唯一的(前序遍历左子树的第一个节点是后序遍历左子树的最后一个节点)
- 不然结果不唯一
真二叉树:所有节点的度都要么为 0,要么为 2。(0,2)
明确要么左右子树都为空,要么都存在。
前驱节点(predecessor)
前驱节点:一个节点在中序遍历中的前一个节点。
- 如果节点有左子树,那么前驱节点将是其左子树的最右叶子节点。这是因为中序遍历的前一个节点总是当前节点的左子树的最右叶子节点。
- 如果节点没有左子树,那么需要向上遍历树,找到一个祖先节点,使当前节点是其右子树的一部分。这个祖先节点就是前驱节点。如果找不到这样的祖先节点,那么当前节点没有前驱节点。
- 8 的前驱节点是 7
- 4 的前驱节点是 3
- 9 的前驱节点是 8
- 1 的前驱节点是 8
- 3 的前驱节点是 2
代码实现:
private Node<E> predecessor(Node<E> node) { if (node == null) return null; // 1、前驱节点在左子树当中(left.right.right.right....) Node<E> p = node.left; if (p != null) { while (p.right != null) { p = p.right; } return p; } // 2、从父节点、祖父节点中寻找前驱节点 while (node.parent != null && node == node.parent.left) { node = node.parent; } // node.parent == null // node == node.parent.right return node.parent; }
后继节点
后继节点:对于任何节点,其后继节点将是在中序遍历中的下一个节点。
- 在二叉搜索树(BST)中,一个节点的后继节点是大于该节点值的最小节点。
- 如果节点有右子树,那么节点的后继节点是其右子树的最左子节点。这是因为在 BST 中,右子树中的最左子节点是大于该节点值的最小节点。
- 如果节点没有右子树,那么需要向上遍历树,直到找到一个祖先节点,该祖先节点的左子树包含原节点。这个祖先节点就是后继节点。
删除节点
删除度为 0 的节点
直接删除。
- node == node.parent.left(左子节点)
- node.parent.left = null
- node == node.parent.right(右子节点)
- node.parent.right = null
- node.parent == null(只有一个根节点)
- root = null
删除度为 1 的节点
用【子节点】替代【原节点】的位置。
- 如果 node 是左子节点
- child.parent = node.parent
- node.parent.left = child
- 如果 node 是右子节点
- child.parent = node.parent
- node.parent.right = child
- 如果 node 是根节点
- root = child
- child.parent = null
删除度为 2 的节点
主要是两个步骤:
-
先用前驱或者后继节点的值覆盖原节点的值
-
然后删除相应的前驱或者后继节点
如果一个节点的度为 2,那么它的前驱、后继节点的度只可能是 1 和 0
代码实现
主要分三个方法
void remove(E element)
Node<E> node(E element)
void remove(Node<E> node)
/** * 传入元素去删除 * @param element */ public void remove(E element) { remove(node(element)); } /** * 根据元素值找到节点 * @param element * @return */ private Node<E> node(E element) { Node<E> node = root; while (node != null) { int cmp = compare(element, node.element); if (cmp == 0) return node; if (cmp > 0) { node = node.right; } else { // cmp < 0 node = node.left; } } return null; } /** * 删除节点 * @param node */ private void remove(Node<E> node) { if (node == null) return; size--; if (node.hasTwoChildren()) { // 度为2的节点 // 找到后继节点 Node<E> s = successor(node); // 用后继节点的值覆盖度为2的节点的值 node.element = s.element; // 删除后继节点 node = s; } // 删除node节点(node的度必然是1或者0) Node<E> 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; } } } private static class Node<E> { E element; Node<E> left; Node<E> right; Node<E> parent; public Node(E element, Node<E> parent) { this.element = element; this.parent = parent; } public boolean isLeaf() { return left == null && right == null; } public boolean hasTwoChildren() { return left != null && right != null; } }
代码重构
二叉树
1、抽象出公共代码 – 二叉树
- 二叉树不包含添加功能,是因为无法确定比较规则,需子类自定义处理逻辑
public class BinaryTree<E> implements BinaryTreeInfo { protected int size; protected Node<E> root; public int size() { return size; } public boolean isEmpty() { return size == 0; } public void clear() { root = null; size = 0; } public void preorder(Visitor<E> visitor) { if (visitor == null) return; preorder(root, visitor); } private void preorder(Node<E> node, Visitor<E> visitor) { if (node == null || visitor.stop) return; visitor.stop = visitor.visit(node.element); preorder(node.left, visitor); preorder(node.right, visitor); } public void inorder(Visitor<E> visitor) { if (visitor == null) return; inorder(root, visitor); } private void inorder(Node<E> node, Visitor<E> visitor) { if (node == null || visitor.stop) return; inorder(node.left, visitor); if (visitor.stop) return; visitor.stop = visitor.visit(node.element); inorder(node.right, visitor); } public void postorder(Visitor<E> visitor) { if (visitor == null) return; postorder(root, visitor); } private void postorder(Node<E> node, Visitor<E> visitor) { if (node == null || visitor.stop) return; postorder(node.left, visitor); postorder(node.right, visitor); if (visitor.stop) return; visitor.stop = visitor.visit(node.element); } public void levelOrder(Visitor<E> visitor) { if (root == null || visitor == null) return; Queue<Node<E>> queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { Node<E> node = queue.poll(); if (visitor.visit(node.element)) return; if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); } } } 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(); if (leaf && !node.isLeaf()) return false; if (node.left != null) { queue.offer(node.left); } else if (node.right != null) { return false; } if (node.right != null) { queue.offer(node.right); } else { // 后面遍历的节点都必须是叶子节点 leaf = true; } } return true; } public int height() { if (root == null) return 0; // 树的高度 int height = 0; // 存储着每一层的元素数量 int levelSize = 1; Queue<Node<E>> queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { Node<E> node = queue.poll(); levelSize--; if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); } if (levelSize == 0) { // 意味着即将要访问下一层 levelSize = queue.size(); height++; } } return height; } public int height2() { return height(root); } private int height(Node<E> node) { if (node == null) return 0; return 1 + Math.max(height(node.left), height(node.right)); } protected Node<E> predecessor(Node<E> node) { if (node == null) return null; // 前驱节点在左子树当中(left.right.right.right....) Node<E> p = node.left; if (p != null) { while (p.right != null) { p = p.right; } return p; } // 从父节点、祖父节点中寻找前驱节点 while (node.parent != null && node == node.parent.left) { node = node.parent; } // node.parent == null // node == node.parent.right return node.parent; } protected Node<E> successor(Node<E> node) { if (node == null) return null; // 前驱节点在左子树当中(right.left.left.left....) Node<E> p = node.right; if (p != null) { while (p.left != null) { p = p.left; } return p; } // 从父节点、祖父节点中寻找前驱节点 while (node.parent != null && node == node.parent.right) { node = node.parent; } return node.parent; } public static abstract class Visitor<E> { boolean stop; /** * @return 如果返回true,就代表停止遍历 */ abstract boolean visit(E element); } protected static class Node<E> { E element; Node<E> left; Node<E> right; Node<E> parent; public Node(E element, Node<E> parent) { this.element = element; this.parent = parent; } public boolean isLeaf() { return left == null && right == null; } public boolean hasTwoChildren() { return left != null && right != null; } } @Override public Object root() { return root; } @Override public Object left(Object node) { return ((Node<E>)node).left; } @Override public Object right(Object node) { return ((Node<E>)node).right; } @Override public Object string(Object node) { Node<E> myNode = (Node<E>)node; String parentString = "null"; if (myNode.parent != null) { parentString = myNode.parent.element.toString(); } return myNode.element + "_p(" + parentString + ")"; } }
二叉搜索树
2、继承二叉树类
public class BST<E> extends BinaryTree<E> { private Comparator<E> comparator; public BST() { this(null); } public BST(Comparator<E> comparator) { this.comparator = comparator; } public void add(E element) { elementNotNullCheck(element); // 添加第一个节点 if (root == null) { root = new Node<>(element, null); size++; return; } // 添加的不是第一个节点 // 找到父节点 Node<E> parent = root; Node<E> node = root; int cmp = 0; do { cmp = compare(element, node.element); parent = node; if (cmp > 0) { node = node.right; } else if (cmp < 0) { node = node.left; } else { // 相等 node.element = element; return; } } while (node != null); // 看看插入到父节点的哪个位置 Node<E> newNode = new Node<>(element, parent); if (cmp > 0) { parent.right = newNode; } else { parent.left = newNode; } size++; } public void remove(E element) { remove(node(element)); } public boolean contains(E element) { return node(element) != null; } private void remove(Node<E> node) { if (node == null) return; size--; if (node.hasTwoChildren()) { // 度为2的节点 // 找到后继节点 Node<E> s = successor(node); // 用后继节点的值覆盖度为2的节点的值 node.element = s.element; // 删除后继节点 node = s; } // 删除node节点(node的度必然是1或者0) Node<E> 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; } } } private Node<E> node(E element) { Node<E> node = root; while (node != null) { int cmp = compare(element, node.element); if (cmp == 0) return node; if (cmp > 0) { node = node.right; } else { // cmp < 0 node = node.left; } } return null; } /** * @return 返回值等于0,代表e1和e2相等;返回值大于0,代表e1大于e2;返回值小于于0,代表e1小于e2 */ private int compare(E e1, E e2) { if (comparator != null) { return comparator.compare(e1, e2); } return ((Comparable<E>)e1).compareTo(e2); } private void elementNotNullCheck(E element) { if (element == null) { throw new IllegalArgumentException("element must not be null"); } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现