数据结构(Java)--树和二叉树

来源:互联网 发布:西南政法大学王洪 知乎 编辑:程序博客网 时间:2024/06/09 16:50
一.
     1.基础知识
 树是由n(n>=0)个结点组成的有限集合。n=0的树称为空树;n>0 的树由以下两个条件约定构成
有且仅有一个结点n,它没有前驱结点,只有后继结点。n称为树的根结点
除结点n外,其余的每一个结点都有且仅有一个直接前驱结点;有零个或多个直接后继结点。
一颗大树分成几个大的分枝,每个大分枝也都是一棵树,由此我们可以给出树的递归定义。
树是由n个结点组成的有限集合。n=0的树称为空树;n>0的树由以下两个条件约定构成:
有且仅有一个结点n,它没有前驱结点,只有后继结点。n称为树的根结点
除根结点之外的其他结点分为m(0<=m<n)个互不相交的集合T。其他的每个集合本身又是一棵树,称为根的子树
父母、孩子与兄弟结点    
  • 结点的直接前驱结点称为其父母结点
  • 结点的直接前驱结点称为其孩子结点
  • 拥有同一个父母结点的多个结点之间称为兄弟结点
  • 结点的祖先是指从根结点到其父母结点所经过的所有结点
  • 结点的后代是指该结点的所有孩子结点,以及孩子的孩子
  • 祖先与后代的关系则是对父子关系的延伸,其定义了树中结点的纵向次序。
 
  • 结点的度是指结点所拥有子树的棵树
  • 度为零的节点称为叶子或终端结点
  • 度不为零的节点称为分支结点或者非终端结点、非叶结点
  • 树的度是指树中各结点的度的最大值
结点层次、树的高度
  • 结点的层次属性反应结点处于树中的层次位置
  • 约定根结点的层次为1,其余结点的层次是其父母结点的层次加1
  • 树的高度或深度是树中结点的最大层次数
边、路径、直径
  • 连接两个结点的线段称为边
  • 从一个点到另一个点所经过的边,称为路径
  • 路径长度即所经过的边数
  • 树的直径指从根到叶子结点的一条最长路径,树的高度-1
无序树、有序数
  • 若把树中每个结点的各子树看成从左到右有次序的(即不能交换),则称该数为有序数;否则称为无序树
森林是M棵互不相交数的集合
  • 给森林加上一个根就变成一棵树
  • 将树的根结点删除就变成了森林
     2.树抽象数据结构
ADT Tree<T>{     boolean isEmpty()  //判空     int level (T key)   //层次     int size()     //结点数     int height()     //高度     void preorder()     //先根次序遍历     void preorder()     //后根次序遍历     void levelorder()     //层次遍历     TreeNode<T> insert(T x) //插入元素X作为根     TreeNode<T> insert(TreeNode<T> p,T x,int i)     //插入x作为p结点的第i(>=0)个孩子     void remove(TreeNode<T> p,int i) //删除子树     void clear()               //删除所有结点     TreeNode<T> search(T key)      //查找     boolean contains(T key)     //包含     T  remove(T key)          //删除子树}


     3.二叉树
二叉树的性质
  • 若根结点的层次为1,则二叉树第i层最多有2i-1个结点
  • 在高度为H的二叉树中,最多有2h-1个结点
  • 设一棵二叉树的叶子结点数为n0,2度结点数为n2,则n0=n2+1
  • 具有n个结点的完全二叉树的高度为
  • 一棵具有N个结点的完全二叉树,对序号为i(0<=i<n),有
    • 若i=0,则i为根结点,无父母结点;若i>0,则i的父母结点序号为:(i-1)/2
    • 若2i+1<n,则i的左孩子结点序号为2i+1;否则i无左孩子
    • 若2i+2<n,则i的右孩子结点序号为2i+2,否则i无右孩子
满二叉树
  • 一棵高度为h,且有 2h-1(h≥0)个结点的二叉树
  • 特点
    • 每一层上的结点数都达到最大值,
    • 满二叉树中不存在度数为1的结点,每个分支结点上均有两棵高度相同的子树,且树叶都在最下层上
完全二叉树
  • 满二叉树最底层从右向左依次去掉若干条边
给二叉树结点编号
    • 在一棵n个结点的完全二叉树中,从树根起,自上层到下层,每层从左至右,给所有结点编号,能得到一个反映整个二叉树结构的线性序列。

二叉树的遍历规则 
  • 先根遍历
  • 后根遍历
  • 中根遍历
二叉树抽象数据类型
ADT BinaryTree<T>{    boolean isEmpty()         //判空    int size()                         //结点数    int height()                     //高度    void preOrder()             //先根次序遍历    void inOrder()               //中根次序遍历    void postOrder()           //后根次序遍历    void levelOrder()          //按层次遍历 BinaryNode<T> insert(T x)   //插入根    BinaryNode<T> insert(BinaryNode<T> p, T x,    boolean leftChild)             //插入孩子    void remove(BinaryNode<T> parent, boolean   leftChild)                           //删除子树    void clear()                              //删除所有结点    BinaryNode<T> search(T key)   //查找    boolean contains(T key)        //包含    int level(T key)                       //key结点所在层次}

二叉树的存储结构
  • 二叉树的顺序存储结构
    • 编号特点
      (1)若i=0,则该结点是为根结点,无父亲。
      (2)若i≠0,则该结点的父亲结点编号为(i-1)/2的整数部分。
      (3)若2i+1<n,则该结点的左儿子结点编号为2i+1,否则该结点无左儿子。该结点必为叶子结点。
      (4)若2i+2<n,则该结点的右儿子结点编号为2i+2,否则该结点无右儿子。
      (5)若i为偶数(不为0),则该结点的左兄弟为i-1。
      (6)若i为奇数(不为n-1),则该结点的右兄弟为i+1。
    • 顺序存储的优点和缺点
      • 对完全二叉树而言,顺序存储结构既简单又节省存储空间。
      • 一般的二叉树采用顺序存储结构时,虽然简单,但易造成存储空间的浪费。
      • 在对顺序存储的二叉树做插入和删除结点操作时,要大量移动结点。
  • 二叉树的链式存储结构
    • 二叉链表

    • 三叉链表
    • 二叉树的二叉链表实现
      • 二叉树的二叉链表结点类(BinaryNode.java)
        public class BinaryNode<T>{    T data;                                     //数据元素    BinaryNode<T> left, right;   //左、右孩子    public BinaryNode(T data, BinaryNode<T> left,               BinaryNode<T> right) //构造结点    public BinaryNode(T data)     //构造叶子    public String toString()          //描述字符串    public boolean isLeaf()           //判结点}


      • 采用二叉链表存储的二叉树类
        public class BinaryTree<T>{     BinaryNode<T> root;        //根结点     public BinaryTree()           //构造空树     public boolean isEmpty()  //判空}


      •  二叉树插入结点
        BinaryNode<T> insert(T x) //插入根结点BinaryNode<T> insert(BinaryNode<T> parent, T x, boolean leftChild)                       //插入x为parent结点的左/右孩子


      • 二叉树删除子树
        //删除parent结点的左/右子树public void remove(BinaryNode<T> parent, boolean leftChild)public void clear()     //删除所有结点

      • 二叉树孩子优先的遍历算法
        (1)先根次序遍历二叉树算法描述
        public void preorder()      //输出先根次序遍历序列{    preorder(this.root);      //遍历以root结点为根的二叉树}   //先根次序遍历以p结点为根的子树,递归方法private void preorder(BinaryNode<T> p){    if (p!=null)                     //若二叉树不空    {   System.out.print(p.data.toString()+" "); //先访问        preorder(p.left);       //遍历p的左子树,递归调用        preorder(p.right);    //遍历p的右子树,递归调用    }}

        (2)先根、中根、后根次序遍历二叉树的递归算法
        String toString()          //先根次序遍历String toString(BinaryNode<T> p)//递归void inorder()              //中根次序遍历序列void inorder(BinaryNode<T> p)    //递归void postorder()         //后根次序遍历序列void postorder(BinaryNode<T> p) //递归//根据不同的应用需求,采用不同次序遍历。    int size()                                       //结点数    int height()                                   //高度


      • 构造二叉树
        二叉树的构造规则,明确以下两点:
        n结点与父母结点及孩子结点之间的层次关系。
        n兄弟结点间的左右子树的次序关系。
        (1) 先根和中根序列表示
      • 2) 标明空子树的先根序列表示

      • 中根遍历非递归算法描述
        1.初始化一个空栈。
        2.从二叉树的根结点p开始,如果p不空或栈不空,循环执行以下操作。
        1) 如果p不空,表示到达了一个结点,将结点p入桟,并进入其左子树。
        2) 如果p为空而栈不空,表明已经沿着某条路径走出了二叉树,此时需返回访问这条路径最后经过的结点,而该结点正好被保存在栈的栈顶,因此弹出栈顶元素并让p指向它,接下来访问结点p,然后进入其右子树。
        3.算法结束
      • 二叉树的层次遍历
        • 层次遍历:是指从二叉树的第1层(根结点)开始,从上至下逐层遍历,在同一层中,则按从左到右的顺序对结点逐个访问。
        • 层序遍历需要借助具有先进现出特点的——队列实现。
        • 1.初始化一个空队。
          2.从二叉树的根结点p开始,如果p不空时,循环执行以下操作。
          • 1) 访问P结点;
          • 2) 如果P存在左孩子结点,将左孩子结点入队;
          • 3) 如果P存在右孩子结点,将右孩子结点入队;
          • 4) 执行出队操作,让p指向出队结点。
        • 3.算法结束。
      • Huffman编码与Huffman树
        • 对字符集进行编码时,要求字符集中任一字符的编码都不是其余字符编码的前缀。
        • 我们用Huffman树可以实现满足这种要求的编码,并且用这样的编码可以使得编码总长度最短。
        • 我们称这种编码为Huffman编码。

        • 二叉树的路径长度
          • 从根结点到所有结点的路径长度之和称为该二叉树的路径长度(Path Length,PL)
        • 二叉树的外路径长度
          • 从根结点到所有叶子结点的路径长度之和称为该二叉树的外路径长度。
        • 二叉树的带权外路径长度
            • 若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。
            • 结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。
            • 所有叶子结点的带权路径长度之和称为二叉树的带权外路径长度,记为WPL。

          • n给定n个权值作为n个叶子结点,构造一棵二叉树,若带权外路径长度达到最小,称这样的二叉树称为Huffman树。

          • Huffman树中,权越大的叶子离根越近;

          • Huffman树的形态不唯一,但WPL一定是最小 。

          • (1)Huffman算法描述
          • (2) 采用静态三叉链表储Huffman树
            public class TriElement      //二叉树的静态三叉链表元素{     int data;                        //数据域    int parent, left, right;  //父母、左、右孩子结点下标    public TriElement(int data, int parent, int left, int right)    public TriElement(int data)     //构造叶子结点    public String toString()    public boolean isLeaf()              //判叶子}

            (3) 构造Huffman树
            字符集{A~H},权集{5,29,7,8,14,23,3,11}
          • public class HuffmanTree{    String charset;             //字符集合    TriElement[] huftree; //静态三叉链表结点数组    HuffmanTree(int[] weights)    String getCode(int i)   //第i个字符编码    String toString()          //所有字符的编码串}


            (4)获得Huffman编码
            8.   树的表示和实现
          •    树的遍历规则
            1、树的先根遍历算法描述如下:
            • 若根结点非空。
            • 访问根结点。
            • 按照从左到右的次序先根遍历根的每一棵子树。
            2、树的后根遍历算法描述如下:
            • 若根结点非空。
            • 按照从左到右的次序后根遍历根的每一棵子树。
            • 访问根结点。

   1、先根遍历     

2、后根遍历

树的存储结构

  • 2、孩子兄弟链表

  • 补充:树或森林与二叉树之间的转换
  • 树或森林与二叉树之间有一个自然的一一对应关系。
  • 任何一个森林或一棵树可惟一地对应到一棵二叉树;
  • 反之,任何一棵二叉树也能惟一地对应到一个森林或一棵树。

  •  1、树转换为二叉树
    • 树中每个结点最多只有一个最左边的孩子(长子)和一个右邻的兄弟。按照这种关系就能将树转换成相应的二叉树:
  • (1)树中所有相邻兄弟之间加一条连线;
    (2)对树中的每个结点,只保留它与最左边的孩子(长子)结点之间的连线,删去它与其它孩子结点之间的连线。
  • 3、森林转换为二叉树
    • 森林是若干棵树的集合,森林亦可用二叉树表示。森林转换为二叉树的方法如下:
    1.将森林中的每棵树转换成相应的二叉树;
    2.第一棵二叉树不动,从第二棵二叉树开始,依次序将后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子,当所有的二叉树连在一起后,这样所得到的二叉树就是由森林转换得到的二叉树。