一、二叉搜索树
- 任意一个节点的值都大于其左子树所有节点的值
- 任意一个节点的值都小于其右子树所有节点的值
- 它的左右子树也是一颗二叉搜索树
- 二叉搜索树存储的元素必须具备可比较性
- 比如:Integer、Double等
- 如果是自定义类型,需要指定比较方式
- 节点值不能为null
-
二、二叉搜索树的接口设计
- int size(); //节点的数量
- boolean isEmpty(); //是否为空
- void clear(); //清空节点
- void add(E element); //添加节点
- void remove(E element); //删除节点
- boolean contains(E element); //是否包含节点
三、二叉搜索树的实现
-
构造函数
private int size; //记录节点的数量 private Node<E> root; //记录根节点 //比较器 private Comparator<E> comparator; //空参构造器:使用comparable接口的比较器 public BinarySearchTree() { this(null); } //使用comparator比较器的构造器 public BinarySearchTree(Comparator<E> comparator) { this.comparator = comparator; } /** * 如果自定义类实现comparator接口,则使用comparator接口比较 * 如果自定义类实现comparable接口,则使用comparable接口比较 * @param e1 节点1 * @param e2 节点2 * @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 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; } }
-
添加元素
private void elementCheckNotNull(E element) { if (element == null) { throw new IllegalArgumentException("节点元素不能为空"); } } /** * 添加节点元素 * 1. 首先找到父节点parent,然后创建新节点node,最后比较parent和node的大小 * 2. 如果node小于parent,将parent.left赋值给parent * 3. 如果node大于parent,将parent.right赋值给parent * 4. 循环操作,直到为空,然后将node赋值给parent * 5. 添加元素前,首先需要检查节点的值是否为空 * 6. 需要特殊处理添加元素到首节点 */ public void add(E element) { //判定节点不为空 elementCheckNotNull(element); //添加第一个节点 if (root == null) { root = new Node<>(element, null); size++; return; } //添加不是第一个节点 Node<E> parent = root; Node<E> node = root; //比较结果 int cmp = 0; while (node != null) { //比较节点的大小 cmp = compare(element, node.element); //保存node的父节点 parent = node; //将节点进行左树和右树的移动 if (cmp > 0) { node = node.right; } else if (cmp < 0) { node = node.left; } else { node.element = element; return; } } //看看插入到父节点的哪个位置 Node<E> newNode = new Node<>(element, parent); if (cmp > 0) { parent.right = newNode; } else { parent.left = newNode; } size++; }
-
删除元素
-
删除叶子节点
删除叶子节点
,直接
删除`即可- 如果
node == node.parent.left
node.parent.left = null
- 如果
node == node.parent.right
node.parent.right = null
- 如果
node.parent == null
root = null
- 如果
-
删除度为1的节点
删除度为1的节点,用子节点
替代
原节点的位置,child = node.left
或者child = node.right
- 用
child
替代node
的位置。 - 如果node是左子节点:
child.parent = node.parent
node.parent.left = child
- 如果node是右子节点:
child.parent = node.parent
node.parent.right = child
- 如果node是根节点:
root = child
- 用
-
删除度为2的节点
删除度为2的节点
,先用前驱或后继节点的值覆盖
原节点的值,然后删除
相应的前驱或者后继节点。(如果这个节点的度为2,那么它的前驱,后继节点的度只能是1和0) -
代码实现
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; } } }
-
四、二叉搜索树的遍历
-
前序遍历
/** * 前序遍历:根节点 -> 前序遍历左子树 -> 前序遍历右子树 */ public void PreOrderTraversal(Node<E> node){ //1. 递归退出条件 if (node == null) return; //2. 打印节点的值 System.out.println(node.element); //3. 遍历左子树 PreOrderTraversal(node.left); //4. 遍历右子树 PreOrderTraversal(node.right); }
-
中序遍历
/** * 中序遍历:中序遍历左子树 -> 根节点 -> 中序遍历右子树 * 补充: * 使用中序遍历的二叉树如果为二叉搜索树则遍历结果为: * 1. 比较结果从小到大(中序遍历左子树 -> 根节点 -> 中序遍历右子树) * 2. 比较结果从大到小(中序遍历右子树 -> 根节点 -> 中序遍历左子树) */ public void InOrderTraversal(Node<E> node){ if (node == null) return; InOrderTraversal(node.left); System.out.println(node.element); InOrderTraversal(node.right); }
-
后序遍历
/** * 后序遍历:后序遍历左子树 -> 后序遍历右子树 -> 根节点 */ public void PostOrderTraversal(Node<E> node){ if (node == null) return; PostOrderTraversal(node.left); PostOrderTraversal(node.right); System.out.println(node.element); }
-
层序遍历
/** * 层序遍历:从上到下,从左到右依次访问节点 * 遍历思路: * 1.使用队列存储节点。 * 2.将根节点入队列。 * 3.将队列头节点A出队列,进行访问。 * 4.将节点A的左子节点入队列。 * 5.将节点A的右子节点入队列。 * 6.循环执行2-4步骤。 */ public void levelOrderTraversal(){ if (root == null) return; Queue<Node<E>> queue = new LinkedList<>(); queue.offer(root); while (queue.isEmpty()){ //取出节点 Node<E> note = queue.poll(); //打印节点值 System.out.println(note.element); if (note.left != null) { queue.offer(note.left); } if (note.left != null) { queue.offer(note.right); } } }
五、二叉树的前驱与后继节点
-
前驱节点(predecessor)
-
中序遍历时的前一个节点
-
如果是二叉搜索树,前驱节点是前一个比它小的节点
-
前驱节点分为以下情况:
- 左子树不为空
- 举例:
6,13,8
predecessor = node.left.right.right.right...
- 终结条件:
right = null
- 举例:
- 左子树为空,父节点不为空
- 举例:
7,11,9
predecessor = node.parent.parent.parent...
- 终结条件:
node
在parent
的右子树
中
- 举例:
- 左子树为空且父节点为空
- 没有前驱节点
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; }
- 左子树不为空
-
-
后继节点(successor)
-
中序遍历时的后一个节点
-
如果是二叉搜索树,后继节点是后一个比它大的节点
-
后继节点分为以下情况:
- 右子树不为空
- 举例:
1,4
successor = node.right.left.left.left...
- 终结条件:
left = null
- 举例:
- 右子树为空,父节点不为空
- 举例:
7,6,3,11
successor = node.parent.parent.parent...
- 终结条件:
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; }
- 右子树不为空
-