一、 树的概述

  树是计算机中应用广泛的一种数据结构,日常生活中常见的图谱,公司组织结构等,都是树结构的数据。

  树结构在计算机中是根朝上,叶子结点向下的。如图,它是由N个有限结点组成的具有层次关系的集合。

   

树结构 mysql 树结构图_树结构 mysql

  树有如下特点:

  • 没有父结点的称为根结点
  • 每个结点有0或多个子结点
  • 每一个非根结点只有一个父结点
  • 每个结点及其后代结点可以看成一颗子树,称为当前结点父结点的一颗子树

二、 树的术语

  • 结点的度:结点所含子树的个数称为结点的度,如下图:2,3,4为1的子树,1的度为3。5,8和6为2的子树,2的度为2。
  • 叶结点:度为0的结点称为叶结点,如4即为叶结点
  • 分支结点:度不为0的结点称为分支结点。
  • 结点的层次:根结点层次为1,往下的后继结点层次为2,依次类推
  • 结点的层序编号:将结点的元素,从上到下,从左到右依次排序,如下图为12345678
  • 树的度:树中所有结点的度的最大值,不一定是根结点,如下截图的6若衍生很多的子结点,则度的最大值计算应该算6的子结点个数
  • 树的深度:层次最多的结点
  • 森林:去掉根结点后的N个不相交的集合,称为森林
  • 孩子结点:一个结点的后继结点称为孩子结点
  • 父结点:一个结点的前驱结点称为父结点
  • 兄弟结点:同一父结点的孩子结点互称为兄弟结点

  

树结构 mysql 树结构图_结点_02

三、 二叉树的基本定义

  二叉树:度不超过2的数称为二叉树

   

树结构 mysql 树结构图_结点_03

  满二叉树:每一个结点度都达到最大值的树称为满二叉树

   

树结构 mysql 树结构图_树结构 mysql_04

  完全二叉树:往二叉树放元素时,按从上到下,从左到右的次序去放,即度为0的叶子结点,只能出现在最下或次下层的称为完全二叉树

   

树结构 mysql 树结构图_结点_05

四、 二叉查找树的构建

   

树结构 mysql 树结构图_结点_06

  构建如上图的二叉树,需要怎样声明对象和组成二叉树呢,见如下实现:

public class Node<Key,Value> {
 
       //结点的键
       public Key key;
      
       //结点的值
       public Value value;
      
       //结点的左子结点
       public Node left;
      
       //结点的右子结点
       public Node right;
 
       public Node(Key key, Value value, Node left, Node right) {
              super();
              this.key = key;
              this.value = value;
              this.left = left;
              this.right = right;
       }
      
       public static void main(String[] args) {
              Node node1 = new Node(1, "A", null, null);
              Node node2 = new Node(2, "B", null, null);
              Node node3 = new Node(3, "C", null, null);
              Node node4 = new Node(4, "D", null, null);
              Node node5 = new Node(5, "E", null, null);
              Node node6 = new Node(6, "F", null, null);
             
              node1.left = node2;
              node1.right = node3;
             
              node2.left = node4;
              node2.right = node5;
             
              node3.left = node6;
       }
}

 

五、 二叉查找树的实现

  二叉树插入键值对:

  (1)     当前二叉树的结点为空时,把新结点作为根结点

  (2)     当前二叉树的结点不为空时,若新结点的key小于当前结点,则继续查找当前结点的左子结点

  (3)     若新结点的key大于当前结点,则继续查找当前结点的右子结点

  (4)     若新结点的key等于当前结点,则将当前结点的value替换

   二叉树获取指定结点:

  (1)     从根结点开始,若查询的key小于当前结点,则继续查找当前结点的左子结点

  (2)     若查询的key大于当前结点,则继续查找当前结点的右子结点

  (3)     若查询的key等于当前结点,则返回当前结点

  二叉树删除指定结点:

  (1)     首先使用获取结点的方法,找到要删除的结点

  (2)     找到被删除结点右子树中最小的元素min,并删除它

  (3)     让被删除结点的左子树成为min的左子树,让被删除结点的右子树成为min的右子树

  (4)     让被删除结点的父节点指向min

public class BinaryTree<Key extends Comparable<Key>,Value> {
 
       // 根结点声明
       public Node root;
      
       //树的元素个数
       public int N;
      
       public BinaryTree() {
              root = null;
              N=0;
       }
      
       /**
        * 二叉树元素个数
        * @return
        */
       public int size() {
              return N;
       }
      
       /**
        * 二叉树是否为空
        * @return
        */
       public boolean isEmpty() {
              return N==0;
       }
      
       /**
        * 往二叉树中添加元素
        * @param key
        * @param value
        */
       public void put(Key key,Value value) {
              root = put(root, key, value);
       }
      
       /**
        * 往二叉树指定结点添加元素
        * @param node
        * @param key
        * @param value
        */
       public Node put(Node node,Key key,Value value) {
              if(node==null) {
                     //总数+1
                     N++;
                     return new Node(key, value, null, null);
              }
             
              int compare = key.compareTo((Key) node.key);
             
              if(compare>0) {
                     //存放的key大于当前结点,则继续遍历当前结点的右子树
                     node.right = put(node.right,key,value);
              }else if(compare<0) {
                     //存放的key小于当前结点,则继续遍历当前结点的左子树
                     node.left = put(node.left,key,value);
              }else {
                     //存放的key等于当前结点,则替换
                     node.value = value;
              }
             
              return node;
       }
      
       /**
        * 从二叉树中获取key对应的value
        * @param key
        * @return
        */
       public Value get(Key key) {
              return get(root,key);
       }
      
       /**
        * 从二叉树指定结点中获取key对应的value
        * @param node
        * @param key
        * @return
        */
       public Value get(Node node,Key key) {
              if(node==null) {
                     return null;
              }
             
              int compare = key.compareTo((Key) node.key);
             
              if(compare>0) {
                     //查找的key大于当前结点,则继续遍历当前结点的右子树
                     return get(node.right,key);
              }else if(compare<0) {
                     //查找的key小于当前结点,则继续遍历当前结点的左子树
                     return get(node.left,key);
              }else{
                     return (Value) node.value;
              }
       }
      
       /**
        * 删除二叉树中指定结点
        * @param key
        */
       public Node delete(Key key) {
              return delete(root,key);
       }
      
       /**
        * 删除二叉树中指定结点
        * @param node
        * @param key
        */
       public Node delete(Node node,Key key) {
              if(node==null) {
                     return null;
              }
             
              int compare = key.compareTo((Key) node.key);
             
              if(compare>0) {
                     //删除的key大于当前结点,则继续遍历当前结点的右子树,
                     node.right = delete(node.right,key);
              }else if(compare<0) {
                     //删除的key大于当前结点,则继续遍历当前结点的右子树,
                     node.left = delete(node.left,key);
              }else {
                     //被删除结点只有左结点,没有右结点的情况
                     if(node.left!=null && node.right == null) {
                            //总数减1
                            N--;
                            return node.left;
                     }
                    
                     //被删除结点只有右结点,没有左节点的情况
                     if(node.right!=null && node.left == null) {
                            //总数减1
                            N--;
                            return node.right;
                     }
                    
                     //找到被删除结点的右子树中的最小结点,用来取代被删除的结点
                     Node min = node.right;
                     while(min.left!=null) {
                            min = min.left;
                     }
                    
                     //删除被删除结点的右子树中的最小结点
                     Node delNode = node.right;
                     while(delNode.left !=null) {
                            if(delNode.left.left==null) {
                                   delNode.left = null;
                            }else {
                                   delNode = delNode.left;
                            }
                     }
                    
                     //让被删除结点右子树中的最小结点,取代被删除的结点
                     min.right = node.right;
                     min.left = node.left;
                     node = min;
                    
                     //总数减1
                     N--;
              }
              return node;
       }
}


六、 二叉查找树最小键

  查找二叉树中最小键的思路:从根结点找起,一直找当前结点的左子树,直到当前结点的左子树为空时,则当前结点为二叉查找树的的最小键

/**
        * 查找二叉查找树的最小键
        * @return
        */
       public Key min() {
              return (Key) min(root).key;
       }
      
       public Node min(Node node) {
              if(node.left!=null) {
                     return min(node.left);
              }else {
                     return node;
              }
       }

 

七、 二叉查找树最大键

  查找二叉树中最大键的思路:从根结点找起,一直找当前结点的右子树,直到当前结点的右子树为空时,则当前结点为二叉查找树的的最大键

/**
        * 查找二叉查找树的最大键
        * @return
        */
       public Key max() {
              return (Key) max(root).key;
       }
      
       public Node max(Node node) {
              if(node.right!=null) {
                     return max(node.right);
              }else {
                     return node;
              }
       }


八、 二叉树的最大深度

  计算二叉树最大深度的思路

  • 当只有一个根结点时为1,当根结点为null时是0
  • 计算左子树的最大深度
  • 计算右子树的最大深度
  • 比较左右子树最大深度,取较大值+1(根结点)

 

/**
         * 查找二叉树的最大深度
        * @return
        */
       public int deepLength() {
              return deepLength(root);
       }             
      
       public int deepLength(Node node) {
              //根结点为空
              if(node==null) {
                     return 0;
              }
             
              //根结点不为空,定义左结点深度maxLeft,右结点深度maxRight,最大深度等于maxLeft和maxRight的较大值+1
              int maxLeft = 0;
              int maxRight = 0;
              int max = 0;
              System.out.println(node.key);
             
              //右子树不为空,则持续遍历当前结点的右子树
              if(node.right!=null) {
                     maxRight = deepLength(node.right);
              }
             
              //左子树不为空,则持续遍历当前结点的左子树
              if(node.left!=null) {
                     maxLeft = deepLength(node.left);
              }
             
              //最大深度计算
              max = maxLeft > maxRight ? maxLeft+1 : maxRight+1;
              return max;
       }

 

九、 二叉树的遍历

  如线性表中的链表,数组,队列,栈一样,二叉树也有遍历查找的需求,由于树的结构和线性表有很大的差异,没有办法从前往后去查询,此时我们可以根据搜索路径来查找,即通过控制根结点

  左子树或右子树的访问顺序,来实现二叉树的遍历查询。

  •  前序遍历:先访问根结点,再访问左子树,后访问右子树
  • 中序遍历:先访问左子树,再访问根结点,后访问右子树
  • 后序遍历:先遍历左子树,再访问右子树,后访问根结点

  

树结构 mysql 树结构图_子树_07

  前序遍历结果:EBADCGFH

  中序遍历结果:ABCDEFGH

  后序遍历结果:ACDBFHGE

十、 二叉树的遍历-前序遍历

  前序遍历的实现思路

  • 将当前结点的key放入队列中
  • 遍历当前结点的左子树,左子树不为空时,递归遍历左子树
  • 遍历当前结点的右子树,右子树不为空时,递归遍历右子树
/**
        * 前序遍历实现
        */
       public Queue<Key> preSearch() {
              Queue<Key> keys = new Queue<Key>();
              preSearch(root, keys);
              return keys;
       }
 
       public void preSearch(Node node, Queue<Key> keys) {
              if (node == null) {
                     return;
              }
 
              // 将当前结点放入队列中
              keys.enqueue((Key) node.key);
 
              // 遍历左子树,不为空时递归遍历
              if (node.left != null) {
                     preSearch(node.left, keys);
              }
 
              // 遍历右子树,不为空时递归遍历
              if (node.right != null) {
                     preSearch(node.right, keys);
              }
       }

 

十一、     二叉树的遍历-后序遍历

  后序遍历的实现思路

  • 遍历当前结点的左子树,左子树不为空时,递归遍历左子树
  • 遍历当前结点的右子树,右子树不为空时,递归遍历右子树
  • 将当前结点的key放入队列中
/**
        * 后序遍历实现
        */
       public Queue<Key> afterSearch() {
              Queue<Key> keys = new Queue<Key>();
              afterSearch(root, keys);
              return keys;
       }
 
       public void afterSearch(Node node, Queue<Key> keys) {
              if (node == null) {
                     return;
              }
 
              // 遍历左子树,不为空时递归遍历
              if (node.left != null) {
                     afterSearch(node.left, keys);
              }
 
              // 遍历右子树,不为空时递归遍历
              if (node.right != null) {
                     afterSearch(node.right, keys);
              }
             
              // 将当前结点放入队列中
              keys.enqueue((Key) node.key);
       }


十二、     二叉树的遍历-中序遍历

  中序遍历的实现思路

  • 遍历当前结点的左子树,左子树不为空时,递归遍历左子树
  • 将当前结点的key放入队列中
  • 遍历当前结点的右子树,右子树不为空时,递归遍历右子树

 

/**
        * 中序遍历实现
        */
       public Queue<Key> midSearch() {
              Queue<Key> keys = new Queue<Key>();
              midSearch(root, keys);
              return keys;
       }
 
       public void midSearch(Node node, Queue<Key> keys) {
              if (node == null) {
                     return;
              }
 
              // 遍历左子树,不为空时递归遍历
              if (node.left != null) {
                     midSearch(node.left, keys);
              }
             
              // 将当前结点放入队列中
              keys.enqueue((Key) node.key);
 
              // 遍历右子树,不为空时递归遍历
              if (node.right != null) {
                     midSearch(node.right, keys);
              }
}


十三、     二叉树的遍历-层序遍历

  层序遍历的实现思路

  • 创建队列A,存储当前结点
  • 循环队列A,首先弹出队列中的结点key,并存储到队列B中,如果当前结点的左子树不为空,则将左节点存储到队列A中,如果当前结点的右子树不为空,则将右结点存储到队列A中

      

/**
        * 层序遍历实现
        */
       public Queue<Key> layerSearch() {
              Queue<Node> A = new Queue<Node>();
              Queue<Key> B = new Queue<Key>();
              A.enqueue(root);
 
              while (!A.isEmpty()) {
                     Node n = A.dequeue();
                     B.enqueue((Key) n.key);
 
                     if (n.left != null) {
                            A.enqueue(n.left);
                     }
 
                     if (n.right != null) {
                            A.enqueue(n.right);
                     }
              }
 
              return B;
       }