数据结构(五):树

 

一、 树的概述

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

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

   

  树有如下特点:

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

二、 树的术语

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

  

三、 二叉树的基本定义

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

   

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

   

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

   

四、 二叉查找树的构建

   

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

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;

       }

 

九、 二叉树的遍历

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

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

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

  

  前序遍历结果: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;

       }

 

posted @ 2020-11-16 22:51  纪煜楷  阅读(844)  评论(0编辑  收藏  举报