数据结构(8)--树

本文介绍了树形结构的基础概念,重点讨论了二叉树的存储表示及其各种运算。此外,还深入探讨了一般树和森林与二叉树之间的转换关系,以及如何通过前序遍历创建二叉树并利用中序遍历进行线索化,形成线索二叉树。

树形结构是一类重要的非线性结构。树形结构是结点之间有分支,并具有层次关系的结构。它非常类似于自然界中的树。树结构在客观世界中是大量存在的,例如家谱、行政组织机构都可用树形象地表示。树在计算机领域中也有着广泛的应用,例如在编译程序中,用树来表示源程序的语法结构;在数据库系统中,可用树来组织信息;在分析算法的行为时,可用树来描述其执行过程。本章重点讨论二叉树的存储表示及其各种运算,并研究一般树和森林与二叉树的转换关系,最后介绍树的应用实例。

关键词:
* 树:树由边连接的节点构成。
* 多路树:节点可以多于两个。
* 路径:顺着连接点的边从一个节点到另一个节点,所以过的节点顺序排列就称做路径。
* 根:树的顶端节点称为根。
* 父节点:每个节点都有一条边向上连接到另一个节点,这个节点就称为父节点。
* 子节点:每个节点都可能有一条或多条边向下连接其它节点,下面这些节点就称为子节点。
* 叶节点:没有子节点的节点为叶子节点或叶节点。
* 子树:每个节点都可以作为子树的根,它和它所有的子节点都包含在子树中。
* 访问:当程序控制流程到达某个节点时,就称为“访问”这个节点。
* 遍历:遍历树意味着要遵循某种特定的顺序访问树中所有的节点。
* 层:一个节点的层数是指从根开始到这个节点有多少“代”。一般根为第0层。
* 关键字:对象中通常会有一个数据域被指定为关键字,通常使用这个关键字进行查询等操作。
* 二叉树:如果树中每个节点最多只能有两个子节点,这样的特殊的树就是二叉树。
* 二叉搜索树:二叉树的一个节点的左子节点的关键字值小于这个节点,右子节点的关键字值大
* 于或等于这个父节点。
* 平衡树与非平衡树:左子节点与左子节点对称的树为平衡树,否则就是非平衡树。
* 完全二叉树:二叉树的最后一层都是叶子结点,其它各层都有左右子树,也叫满二叉树。
*
* 为什么用二叉树:1.二叉树结合了另外两种数据结构的优点:一种是有序数组,另一种是链表。
* 在树中查找数据的速度和在有序数组中查找的速度一样快,同时插入的速度
* 和删除的速度和链表的速度一样。
* 2.在有序数组中插入数据项太慢:用二分查找法可以在有序数据中快速的查找
* 特定的值,查找所需时间复杂度为O(logN)。然而插入和删除是非常低效的。
* 3.在链表中查找太慢:链表的插入和删除操作都很快,时间复杂度是O(1)。
* 然而查找数据项是非常低效的。
* 二叉树的效率:时间复杂度为O(logN)。树对所有的数据存储操作都很高效。
*
* 程序介绍:对树的一些常用操作进行了封装,包括查询,插入,删除,遍历二叉树(中序,后序,前序)
* 以及以树的方式显示二对树的各个结点。

线索二叉树

一般实现树都可以通过前序遍历进行创建,利用中序遍历进行线索化.我们称之为线索二叉树.
在一棵只有n个结点的二叉树中,假设有n个结点,那么就有2n个指针域,因为二叉树只用到了其中的n-1个结点,所以只要利用剩下的n+1个节点,我们就能把中需遍历时所得到的中序二叉树保存下来,以便下次访问。

中序二叉树的指针域有两种类型:一是用于链接二叉树本身;二是用于链接中序遍历序列。这类型的指针,左指指向中序遍历时节点顺序的前驱,右指针指向中序遍历时的后继。为了区别这两种指针域类型,我们在树的结点中要加上两个标志lchild和rchild,分别标志左右指针域的类型。

代码如下:

/**
 * 为什么需要头结点呢?
 * 答:
 * 1. 因为为了仿照线性表的存储结构,我们需要添加上一个头结点,不然在初始化的时候,我们无法限定到二叉树的根节点,会出现错误.
 * 2. 具体实现:令其lchild域指向二叉树的根节点,其rchild域的指针指向中序遍历访问的最后一个节点。
 * 反之,令二叉树中序序列中的第一个节点的lchild域指针和最后一个节点的rchild域的指针均指向头结点。
 * 
 * @author Administrator
 *
 */
public class Tree {
     TreeNode head; //头结点
     TreeNode root;
     public void initTree(){
            head=new TreeNode(-1);
     }
    /**
     * 创建二叉树:通过前序遍历创建二叉树
     * @param args
     */

     public void buildTree(char[] data){
         head=null;
         root=new TreeNode(data[0]);
         for(int i=1;i<data.length;i++){
             TreeNode tempNode=root;
             while(true){
                 //等于根节点
                 if(tempNode.data==data[i]){
                      break;
                 }
                 //少于根节点
                 if(tempNode.data>data[i]){
                     //如果左孩子为空,就将这个元素插入
                     if(tempNode.lchild==null){
                         tempNode.lchild = new TreeNode(data[i]);
                         break;
                     }
                     //解析一下:如果不为空,这里就是迭代的好处
                     /**
                      * 假如不为空就是根节点的左边存在元素,这里我们的根节点就是tempNode
                      * 就要将他的左边那个子节点拿出来比较,所以实际是第一步是root.lchild的对象取出
                      * 再放进来进行一次比较
                      */ 
                     tempNode=tempNode.lchild;
                 }else{
                     if(tempNode.rchild==null){
                         tempNode.rchild=new TreeNode(data[i]);
                         break;
                     }
                     tempNode=tempNode.rchild;
                 }

             }
         }

     }

     /**
      * 中序遍历:线索化
      * 
      */
     public void inOrderThreading(){
         TreeNode current;//当前节点
         TreeNode previous;//上次访问的节点,用于记住是前驱还是孩子,作为一个区别作用
         initTree();
         //创建了头结点对象,并且进行标识赋值
         head.LTag = 0; //头结点的左边是左孩子
         head.RTag = 1;  //头结点右边是前驱
         // 二叉树为空的时候,头结点指向其本身,不存在根节点
         if (root == null) {
             head.lchild = head.rchild = head;
         } else {
             //不等于0的时候标识,当前节点是root
             current = root;
             head.lchild=current;
             previous=head;//这时候相对于root来说 他的前驱是head
             //获取当前节点的前驱,设定前驱是1,后继是头结点.
             previous=inThreading(current, previous);
             System.out.println("建立线索二叉树后,previous指针的值为:" + previous.data);
             previous.RTag = 1;
             //让最后一个元素的的右指针域指向头结点
             previous.rchild = head;
             //设定头结点的右孩子是最后一个结点
             head.rchild = previous;            
             System.out.println("建立线索二叉树后,最后一个节点为:" + previous.data
                     + ",对应的后继节点为:" + previous.rchild.data);
         }
     }
     // 前驱后继都是相对于头结点和叶子节点而言
     // 其中current指针指向当前访问的节点;previous节点指向刚刚访问过的节点
     private TreeNode inThreading(TreeNode current, TreeNode previous) {
         //当前有访问的节点
         if (current != null) {
             //不断地向左边进行遍历,只当当前节点为空,就是遍历到没有左孩子了
             TreeNode tmpNode = inThreading(current.lchild, previous);
             // 前驱线索,当左孩子为空而且并且标识默认为有左孩子,我们就改变成前驱
             if (current.lchild == null && current.LTag == 0) {
                 current.LTag = 1;
                 current.lchild = previous;
             } 
             //将遍历到的前一个节点就作为前驱
             previous = tmpNode;
             // 后继线索
             if (previous.rchild == null && previous.RTag == 0) {
                 previous.RTag = 1;
                 previous.rchild = current;
             }  
             previous = current;// 保持previous指向current的前驱
             previous = inThreading(current.rchild, previous);
             return previous;
         }
         return previous;
     }
     /**
      * 未线索化的中序递归遍历二叉树
      */
     public void traversalTBTree() {
         traversalTBTree(root);
         System.out.println();
     }

     private void traversalTBTree(TreeNode node) {
         if (node != null) {
             traversalTBTree(node.lchild);
             System.out.print(node.data + "  ");
             traversalTBTree(node.rchild);
         }
     }
     /**
      * 线索化的递归遍历二叉树
      */
     public void inOrderReaversal() {
         TreeNode node;
         if (head != null) {
             node = head.lchild; // node表示head头指针指向的root节点
             // 空树或者遍历结束 node==head
             while (node != head) {
                 // 访问左子树
                 while (node.LTag == 0)
                     node = node.lchild;
                 System.out.print(node.data + "   ");
                 while (node.RTag == 1 && node.rchild != head) {
                     // 访问叶子节点的后继
                     node = node.rchild;
                     System.out.print(node.data + "   ");
                 }
                 // 访问完叶子节点的后继后,访问右子树
                 node = node.rchild;
             }
         }
     }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
         Tree tbTree = new Tree();
            /***********************************************************************
             * 初始化操作
             **********************************************************************/
            char[] data = {2, 8, 7, 4, 9, 3, 1, 6, 7, 5};
            tbTree.buildTree(data);
            tbTree.traversalTBTree();
            System.out.println(tbTree.head == null);
            System.out.println("########################################");
            System.out.println("线索化后,二叉树遍历结果:");
            tbTree.inOrderThreading();
            tbTree.inOrderReaversal();

    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值