20.第25章:二叉查找树

1.基本概念

  • 二叉树:是一种层次结构,要么是空集,要么由root元素和两棵不同的二叉树组成,左子树,右子树。允许左右子树一棵或两棵为空。


    20.第25章:二叉查找树_第1张图片
  • 路径长度:指在该条路径上边的个数
    结点60到45的路径长度为2

  • 结点深度:从该结点到根结点的路径长度
    结点60:深度为0
    结点55:深度为1
    结点45:深度为2

  • 树的高度:从根节点到最远的叶结点路径长度,只有一个结点的树高度为0,空树高度为-1
    a)b)图树的高度为2

  • 二叉查找树(BST):是特殊类型的二叉树,具有如下特点:
    1) 没有重复元素
    2)对于树中每一个结点,它的左子树中结点的值都小于该结点的值,它的右子树中结点的值都大于该结点的值。

2.表示二叉查找树

public class TreeNode  {
    protected E element;
    protected TreeNode left;
    protected TreeNode right;
    public TreeNode(E element){
        this.element=element;
    }
}

3.查找一个元素

    /**
     * 查找一个元素
     * @param element
     * @param root
     */
    protected  boolean search(E element) {
      if(root==null)
          return false;
      TreeNode current=root;
      while (current!=null)
      {
          //如果元素值小于当前结点的元素值,说明在左子树
          if(element.compareTo(current.element)<0){
              current=current.left;
          }
          //如果元素大于当前结点的元素值,说明在右子树
          else if(element.compareTo(current.element)>0){
              current=current.right;
          }
          //表示元素值和当前节点元素值相等
          else
              return true;
      }
      //直到current为null,也没找到
        return false;
    }

4.在BST二叉查找树中插入一个元素

主要是找到插入元素的父元素parent的位置

/**
     * 在BST插入一个元素
     *
     */
    public boolean insert(E element){
        if(root==null)
            root=createNewNode(element);
        //定义一个parent结点,一个current结点,用于遍历寻找位置
        TreeNode parent,current;
        //最开始都等于根元素
        parent=current=root;
        while (current!=null){
            //元素值比当前元素小
            if(element.compareTo(current.element)<0){
                parent=current;
                current=current.left;
            }
            else if(element.compareTo(current.element)>0){
                parent=current;
                current=current.right;
            }
            //已经有这个元素,冲突
            else
                return false;
        }

        if(element.compareTo(parent.element)<0)
            parent.left=createNewNode(element);
        else
            parent.right=createNewNode(element);
        size++;
        return true;
    }

    private TreeNode createNewNode(E element) {
        return  new TreeNode(element);

    }

5.树的遍历

访问树中每个结点一次且只有一次的过程。

遍历方法:
1)深度优先遍历遍历:(前序、中序、后序)
  • 递归:
    • 前序遍历:首先访问当前结点,然后递归的访问该结点的左子树,最后递归的访问该结点的右子数。
    • 中序遍历:首先递归的访问该结点的左子树,然后访问当前结点,最后递归的访问该结点的右子数。
    • 后序遍历:首先递归的访问该结点的左子树,然后递归的访问该结点的右子数,最后访问当前结点。
  • 非递归:使用栈stack(后进先出)实现


    20.第25章:二叉查找树_第2张图片
    • 前序遍历
      1)60放进栈。【60】
      2)弹出60,将右孩子100进栈,左孩子55进栈【100,55】
      3)弹出55,将右孩子57进栈,左孩子45进栈【100,57,45】
      4)弹出45,45左右孩子为null。【100,57】
      5)弹出57,57的左右孩子为null。【100】
      6)弹出100,将右孩子107进栈,左孩子67进栈。【107,67】
      7)弹出67,67的左右孩子为null。【107】
      8)弹出107,右孩子为null,左孩子101进栈。【101】
      9)弹出101.
      弹出的元素就是前序遍历结果。
  • 中序遍历
    1)将根节点及其所有左侧节点压入栈【60,55,45】
    2)弹出45,左右结点为null,不用处理【60,55】
    3)弹出55,将右孩子57及其所有左侧结点压入栈,这里只有57.【60,57】
    4)弹出57,左右结点为null,不用处理。【60】
    5)弹出60,将60的右孩子100及其所有左侧结点压入栈。【100,67】
    6)弹出67,左右结点为null,不用处理。【100】
    7)弹出100,右孩子107及其左右左侧结点压入栈。【107,101】
    8)弹出101【107】
    9)弹出107
  • 后序遍历
    思想:将根节点及其左侧结点全部入栈,
    访问一个结点前,判断其右结点是否被访问过,需要一个preNode存储前一个访问的结点。
    若没访问过,按如上规则去处理右子树所有结点。
2)广度优先遍历

逐层访问树中结点。
首先访问根结点,然后从左往右访问根节点所有子结点,再从左往右访问根节点的所有孙子结点。
思想:使用队列Queue(先进先出)来实现。


20.第25章:二叉查找树_第3张图片

1)将根节点60放入队列。(60)
2)弹出60,将60的左孩子和右孩子放入队列。(55,100)
3)弹出55,将55的左右孩子放入队列(100,45,57)
4)弹出100,将100的左孩子右孩子放入队列(45,57,67,107)
5)弹出45,没有左右孩子。(57,67,107)
6)弹出57,将左右孩子放入队列(67,107,59)
7)弹出67,没有左右孩子。(107,59)
8)弹出107,将左右孩子放入队列(59,101)
9)弹出59
10)弹出101

3) 例子
20.第25章:二叉查找树_第4张图片

上述图片遍历情况如下。
前序:60 55 45 57 59 100 67 107 101
中序:45 55 57 59 60 67 100 101 107
后序:45 59 57 55 67 101 107 100 60
广度优先:60 55 100 45 57 67 107 59 101

4)代码实现
 /**
     * 二叉查找树遍历
     */

    //1.前序遍历
    public void preorder(){
        preorder(root);
    }

    protected void preorder(TreeNode root) {
        if(root==null)return;
        System.out.print(root.element+" ");
        preorder(root.left);
        preorder(root.right);
    }

    //前序遍历非递归实现
    public void iterativepreorder(){
        iterativepreorder(root);
    }

    private void iterativepreorder(TreeNode root) {
        Stack stack=new Stack<>();
        if(root!=null){
            stack.push(root);
            while(!stack.isEmpty()){
                TreeNode current=stack.pop();
                System.out.print(current.element+" ");
                if(current.right!=null)
                    stack.add(current.right);
                if(current.left!=null)
                    stack.add(current.left);
            }
        }
    }


    //2.中序遍历
    public void inorder(){
        inorder(root);
    }

    private void inorder(TreeNode root) {
        if(root==null)return;
        inorder(root.left);
        System.out.print(root.element+" ");
        inorder(root.right);
    }

    //中序遍历非递归实现
    public void iterativeinorder(){
        iterativeinorder(root);
    }

    private void iterativeinorder(TreeNode root) {
        Stack stack=new Stack<>();
        if(root!=null){
           TreeNode node=root;
           while(node!=null||stack.size()>0){
               //将所有的左侧结点入栈
               while (node!=null){
                   stack.add(node);
                   node=node.left;
               }
               if(stack.size()>0){
                   TreeNode current=stack.pop();
                   System.out.print(current.element+" ");
                   if(current.right!=null)
                       node=current.right;
               }
           }
        }
    }
    //后序遍历
    public void postorder(){
        postorder(root);
    }

    private void postorder(TreeNode root) {
        if(root==null)return;
        postorder(root.left);
        postorder(root.right);
        System.out.print(root.element+" ");
    }

    //非递归后序遍历
    public void iterativepostorder(){
        iterativepostorder(root);
    }

    private void iterativepostorder(TreeNode root) {
        Stack stack=new Stack<>();
        if(root!=null){
            TreeNode node=root;
            TreeNode preNode=null;
            while (node!=null||stack.size()>0){

                //将左侧结点全部压入栈
                while (node!=null){
                    stack.add(node);
                    node=node.left;
                }
                if(stack.size()>0){
                    //如果该节点的右结点为null,或者已经被访问
                    if(stack.peek().right==null||stack.peek().right==preNode){
                        TreeNode current=stack.pop();
                        System.out.print(current.element+" ");
                        //记录前一个被访问的结点
                        preNode=current;
                    }else {
                        node=stack.peek().right;
                    }

                }
            }
        }
    }

    //广度优先遍历(层次遍历)
    public void layerTraversal(){
        layerTraversal(root);
    }

    private void layerTraversal(TreeNode root) {
        Queue queue=new LinkedList<>();
        if(root!=null) {
            queue.add(root);
            //当队列不是空的
            while (!queue.isEmpty()) {

                //弹出第一个结点
                TreeNode current = queue.poll();
                System.out.print(current.element+" ");
                //如果该结点的左孩子不是null,加入到队列中
                if (current.left != null)
                    queue.offer(current.left);
                //如果该结点的右孩子不是null,加入到队列中
                if (current.right != null)
                    queue.offer(current.right);
            }
        }
    }

6.删除一个元素

有两种情况:

  • 情况1:要删除的元素,没有左子结点
    如图所示,要删除10,只需要将该节点的父结点与该结点的右子结点相连。


    20.第25章:二叉查找树_第5张图片
  • 情况2:要删除的元素有左子结点


    20.第25章:二叉查找树_第6张图片

    20.第25章:二叉查找树_第7张图片
 public boolean delete(E element){
        TreeNode parent=null;
        TreeNode current=root;
        //1.先找到要删除的元素的位置,current表示,父元素的parent
        while (current!=null){
            if(element.compareTo(current.element)<0){
                parent=current;
                current=current.left;
            }else if(element.compareTo(current.element)>0){
                parent=current;
                current=current.right;
            }else
                break;

        }
        //2.没找到这个元素
        if(current==null)
            return false;

        //3.case1:要删除的元素没有左子结点
        if(current.left==null){
            //如果没有父节点,相当于要删除的是根节点,
            if(parent==null)
                root=current.right;
            //判断要删除的元素是在父元素的左子树上
            else if(element.compareTo(parent.element)<0)
                parent.left=current.right;
            else
                parent.right=current.right;
        }
        //4.case2:要删除的结点有左子结点
        else{
            TreeNode parentOfRightMost=current;
            TreeNode rightMost=current.left;
            //通过此方法得到要删除结点左子树最大的元素(也就是最靠右的元素)
            while (rightMost.right!=null)
            {
                parentOfRightMost=rightMost;
                rightMost=rightMost.right;
            }
            //将左子树最大值复制给删除节点值
            current.element=rightMost.element;

            //如果rightMost是父节点的右边结点,此时不可能有更右边的结点了,
            // 删除之后,就让父节点连接rightMost的左结点
            if(parentOfRightMost.right==rightMost)
                parentOfRightMost.right=rightMost.left;
            else
                parentOfRightMost.left=rightMost.left;

        }

        size--;
        return true;
}

7.所有方法总结:

1)TreeNode类

package java_04_java语言程序设计.第25章_二叉查找树;

/*
   一个结点包括元素本身,指向左结点的变量left,指向右结点的变量right
 */
public class TreeNode  {
    protected E element;
    protected TreeNode left;
    protected TreeNode right;
    public TreeNode(E element){
        this.element=element;
    }


}

2)实现增删改查的类BST

package java_04_java语言程序设计.第25章_二叉查找树;

import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;

/**
 * 实现二叉查找树的增删改查
 */
public class BST>{
    private TreeNode root;
    private int size=0;
    public BST(){

    }
    public BST(E[] objects){
        for(int i=0;i current=root;
      while (current!=null)
      {
          //如果元素值小于当前结点的元素值,说明在左子树
          if(element.compareTo(current.element)<0){
              current=current.left;
          }
          //如果元素大于当前结点的元素值,说明在右子树
          else if(element.compareTo(current.element)>0){
              current=current.right;
          }
          //表示元素值和当前节点元素值相等
          else
              return true;
      }
      //直到current为null,也没找到
        return false;
    }

    /**
     * 在BST插入一个元素
     *
     */
    public boolean insert(E element){
        if(root==null)
            root=createNewNode(element);
        //定义一个parent结点,一个current结点,用于遍历寻找位置
        TreeNode parent,current;
        //最开始都等于根元素
        parent=current=root;
        while (current!=null){
            //元素值比当前元素小
            if(element.compareTo(current.element)<0){
                parent=current;
                current=current.left;
            }
            else if(element.compareTo(current.element)>0){
                parent=current;
                current=current.right;
            }
            //已经有这个元素,冲突
            else
                return false;
        }

        if(element.compareTo(parent.element)<0)
            parent.left=createNewNode(element);
        else
            parent.right=createNewNode(element);
        size++;
        return true;
    }

    private TreeNode createNewNode(E element) {
        return  new TreeNode(element);

    }


    /**
     * 二叉查找树遍历
     */

    //1.前序遍历
    public void preorder(){
        preorder(root);
    }

    protected void preorder(TreeNode root) {
        if(root==null)return;
        System.out.print(root.element+" ");
        preorder(root.left);
        preorder(root.right);
    }

    //前序遍历非递归实现
    public void iterativepreorder(){
        iterativepreorder(root);
    }

    private void iterativepreorder(TreeNode root) {
        Stack stack=new Stack<>();
        if(root!=null){
            stack.push(root);
            while(!stack.isEmpty()){
                TreeNode current=stack.pop();
                System.out.print(current.element+" ");
                if(current.right!=null)
                    stack.add(current.right);
                if(current.left!=null)
                    stack.add(current.left);
            }
        }
    }


    //2.中序遍历
    public void inorder(){
        inorder(root);
    }

    private void inorder(TreeNode root) {
        if(root==null)return;
        inorder(root.left);
        System.out.print(root.element+" ");
        inorder(root.right);
    }

    //中序遍历非递归实现
    public void iterativeinorder(){
        iterativeinorder(root);
    }

    private void iterativeinorder(TreeNode root) {
        Stack stack=new Stack<>();
        if(root!=null){
           TreeNode node=root;
           while(node!=null||stack.size()>0){
               //将所有的左侧结点入栈
               while (node!=null){
                   stack.add(node);
                   node=node.left;
               }
               if(stack.size()>0){
                   TreeNode current=stack.pop();
                   System.out.print(current.element+" ");
                   if(current.right!=null)
                       node=current.right;
               }
           }
        }
    }
    //后序遍历
    public void postorder(){
        postorder(root);
    }

    private void postorder(TreeNode root) {
        if(root==null)return;
        postorder(root.left);
        postorder(root.right);
        System.out.print(root.element+" ");
    }

    //非递归后序遍历
    public void iterativepostorder(){
        iterativepostorder(root);
    }

    private void iterativepostorder(TreeNode root) {
        Stack stack=new Stack<>();
        if(root!=null){
            TreeNode node=root;
            TreeNode preNode=null;
            while (node!=null||stack.size()>0){

                //将左侧结点全部压入栈
                while (node!=null){
                    stack.add(node);
                    node=node.left;
                }
                if(stack.size()>0){
                    //如果该节点的右结点为null,或者已经被访问
                    if(stack.peek().right==null||stack.peek().right==preNode){
                        TreeNode current=stack.pop();
                        System.out.print(current.element+" ");
                        //记录前一个被访问的结点
                        preNode=current;
                    }else {
                        node=stack.peek().right;
                    }

                }
            }
        }
    }

    //广度优先遍历(层次遍历)
    public void layerTraversal(){
        layerTraversal(root);
    }

    private void layerTraversal(TreeNode root) {
        Queue queue=new LinkedList<>();
        if(root!=null) {
            queue.add(root);
            //当队列不是空的
            while (!queue.isEmpty()) {

                //弹出第一个结点
                TreeNode current = queue.poll();
                System.out.print(current.element+" ");
                //如果该结点的左孩子不是null,加入到队列中
                if (current.left != null)
                    queue.offer(current.left);
                //如果该结点的右孩子不是null,加入到队列中
                if (current.right != null)
                    queue.offer(current.right);
            }
        }
    }


    //删除元素
    public boolean delete(E element){
        TreeNode parent=null;
        TreeNode current=root;
        //1.先找到要删除的元素的位置,current表示,父元素的parent
        while (current!=null){
            if(element.compareTo(current.element)<0){
                parent=current;
                current=current.left;
            }else if(element.compareTo(current.element)>0){
                parent=current;
                current=current.right;
            }else
                break;

        }
        //2.没找到这个元素
        if(current==null)
            return false;

        //3.case1:要删除的元素没有左子结点
        if(current.left==null){
            //如果没有父节点,相当于要删除的是根节点,
            if(parent==null)
                root=current.right;
            //判断要删除的元素是在父元素的左子树上
            else if(element.compareTo(parent.element)<0)
                parent.left=current.right;
            else
                parent.right=current.right;
        }
        //4.case2:要删除的结点有左子结点
        else{
            TreeNode parentOfRightMost=current;
            TreeNode rightMost=current.left;
            //通过此方法得到要删除结点左子树最大的元素(也就是最靠右的元素)
            while (rightMost.right!=null)
            {
                parentOfRightMost=rightMost;
                rightMost=rightMost.right;
            }
            //将左子树最大值复制给删除节点值
            current.element=rightMost.element;

            //如果rightMost是父节点的右边结点,此时不可能有更右边的结点了,
            // 删除之后,就让父节点连接rightMost的左结点
            if(parentOfRightMost.right==rightMost)
                parentOfRightMost.right=rightMost.left;
            else
                parentOfRightMost.left=rightMost.left;

        }

        size--;
        return true;

    }



}

3)测试类

package java_04_java语言程序设计.第25章_二叉查找树;

/**
 * Created by Administrator on 2017/12/7 0007.
 */
public class testTreeNode {
    public static void main(String[] args){
        //创建三个节点
        Integer []node={60,55,100,45,57,67,107,59,101};
        BST BST =new BST(node);
        //前序遍历
        System.out.println("前序遍历:");
        BST.preorder();
        System.out.println();
        //非递归前序遍历
        System.out.println("非递归前序遍历:");
        BST.iterativepreorder();
        System.out.println();
        //中序遍历
        System.out.println("中序遍历:");
        BST.inorder();
        System.out.println();
        //非递归中序遍历
        System.out.println("非递归中序遍历:");
        BST.iterativeinorder();
        System.out.println();
        //后序遍历
        System.out.println("后序遍历:");
        BST.postorder();
        System.out.println();
        //非递归h后序遍历
        System.out.println("非递归后序遍历:");
        BST.iterativepostorder();
        System.out.println();
        //广度优先遍历
        System.out.println("广度优先遍历:");
        BST.layerTraversal();
        System.out.println();

        BST.delete(55);
        System.out.println("删除后前序遍历:");
        BST.preorder();
        System.out.println();

    }
}

8.示例学习:哈夫曼编码

20.第25章:二叉查找树_第8张图片

思想:
1).字符串中每个字符出现的次数为权重
可以利用map来实现字符串中每个字符出现次数的统计。
2).创建节点类Node,包括字符、权重、左孩子、右孩子四个属性;每个字符都是一个叶子节点,将所有的叶子节点放在一个Linkedlist中。
3).创建哈夫曼树:
按从小到大的顺序对Linkedlist进行排序。
找到最小的两个节点,为他们创建父节点,父节点的权重为两个节点的权重之和。
linkedlist删除两个节点,将父节点加入。
循环上述操作,直到linkedlist只有一个节点,该节点就是root节点。
4).遍历哈夫曼树,找到叶子节点,将字符和编码存储到map中。
5).输出字符串的编码。

/*
    实现哈夫曼编码
    提示用户输入一个字符串,显示每个字符的哈夫曼编码
 */
public class HuffmanCode {

    /*
        定义节点类,包括字符,权重,左孩子,右孩子
     */
    public static class Node{
        //定义节点的字符
        private Character elem;
        //定义该字符的权值就是字符出现的次数
        private double weight;
        //定义左孩子
        private Node leftChild;
        //定义右孩子
        private Node rightChild;

        //为中间的节点准备的构造函数
        public Node(double weight, Node leftChild, Node rightChild) {
            this.weight = weight;
            this.leftChild = leftChild;
            this.rightChild = rightChild;
        }

        //为叶子节点准备的构造函数
        public Node(Character elem, double weight) {
            this.elem = elem;
            this.weight = weight;
        }

        @Override
        public String toString() {
            return "Node{" +
                    "elem=" + elem +
                    ", weight=" + weight +
                    ", leftChild=" + leftChild +
                    ", rightChild=" + rightChild +
                    '}';
        }
    }

    //实现Node的比较器,根据权重来比较Node节点,排序时需要使用
    static class NodeComparator implements Comparator {

        @Override
        public int compare(Node o1, Node o2) {
           if(o1.weighto2.weight)
               return 1;
           else
               return 0;
        }
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.print("请输入字符串");
        //text存储输入的字符串
        String text = input.nextLine();
        //统计字符串中各个字符出现的次数
        Map map = getCharacterFrequency(text);
        //创建叶子节点,放在list中
        LinkedList nodelist= CreateNodes(map);
       //构造哈夫曼树
        Node root=CreateTree(nodelist);
       //将哈夫曼编码结果存储在map中
        Map letterCodeMap=getLetterCode(root);
        for(int i=0;i getCharacterFrequency(String text) {
        Map map = new HashMap();
        for (int i = 0; i < text.length(); i++) {
            //字符值为键值
            Character c = text.charAt(i);
            //出现的次数为value值
            Integer count = map.get(text.charAt(i));
            //如果count是null,就把1放进去,不然就给count+1
            map.put(c, count == null ? 1 : count + 1);
        }
        return map;
    }

    //创建叶子节点,放在list中
    private static LinkedList CreateNodes(Map map) {
        LinkedList nodeList=new LinkedList<>();
        for(Map.Entry entry:map.entrySet()){
            Character elem=entry.getKey();
            Integer value=entry.getValue();
            Node node=new Node(elem,value);
            nodeList.add(node);
        }
        return nodeList;
    }

    //构造哈夫曼树
    private static Node CreateTree(LinkedList nodelist) {
        NodeComparator nodeComparator=new NodeComparator();
        //有大于一个的元素就需要进行合并
        while(nodelist.size()>1){
            //将节点按从小到大的顺序排序。
            nodelist.sort(nodeComparator);
            //找到最小的两个节点
            Node left=nodelist.get(0);
            Node right=nodelist.get(1);
            //创建parent节点,权值为最小的两个节点权值之和,左孩子是left节点,右孩子是right节点
            Node parent=new Node(left.weight+right.weight,left,right);
            //删掉集合中的最小的两个元素
            nodelist.remove(0);
            nodelist.remove(0);
            //将parent元素加入
            nodelist.add(parent);
        }
        //直到只剩下一个元素,就是根元素
        return nodelist.get(0);
    }


    //将得到的哈夫曼树,转成哈夫曼编码,存储到map中(字符,编码)
    private static Map getLetterCode(Node root) {
        Map letterCodeMap=new HashMap<>();
        //只有一个节点
        if(root.leftChild==null&&root.rightChild==null){
            letterCodeMap.put(root.elem,"1");
            return letterCodeMap;
        }
        getLetterCode(root," ",letterCodeMap);
        return letterCodeMap;
    }

    private static void getLetterCode(Node root, String suffix, Map letterCodeMap) {
        if(root!=null){
            //递归出口,是叶子节点
            if(root.leftChild==null&&root.rightChild==null){
                letterCodeMap.put(root.elem,suffix);
            }
            getLetterCode(root.leftChild,suffix+"0",letterCodeMap);
            getLetterCode(root.rightChild,suffix+"1",letterCodeMap);
        }
    }


}

你可能感兴趣的:(20.第25章:二叉查找树)