- 浏览: 702380 次
- 性别:
- 来自: 北京
博客专栏
-
读金庸故事,品程序人生
浏览量:47704
文章分类
最新评论
-
hty881008:
LZ,你的json返回是怎么出来的,我的怎么是No messa ...
使用CXF暴露您的REST服务 -
jxFY:
赞
Apache的对象池化工具commons-pool -
wangyudong:
新版本的Wisdom RESTClient地址https:// ...
使用CXF暴露您的REST服务 -
wangyudong:
由CXF实现的微服务需要有比较好的工具去测试RESTful A ...
使用CXF暴露您的REST服务 -
spring_springdata:
可以参考最新的文档:如何在eclipse jee中检出项目并转 ...
Maven3实战笔记01环境配置与使用入门
1. 树的概念
如果线性表、栈、队列是线性结构(一维结构)的话,那么树就代表着一种非线性的、复杂的二维结构,何为线性结构、何为二维结构?就是1对1的一条直线,每个元素都是这条线上的节点、节点之间只知道1VS1的、前后关系。而二维结构就是一个面,1对N的一个面,这个面上的每一个元素都对应着多个此面上其他的元素。树就是指N个有父子关系的节点的有限集合。树仅仅只能有一个根节点。除了根节点,其他没有子节点的节点叫做叶子节点。树的遍历是最考究程序员算法的,因为后面的算法很多用到了一些概念,那先将树的有关概念解释一下。
节点的度:节点拥有的子树的个数
树的度:树中所有节点的度的最大值
节点的层次:从根节点开始算起,根的层次为1,其余节点层次为父节点层次+1。
树的深度:树中节点的最大层次值称为树的深度。
2. 树的基本操作:
树的操作有以下:为指定节点增加子节点、判断树是否为空、返回树的根节点、返回指定节点的父节点、返回指定节点的所有子节点集合、返回指定节点的第i个子节点、返回树的深度、返回指定节点的深度、返回指定节点的索引位置。
3. 树的使用场景
树形结构使用场景非常之多,抛开编程语言不说,就看身边的例子。大家windows的资源管理器、开发web系统的树形菜单、负载均衡的内核算法可以使用哈夫曼树来辅助实现、将要排序的数据用排序二叉树存储,查找的效率很快。
4. 树的记父节点实现
书上管其叫做父节点表示法,这个名称我觉得很容易让人产生不解,所以笔者管它叫做记父节点实现更好一些。它的算法的原理就是利用一个节点对象,该节点对象不仅记录了该节点的数据,还记录了此节点的父节点的位置。那么我们访问这个节点的时候,实际上父节点也能间接的获取到了。其实我们在做很多数据库设计的时候,遇到一个树形菜单基本上都是采用这种记父方式来实现的。其实很多的看似二维的结构,数据库记录,都可以提取出相应的一种图形化的模型。就像UML的类图关系就完全可以转化成关系型结构化数据库表模型,而数据库表结构也能提取出相应的领域模型一样。
主键 |
数据 |
父节点主键 |
0 |
根菜单 |
-1 |
1 |
子菜单1 |
0 |
2 |
子菜单2 |
0 |
3 |
子菜单11 |
1 |
上面的二维表结构可以描绘成以下结构
算法如下:
package dateStructer.tree; import java.util.ArrayList; import java.util.List; public class TreeParent<E> { /** * 节点结构 */ public class Node<T> { // 真正的数据域 private E date; // 记录父节点的索引位置 private int parentIndex; public Node() { } public Node(E date, int parentIndex) { this.date = date; this.parentIndex = parentIndex; } @Override public boolean equals(Object obj) { if (((Node) obj).date == this.date && ((Node) obj).parentIndex == this.parentIndex) return true; return false; } @Override public int hashCode() { return super.hashCode(); } @Override public String toString() { return (String) date; } } // 默认数组大小 private final int DefSize = 150; // 记录节点个数 private int nodeSize; // 父节点对象 private Node<E>[] nodes; public TreeParent() { nodes = new Node[DefSize]; } public TreeParent(E e) { nodes = new Node[DefSize]; nodeSize++; nodes[0] = new Node(e, -1); } /** * 为指定节点增加子节点 * * @param e * @param parentNode * @return */ public boolean addNodeByParent(E e, Node<E> parentNode) { for (int i = 0; i < nodes.length; i++) { if (nodes[i] == null) { int parentNodeIndex = getNodeIndex(parentNode); nodes[i] = new Node<E>(e, parentNodeIndex); nodeSize++; return true; } } return false; } /** * 根据node获得索引 * * @param node * @return */ public int getNodeIndex(Node<E> node) { for (int i = 0; i < nodes.length; i++) { if (nodes[i].equals(node)) { return i; } } return -1; } /** * 判断是否是空树 * * @return */ public boolean isEmpty() { return nodeSize == 0; } /** * 返回树的根节点 * * @return */ public Node<E> getRootNode() { if (nodeSize == 0) { return null; } return nodes[0]; } /** * 根据子节点返回父节点对象 * * @param chileNode * @return */ public Node<E> getParentNodeByChildNode(Node<E> chileNode) { for (int i = 0; i < nodeSize; i++) { if(chileNode == null){ return null; } if (i == chileNode.parentIndex) { return nodes[i]; } } return null; } /** * 根据父节点返回子节点对象集合 * * @param chileNode * @return */ public Node<E> getParentNodeByChildNode(Node<E> chileNode) { if (chileNode == null) { return null; } return nodes[chileNode.parentIndex]; } /** * 返回指定节点的第index个子节点,第1个代表第一个 * * @param parentNode * @param index * @return */ public Node<E> getIndexChildByParentNode(Node<E> parentNode, int index) { if (index == 0) { throw new RuntimeException("没有第0个子节点,从1开始"); } int childCount = 0; int parentIndex = getNodeIndex(parentNode); for (int i = 0; i < nodeSize; i++) { if (nodes[i].parentIndex == parentIndex) { ++childCount; if (childCount == index) { return nodes[i]; } } } return null; } /** * 返回节点的深度 * * @return */ public int returnNodeDeep(Node<E> node) { if (node == getRootNode()) { return 1; } else { Node<E> parentNode = getParentNodeByChildNode(node); if(parentNode != null){ return returnNodeDeep(parentNode) + 1; } return 0; } } /** * 返回树的深度 * * @return */ public int returnTreeDeep() { int max = 0; for (int i = 0; i < nodeSize; i++) { int nodeDeep = returnNodeDeep(nodes[i]); if (max < nodeDeep) { max = nodeDeep; } } return max; } @Override public String toString() { StringBuffer str = new StringBuffer("["); for (int i = 0; i < nodeSize; i++) { str.append("[" + nodes[i].date + "],"); } if (nodeSize > 0) { return str.substring(0, str.lastIndexOf(",")) + "]"; } return str.append("]").toString(); } }
测试代码
public static void main(String[] args) { TreeParent<String> tree = new TreeParent<String>("根菜单"); TreeParent<String>.Node<String> root = tree.getRootNode(); tree.addNodeByParent("子菜单1", root); tree.addNodeByParent("子菜单2", root); TreeParent<String>.Node<String> node1 = tree.getIndexChildByParentNode(root, 1); tree.addNodeByParent("子菜单11", node1); System.out.println("树的数据:"+tree); System.out.println("节点的深度:"+tree.returnNodeDeep(node1)); System.out.println("节点的深度:"+tree.returnNodeDeep(root)); TreeParent<String>.Node<String> node11 = tree.getIndexChildByParentNode(node1, 1); System.out.println("节点的深度:"+tree.returnNodeDeep(node11)); System.out.println("树的深度:"+tree.returnTreeDeep()); }
测试效果如下
树的数据:[[根菜单],[子菜单1],[子菜单2],[子菜单11]] 节点的深度:2 节点的深度:1 节点的深度:3 树的深度:3
记父节点是让树的节点记住自己节点的父节点索引位置,可以根据索引查找父节点,查找某个节点的父节点自然是手到擒来了,但是查找某个节点的子节点就略显笨拙,得遍历整个树的存储数组。
5. 树的记子节点实现
与记父节点不同的是,记子节点实现是Node节点记录的是自己子节点的信息,之后利用第一个子节点记录第二个子节点信息、利用第二个子节点记录第三个字节点信息,以此类推。也就是说子节点对象就是记录父节点的子节点信息的。第n个节点记录了第n+1个子节点的信息。代码如下
package dateStructer.tree; import java.util.ArrayList; import java.util.List; public class TreeChildren<E> { /** * 记录子节点对象 * * @author liuyan */ class SonNode<E> { E data; int index; SonNode<E> sonNode; public SonNode(E data, int index, SonNode sonNode) { this.data = data; this.index = index; this.sonNode = sonNode; } @Override public String toString() { return (String) data; } public Node<E> paseNode(){ return new Node<E>(data,index); } } /** * 实际应用的Node对象 * * @author liuyan */ class Node<E> { E data; int index; SonNode firstSonNode; public Node(E data, int index) { this.data = data; this.index = index; } public Node(E data, int index, SonNode sonNode) { this.data = data; this.index = index; this.firstSonNode = sonNode; } @Override public boolean equals(Object obj) { if (((Node) obj).data == this.data && ((Node) obj).index == this.index) return true; return false; } @Override public int hashCode() { return super.hashCode(); } @Override public String toString() { return (String) data; } } // 默认数组大小 private final int DefSize = 150; // 记录节点个数 private int nodeSize; // 父节点对象 private Node<E>[] nodes; public TreeChildren() { nodes = new Node[DefSize]; } public TreeChildren(E e) { nodes = new Node[DefSize]; nodeSize++; nodes[0] = new Node(e, 0); } /** * 为指定节点增加子节点 * * @param e * @param parentNode * @return */ @SuppressWarnings("unchecked") public boolean addNodeByParent(E e, Node<E> parentNode) { for (int i = 0; i < nodes.length; i++) { if (nodes[i] == null) { SonNode sonNode = new SonNode(e, i, null); SonNode lastSonNode = getLastSonNodeByParent(parentNode); if (lastSonNode == null) { parentNode.firstSonNode = sonNode; } else { lastSonNode.sonNode = sonNode; } nodes[i] = sonNode.paseNode(); nodeSize++; return true; } } return false; } /** * 由SonNode到普通Node的转化 * @param sonNode * @return */ public Node<E> paseNode(SonNode<E> sonNode) { for (int i = 0; i < nodeSize; i++) { if (nodes[i] != null && nodes[i].data == sonNode.data && nodes[i].index == sonNode.index) { return nodes[i]; } } return null; } /** * 获得一个父节点的最后子节点对象 * * @param parentNode * @return */ @SuppressWarnings("unchecked") public SonNode getLastSonNodeByParent(Node<E> parentNode) { if (parentNode.firstSonNode == null) { return null; } SonNode sonNodeNow = parentNode.firstSonNode; while (sonNodeNow.sonNode != null) { sonNodeNow = sonNodeNow.sonNode; } return sonNodeNow; } /** * 根据node获得索引 * * @param node * @return */ public int getNodeIndex(Node<E> node) { for (int i = 0; i < nodes.length; i++) { if (nodes[i].equals(node)) { return i; } } return -1; } /** * 判断是否是空树 * * @return */ public boolean isEmpty() { return nodeSize == 0; } /** * 返回树的根节点 * * @return */ public Node<E> getRootNode() { if (nodeSize == 0) { return null; } return nodes[0]; } /** * 根据子节点返回父节点对象 * * @param chileNode * @return */ @SuppressWarnings("unchecked") public Node<E> getParentNodeByChildNode(Node<E> chileNode) { for (int i = 0; i < nodes.length; i++) { if (nodes[i] != null && nodes[i].firstSonNode != null) { SonNode<E> sonNode = nodes[i].firstSonNode; while (sonNode != null) { if (sonNode.data == chileNode.data && sonNode.index == chileNode.index) { return nodes[i]; } sonNode = sonNode.sonNode; } } } return null; } /** * 根据父节点返回父子节点对象集合 * * @param chileNode * @return */ @SuppressWarnings("unchecked") public List<SonNode<E>> getChildsNodeByParentNode(Node<E> parentNode) { if (parentNode == null) { return null; } SonNode<E> sonNodeNow = parentNode.firstSonNode; List<SonNode<E>> list = new ArrayList<SonNode<E>>(); while (sonNodeNow.sonNode != null) { list.add(sonNodeNow); sonNodeNow = sonNodeNow.sonNode; } return list; } /** * 返回指定节点的第index个子节点,第1个代表第一个 * * @param parentNode * @param index * @return */ public SonNode<E> getIndexChildByParentNode(Node<E> parentNode, int index) { if (index == 0) { throw new RuntimeException("没有第0个子节点,从1开始"); } if (parentNode.firstSonNode == null) { return null; } int childCount = 0; SonNode<E> sonNode = parentNode.firstSonNode; while (sonNode != null) { childCount++; if (index == childCount) { return sonNode; } sonNode = sonNode.sonNode; } return null; } /** * 返回节点的深度 * * @return */ public int returnNodeDeep(Node<E> node) { if (node == getRootNode()) { return 1; } else { Node<E> parentNode = getParentNodeByChildNode(node); if (parentNode != null) { return returnNodeDeep(parentNode) + 1; } return 0; } } /** * 返回树的深度 * * @return */ public int returnTreeDeep() { int max = 0; for (int i = 0; i < nodeSize; i++) { int nodeDeep = returnNodeDeep(nodes[i]); if (max < nodeDeep) { max = nodeDeep; } } return max; } @Override public String toString() { StringBuffer str = new StringBuffer("["); for (int i = 0; i < nodeSize; i++) { str.append("[" + nodes[i].data + "],"); } if (nodeSize > 0) { return str.substring(0, str.lastIndexOf(",")) + "]"; } return str.append("]").toString(); } }
这个构想是找到某个节点的子节点比较容易,但是找到某个节点的父节点就相对麻烦一些了。所以使用记子节点还是记父节点,各有利弊。不同场合不同使用。
6. 总结
这次总结了一般树的一般组建内核,下次将对特殊的树二叉树进行学习。一般在程序里面使用树结构进行模型构建使用最多的还是记父节点方式,无论是Java还是JS特效树,都是采用此方式进行树构建的比较多,而记子节点方式相对于记父节点来说思维很不一样。如果一个菜单数据十分巨大,那么一个记子树形菜单所消耗的资源将是巨大的。对不起,因为笔者在本片复习小结中生了一场病。所以学习笔记有些松散,下不为例。祝大家多多锻炼、注意身体。身体真是本钱!身体差却是吃亏啊。
发表评论
-
Web应用单点压力测试调优-第6季-阶段性总结
2014-03-14 12:24 3378阶段性总结 <! ... -
Web应用单点压力测试调优-第5季
2014-03-13 09:32 4131各项配置: my.cnf [clien ... -
Web应用单点压力测试调优-第4季
2014-03-12 14:55 3155调整5-Tomcat的启动JVM参数 首先先启动 ... -
单点网站压力测试调优-第3季
2014-03-11 16:21 3416调整2-调整配置,数据库连接池数量 mysql ... -
Web应用单点压力测试调优-第2季
2014-03-07 16:52 8878并发1000,准备时间1s,让它产生大量的等待请求 ... -
单点网站压力测试调优-第1季
2014-03-07 10:36 3942环境介绍 虚拟机配置 ... -
编程质量提高建议总结1(持续总结)
2014-03-05 19:42 1297编程质量提高建议总结1(持续总结) 1.混淆字母要明显 ... -
关于博客文章内容显示不全的问题
2011-06-14 09:36 2384关于博客文章内容显示不全的问题,我发现有些文章显示内容不全。 ... -
Maven3实战笔记05仓库依赖解析与插件解析
2011-06-07 09:00 34231. Maven仓库依赖解析机 ... -
Apache的对象池化工具commons-pool
2011-05-16 09:21 130951. 前言 当我们的应用中创建一个十分最重量级的 ... -
将Sun的Open Message Queue与Spring集成
2011-05-06 09:01 34851. 前言 基于JMS标准的消息中间件实现的产品 ... -
要不要池化是个艰难的选择(转)-我觉得很生动就转载了下来
2011-05-05 09:50 1567转自http://www.ixpub.net/thre ... -
java.lang.IllegalStateException: STREAM错误的理解(转)
2011-05-04 18:09 13816转自http://dimple.iteye.com/blog/ ... -
Spring3配置声明式事务
2011-05-02 16:52 45491. 配置Spring3声明式事务 在Sprin ... -
Java基础复习笔记11基本排序算法
2011-04-25 13:20 21321. 排序 排序是一个历来都是很多算法家热衷的领 ... -
Java基础复习笔记08数据结构-二叉树和二叉树的遍历
2011-04-22 09:10 25431. 二叉树 一 ... -
Java基础复习笔记06数据结构-队列
2011-04-19 17:25 16841. 队列 队列又是一种比较特殊的线性表,和栈一 ... -
Java基础复习笔记04数据结构-线性表
2011-04-15 14:14 23011. 线性表 线性表是数据结构的一种逻辑结构,其 ... -
Java基础复习笔记03面试、笔试、开发中我们不太注意的陷阱之流程控制、面向对象、异常处理
2011-04-13 09:59 22181. switch语句的用法 有人说:“笔者基础 ... -
Java基础复习笔记03面试、笔试、开发中我们不太注意的陷阱之多线程
2011-04-13 09:51 19641. 什么样的对 ...
相关推荐
### Java基础复习笔记09数据结构-哈夫曼树 #### 1. 哈夫曼树概述 哈夫曼树是一种特殊的二叉树,它在计算机科学领域有着广泛的应用,尤其是在数据压缩方面。哈夫曼树也被称为最优二叉树,其构建原则是使得带权路径...
### Java基础复习笔记10数据结构-排序二叉树 #### 概述 本文档主要介绍了Java中的排序二叉树(Sorted Binary Tree)的基本概念、性质以及如何在Java中实现和操作这种数据结构。排序二叉树是一种重要的数据结构,在...
### Java基础复习笔记08数据结构-二叉树和二叉树的遍历 #### 一、二叉树概述 二叉树(Binary Tree)是一种重要的数据结构,在计算机科学中有广泛的应用。二叉树中的每个节点最多有两个子节点,分别称为左子节点和右...
这份"java_Java_学习笔记.zip"包含了丰富的Java基础知识,对于初学者来说是极好的参考资料。以下是一些主要的知识点概述: 1. **Java简介**: - Java由Sun Microsystems开发,后被Oracle公司收购。 - Java的设计...
标题中的“全套java笔记数据库部分”表明这是一份关于Java编程语言中数据库操作的全面学习资料,涵盖了从基础到进阶的各种主题。描述提到“最新的全套javaEE开发笔记”,暗示了这些笔记可能针对的是Java企业版(Java...
### Java自考复习知识点梳理 #### 一、Java语言基础 **Java语言的特点:** 1. **强类型:** - Java是一种强类型语言,它强制程序员遵守更严格的编程规范,这有助于编译器检测尽可能多的错误。 2. **编译与解释...
【疯狂Java讲义笔记】是针对《疯狂JAVE讲义》这本书的知识点提炼,适合用于复习Java编程。书中涵盖了Java的基础概念、面向对象的理解、数据类型和运算符以及数组等核心内容。 一、Java概述 Java程序在编译后产生与...
### JavaWeb期末复习知识点梳理 #### 第一章:JAVA概述 ...以上内容总结了JavaWeb期末复习所需掌握的核心知识点,从Java语言的基础概念到面向对象的基本原理,旨在帮助学生全面理解和掌握Java编程语言及其应用。
"18天Java学习之经典笔记"是一份专为快速掌握Java基础知识而设计的学习资料,适用于那些希望在短时间内复习、备考或者准备面试的人员。这份笔记涵盖了Java的核心概念,通过18天的学习计划,帮助读者逐步理解并熟练...
面试宝典还可能涵盖并发编程、垃圾收集机制、数据结构与算法、设计模式、UML统一建模语言、Spring MVC框架等内容。在并发编程中,面试者应熟悉线程同步、锁机制、并发集合等。在垃圾收集部分,理解对象的生命周期、...
- 复习上一节所学的Java基础知识。 - 重点复习数据类型和运算符的使用方法。 **机器狂人:** - 使用IDEA或Eclipse等集成开发环境创建新的Java项目。 - 设置项目的基本配置,如编码格式、构建路径等。 **高手之路:...
每个“JAVA复习题20100X.xls”文件很可能是对这些主题的测试或练习,可能包含选择题、填空题、简答题等形式,帮助学习者巩固所学知识。通过解答这些问题,学习者可以评估自己对Java语言的理解程度,并找到需要加强的...
数据结构与算法是计算机科学的基础,对于理解和设计高效的软件系统至关重要。这个压缩包文件很可能包含了一系列关于这个主题的教程、笔记、练习题或者代码示例,帮助学习者深入理解这一领域。 数据结构是组织和存储...
这份文档通常会包含Java编程语言的基础面试问题,可能涵盖变量、数据类型、控制结构(如if-else,switch-case,循环)、类与对象、继承、封装、多态、接口、异常处理、集合框架(如ArrayList、LinkedList、HashMap...
**JavaSE进阶知识点概述** JavaSE(Java Standard Edition)是Java编程语言的基础部分,它提供了用于开发桌面应用程序和服务器端应用的核心库。本压缩包包含的笔记涵盖了JavaSE的一些重要进阶主题,包括抽象类与...
【Java基础概述】 Java是一种广泛使用的面向对象的编程语言,由Sun Microsystems(现已被Oracle公司收购)于1995年发布。它以其跨平台、安全性强和性能优秀等特点受到全球开发者的喜爱。"毕向东javase35天上课笔记...
### MyBatis复习笔记知识点详解 #### 一、MyBatis概述 1. **定义**:MyBatis是一个优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集的...