二叉搜索树(BST)

image.png

BST 的基本功能

  1. public class BST<E extends Comparable<E>> {
  2. private Node root;
  3. private int size;
  4. public BST(){
  5. root=null;
  6. size=0;
  7. }
  8. public int size(){
  9. return size;
  10. }
  11. public boolean isEmpty(){
  12. return size==0;
  13. }
  14. private class Node{
  15. public E e;
  16. public Node left,right;
  17. public Node(E e){
  18. this.e=e;
  19. this.left=null;
  20. this.right=null;
  21. }
  22. }
  23. }

添加元素

思路

  • 对于如下的 BST,向其中添加 28:

image.png

  • 28 与根结点 41 比较,应该插入左子树

image.png

  • 28 与左子树的根结点 22 比较,应该插入该结点的右子树

image.png

  • 28 与 33 比较,应该插入该结点的左子树,该结点的左子树为空,则将值为 28 的结点添加到该结点的左子树中

image.png

  • 最终将 28 添加到该 BST 中

image.png

代码

  1. //向BST中添加新元素e
  2. public void add(E e){
  3. if(root==null){
  4. root=new Node(e);
  5. size++;
  6. return;
  7. }else{
  8. add(root,e);
  9. }
  10. }
  11. //向以node为根节点的BST树种插入元素e
  12. private void add(Node node,E e){
  13. if(e.equals(node.e)){
  14. //插入元素与根节点相等,直接返回
  15. return;
  16. }else if(e.compareTo(node.e)<0 && node.left==null){
  17. node.left=new Node(e);
  18. size++;
  19. return;
  20. }else if(e.compareTo(node.e)>0 && node.right==null){
  21. node.right=new Node(e);
  22. size++;
  23. return;
  24. }
  25. if(e.compareTo(node.e)<0){
  26. add(node.left,e);
  27. }else{ //e.compareTo(node.e)>0
  28. add(node.right,e);
  29. }
  30. }

改进添加元素

前文中的添加操作,对 root==null 的情况进行了特殊处理,并且 add(Node,E) 中的递归条件也太冗长了。
这里我们写一个 add() 方法,返回插入新节点后该 BST 的根节点

  1. //向BST中添加新元素e
  2. public void add(E e){
  3. root=add(root,e);
  4. }
  5. //向以node为根节点的BST树种插入元素e
  6. //返回插入新元素后该BST的根
  7. private Node add(Node node,E e){
  8. if(node==null){
  9. size++;
  10. return new Node(e);
  11. }
  12. //注意:对于e.equals(node.e)不做处理
  13. if(e.compareTo(node.e)<0){
  14. node.left=add(node.left,e);
  15. }else if(e.compareTo(node.e)>0){
  16. node.right=add(node.right,e);
  17. }
  18. return node;
  19. }

查询元素

  1. //查看BST中是否包含元素e
  2. public boolean contains(E e){
  3. return contains(root,e);
  4. }
  5. //查看以node为根节点的BST中是否包含元素e
  6. private boolean contains(Node node,E e){
  7. if(node==null){
  8. return false;
  9. }
  10. if(e.compareTo(node.e)==0){
  11. return true;
  12. }else if(e.compareTo(node.e)<0){
  13. return contains(node.left,e);
  14. }else{
  15. return contains(node.right,e);
  16. }
  17. }

遍历元素(递归形式)

  • 前序遍历
  1. //BST的前序遍历
  2. public void preOrder(){
  3. preOrder(root);
  4. }
  5. private void preOrder(Node node){
  6. if(node==null){
  7. return;
  8. }
  9. System.out.println(node.e);
  10. preOrder(node.left);
  11. preOrder(node.right);
  12. }
  1. /**
  2. * 利用前序遍历打印 BST
  3. */
  4. @Override
  5. public String toString() {
  6. StringBuilder res=new StringBuilder();
  7. generateBST(root,0,res);
  8. return res.toString();
  9. }
  10. //生成以node为根节点,深度为depth的描述二叉树的字符串(利用前序遍历)
  11. private void generateBST(Node node,int depth,StringBuilder res){
  12. if(node==null){
  13. res.append(generateDepth(depth)+"null\n");
  14. return;
  15. }
  16. res.append(generateDepth(depth)+node.e+"\n");
  17. generateBST(node.left,depth+1,res);
  18. generateBST(node.right,depth+1,res);
  19. }
  20. private String generateDepth(int depth){
  21. StringBuilder res=new StringBuilder();
  22. for(int i=0;i<depth;i++){
  23. res.append("--");
  24. }
  25. return res.toString();
  26. }
  • 中序遍历
  1. //BST的中序遍历
  2. public void inOrder(){
  3. inOrder(root);
  4. }
  5. private void inOrder(Node node){
  6. if(node==null){
  7. return;
  8. }
  9. inOrder(node.left);
  10. System.out.println(node.e);
  11. inOrder(node.right);
  12. }
  • 后序遍历
  1. //BST的后序遍历
  2. public void postOrder(){
  3. postOrder(root);
  4. }
  5. private void postOrder(Node node){
  6. if(node==null){
  7. return;
  8. }
  9. postOrder(node.left);
  10. postOrder(node.right);
  11. System.out.println(node.e);
  12. }

遍历元素(非递归形式)

  1. //枚举命令,GO表示访问元素,COUT表示打印元素
  2. private enum Command{GO,COUT};
  3. private class StackNode{
  4. Command command;
  5. Node node;
  6. StackNode(Command command,Node node){
  7. this.command=command;
  8. this.node=node;
  9. }
  10. }
  • 前序遍历
  1. //BST的前序遍历(非递归方式)
  2. public void preOrderNR(){
  3. preOrderNR(root);
  4. }
  5. private void preOrderNR(Node node){
  6. if(node==null){
  7. return;
  8. }
  9. Stack<StackNode> stack=new Stack<>();
  10. stack.push(new StackNode(Command.GO,node));
  11. while(!stack.isEmpty()){
  12. StackNode stackNode=stack.pop();
  13. Node n=stackNode.node;
  14. Command command=stackNode.command;
  15. if(command==Command.COUT){
  16. System.out.println(stackNode.node.e);
  17. }else{
  18. if(n.right!=null){
  19. stack.push(new StackNode(Command.GO,n.right));
  20. }
  21. if(n.left!=null){
  22. stack.push(new StackNode(Command.GO,n.left));
  23. }
  24. stack.push(new StackNode(Command.COUT,n));
  25. }
  26. }
  27. }
  • 中序遍历
  1. //BST的中序遍历(非递归方式)
  2. public void inOrderNR(){
  3. inOrderNR(root);
  4. }
  5. private void inOrderNR(Node node){
  6. if(node==null){
  7. return;
  8. }
  9. Stack<StackNode> stack=new Stack<>();
  10. stack.push(new StackNode(Command.GO,node));
  11. while(!stack.isEmpty()){
  12. StackNode stackNode=stack.pop();
  13. Node n=stackNode.node;
  14. Command command=stackNode.command;
  15. if(command==Command.COUT){
  16. System.out.println(stackNode.node.e);
  17. }else{
  18. if(n.right!=null){
  19. stack.push(new StackNode(Command.GO,n.right));
  20. }
  21. stack.push(new StackNode(Command.COUT,n));
  22. if(n.left!=null){
  23. stack.push(new StackNode(Command.GO,n.left));
  24. }
  25. }
  26. }
  27. }
  • 后序遍历
  1. //BST的后序遍历(非递归方式)
  2. public void postOrderNR(){
  3. postOrderNR(root);
  4. }
  5. private void postOrderNR(Node node){
  6. if(node==null){
  7. return;
  8. }
  9. Stack<StackNode> stack=new Stack<>();
  10. stack.push(new StackNode(Command.GO,node));
  11. while(!stack.isEmpty()){
  12. StackNode stackNode=stack.pop();
  13. Node n=stackNode.node;
  14. Command command=stackNode.command;
  15. if(command==Command.COUT){
  16. System.out.println(stackNode.node.e);
  17. }else{
  18. stack.push(new StackNode(Command.COUT,n));
  19. if(n.right!=null){
  20. stack.push(new StackNode(Command.GO,n.right));
  21. }
  22. if(n.left!=null){
  23. stack.push(new StackNode(Command.GO,n.left));
  24. }
  25. }
  26. }
  27. }

层序遍历

  1. //BST的层序遍历
  2. public void levelOrder(){
  3. Queue<Node> q=new LinkedList<>();
  4. q.add(root);
  5. while(!q.isEmpty()){
  6. Node node=q.remove();
  7. System.out.println(node.e);
  8. if(node.left!=null){
  9. q.add(node.left);
  10. }
  11. if(node.right!=null){
  12. q.add(node.right);
  13. }
  14. }
  15. }

删除元素

删除最小值和最大值

  • 寻找 BST 中的最小元素和最大元素
  1. //寻找BST中的最小元素
  2. public E min(){
  3. if(size==0){
  4. throw new IllegalArgumentException("BST is emmpty");
  5. }
  6. return min(root).e;
  7. }
  8. private Node min(Node node){
  9. if(node.left==null){
  10. return node;
  11. }
  12. return min(node.left);
  13. }
  14. //寻找BST中的最大元素
  15. public E max(){
  16. if(size==0){
  17. throw new IllegalArgumentException("BST is emmpty");
  18. }
  19. return max(root).e;
  20. }
  21. private Node max(Node node){
  22. if(node.right==null){
  23. return node;
  24. }
  25. return max(node.right);
  26. }
  • 删除BST中的最大值和最小值
    情况一:最小值是叶子节点。直接删除该节点
    image.png

情况二:最小值有右子树。删除该节点,再将该节点的右子树连接到原来的 BST 中

image.png

  • 代码:
  1. //删除BST中的最小值
  2. public E delMin(){
  3. E res=min();
  4. delMin(root);
  5. return res;
  6. }
  7. //删除以node为根结点的BST中的最小值元素
  8. //这里考虑最小值只有右子树的情况,因为叶子节点是其特例
  9. private Node delMin(Node node){
  10. if(node.left==null){
  11. Node nodeRight=node.right;
  12. node.right=null;
  13. size--;
  14. return nodeRight;
  15. }
  16. node.left=delMin(node.left);
  17. return node;
  18. }
  19. //删除BST中的最大值
  20. public E delMax(){
  21. E res=max();
  22. delMax(root);
  23. return res;
  24. }
  25. //删除以node为根结点的BST中的最大值元素
  26. private Node delMax(Node node){
  27. if(node.right==null){
  28. Node nodeLeft=node.left;
  29. node.left=null;
  30. size--;
  31. return nodeLeft;
  32. }
  33. node.right=delMax(node.right);
  34. return node;
  35. }

删除任意元素

  • 删除只有左孩子的节点。直接删除即可

image.png

  • 删除只有右孩子的节点。直接删除即可

image.png

  • 删除左右孩子都有的节点

image.png

Habbard-Deletion:

d 是待删除的节点,s 节点是 d 的后继,也就是 d 的右子树的最小值所在的节点。

注意:这里选择左子树的最大值所在的节点效果也是相同的。

image.png

使用 s 代替 d

image.png

最后删除 d 节点

  • 代码
  1. //删除BST中任意元素
  2. public void del(E e){
  3. root=del(root,e);
  4. }
  5. private Node del(Node node,E e){
  6. if(node==null){
  7. return null;
  8. }
  9. if(e.compareTo(node.e)<0){
  10. node.left=del(node.left,e);
  11. return node;
  12. }else if(e.compareTo(node.e)>0){
  13. node.right=del(node.right,e);
  14. return node;
  15. }else{
  16. //节点node就是要删除的节点
  17. //该节点只右有子树
  18. if(node.left==null){
  19. Node rightNode=node.right;
  20. node.right=null;
  21. size--;
  22. return rightNode;
  23. }
  24. //该节点只有左子树
  25. if(node.right==null){
  26. Node leftNode=node.left;
  27. node.left=null;
  28. size--;
  29. return leftNode;
  30. }
  31. //删除既有左子树又有右子树的节点
  32. Node s=min(node.right);
  33. s.right=delMin(node.right);
  34. s.left=node.left;
  35. //删除node
  36. node.left=node.right=null;
  37. return s;
  38. }
  39. }