数据结构(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(); }}
- 数据结构(8)--树
- 数据结构--树
- 树--数据结构
- 数据结构-树
- 【数据结构】树
- 数据结构--树
- 数据结构---树
- 数据结构---->树
- 数据结构 树
- 数据结构树
- 数据结构 树
- 数据结构----树
- 数据结构--树
- 数据结构 - 树
- 数据结构:树
- 数据结构 - 树
- 数据结构-树
- 数据结构---树
- (16)Struts2_OGNL读取Map栈及其他字段和方法属性
- OKHttp源码分析2 - Request的创建和发送
- 操作系统-进程
- 数据结构(7)--队列
- 决策树
- 数据结构(8)--树
- React-Native入门指导之iOS篇 —— 一、准备工作
- 【微信服务号】ngrok内网穿透
- case语句
- Ubuntu 16.04查看软件安装位置
- 下拉菜单的实现(一)
- ubuntu关机命令
- DOS常用命令
- android开发入门网站和书籍推荐