数据结构(8)--树

来源:互联网 发布:linux tomcat自动关闭 编辑:程序博客网 时间:2024/06/05 12:42

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

关键词:
* 树:树由边连接的节点构成。
* 多路树:节点可以多于两个。
* 路径:顺着连接点的边从一个节点到另一个节点,所以过的节点顺序排列就称做路径。
* 根:树的顶端节点称为根。
* 父节点:每个节点都有一条边向上连接到另一个节点,这个节点就称为父节点。
* 子节点:每个节点都可能有一条或多条边向下连接其它节点,下面这些节点就称为子节点。
* 叶节点:没有子节点的节点为叶子节点或叶节点。
* 子树:每个节点都可以作为子树的根,它和它所有的子节点都包含在子树中。
* 访问:当程序控制流程到达某个节点时,就称为“访问”这个节点。
* 遍历:遍历树意味着要遵循某种特定的顺序访问树中所有的节点。
* 层:一个节点的层数是指从根开始到这个节点有多少“代”。一般根为第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();    }}
0 0
原创粉丝点击