`
suhuanzheng7784877
  • 浏览: 704618 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
Ff8d036b-05a9-33b5-828a-2633bb68b7e6
读金庸故事,品程序人生
浏览量:47756
社区版块
存档分类
最新评论

Java基础复习笔记07数据结构-树的概述

阅读更多

1.       树的概念

如果线性表、栈、队列是线性结构(一维结构)的话,那么树就代表着一种非线性的、复杂的二维结构,何为线性结构、何为二维结构?就是11的一条直线,每个元素都是这条线上的节点、节点之间只知道1VS1的、前后关系。而二维结构就是一个面,1N的一个面,这个面上的每一个元素都对应着多个此面上其他的元素。树就是指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特效树,都是采用此方式进行树构建的比较多,而记子节点方式相对于记父节点来说思维很不一样。如果一个菜单数据十分巨大,那么一个记子树形菜单所消耗的资源将是巨大的。对不起,因为笔者在本片复习小结中生了一场病。所以学习笔记有些松散,下不为例。祝大家多多锻炼、注意身体。身体真是本钱!身体差却是吃亏啊。

  • 大小: 18.7 KB
分享到:
评论

相关推荐

    Java基础复习笔记09数据结构-哈夫曼树

    ### Java基础复习笔记09数据结构-哈夫曼树 #### 1. 哈夫曼树概述 哈夫曼树是一种特殊的二叉树,它在计算机科学领域有着广泛的应用,尤其是在数据压缩方面。哈夫曼树也被称为最优二叉树,其构建原则是使得带权路径...

    Java基础复习笔记10数据结构-排序二叉树

    ### Java基础复习笔记10数据结构-排序二叉树 #### 概述 本文档主要介绍了Java中的排序二叉树(Sorted Binary Tree)的基本概念、性质以及如何在Java中实现和操作这种数据结构。排序二叉树是一种重要的数据结构,在...

    Java基础复习笔记08数据结构-二叉树和二叉树的遍历

    ### Java基础复习笔记08数据结构-二叉树和二叉树的遍历 #### 一、二叉树概述 二叉树(Binary Tree)是一种重要的数据结构,在计算机科学中有广泛的应用。二叉树中的每个节点最多有两个子节点,分别称为左子节点和右...

    java_Java_学习笔记.zip

    这份"java_Java_学习笔记.zip"包含了丰富的Java基础知识,对于初学者来说是极好的参考资料。以下是一些主要的知识点概述: 1. **Java简介**: - Java由Sun Microsystems开发,后被Oracle公司收购。 - Java的设计...

    全套java笔记数据库部分

    标题中的“全套java笔记数据库部分”表明这是一份关于Java编程语言中数据库操作的全面学习资料,涵盖了从基础到进阶的各种主题。描述提到“最新的全套javaEE开发笔记”,暗示了这些笔记可能针对的是Java企业版(Java...

    4747-java自考复习资料串讲笔记完整版无水印.doc

    ### Java自考复习知识点梳理 #### 一、Java语言基础 **Java语言的特点:** 1. **强类型:** - Java是一种强类型语言,它强制程序员遵守更严格的编程规范,这有助于编译器检测尽可能多的错误。 2. **编译与解释...

    疯狂java讲义笔记

    【疯狂Java讲义笔记】是针对《疯狂JAVE讲义》这本书的知识点提炼,适合用于复习Java编程。书中涵盖了Java的基础概念、面向对象的理解、数据类型和运算符以及数组等核心内容。 一、Java概述 Java程序在编译后产生与...

    javaweb期末复习笔记

    ### JavaWeb期末复习知识点梳理 #### 第一章:JAVA概述 ...以上内容总结了JavaWeb期末复习所需掌握的核心知识点,从Java语言的基础概念到面向对象的基本原理,旨在帮助学生全面理解和掌握Java编程语言及其应用。

    18天java学习之经典笔记

    "18天Java学习之经典笔记"是一份专为快速掌握Java基础知识而设计的学习资料,适用于那些希望在短时间内复习、备考或者准备面试的人员。这份笔记涵盖了Java的核心概念,通过18天的学习计划,帮助读者逐步理解并熟练...

    java面试大全-黑马

    面试宝典还可能涵盖并发编程、垃圾收集机制、数据结构与算法、设计模式、UML统一建模语言、Spring MVC框架等内容。在并发编程中,面试者应熟悉线程同步、锁机制、并发集合等。在垃圾收集部分,理解对象的生命周期、...

    Java实验指导书 2009

    - 复习上一节所学的Java基础知识。 - 重点复习数据类型和运算符的使用方法。 **机器狂人:** - 使用IDEA或Eclipse等集成开发环境创建新的Java项目。 - 设置项目的基本配置,如编码格式、构建路径等。 **高手之路:...

    java study note

    每个“JAVA复习题20100X.xls”文件很可能是对这些主题的测试或练习,可能包含选择题、填空题、简答题等形式,帮助学习者巩固所学知识。通过解答这些问题,学习者可以评估自己对Java语言的理解程度,并找到需要加强的...

    数据结构与算法

    数据结构与算法是计算机科学的基础,对于理解和设计高效的软件系统至关重要。这个压缩包文件很可能包含了一系列关于这个主题的教程、笔记、练习题或者代码示例,帮助学习者深入理解这一领域。 数据结构是组织和存储...

    JAVA基础面试大全.doc corejavanetbook.doc jsp技术大全.pdf

    这份文档通常会包含Java编程语言的基础面试问题,可能涵盖变量、数据类型、控制结构(如if-else,switch-case,循环)、类与对象、继承、封装、多态、接口、异常处理、集合框架(如ArrayList、LinkedList、HashMap...

    JavaSE进阶每日复习与笔记.zip

    **JavaSE进阶知识点概述** JavaSE(Java Standard Edition)是Java编程语言的基础部分,它提供了用于开发桌面应用程序和服务器端应用的核心库。本压缩包包含的笔记涵盖了JavaSE的一些重要进阶主题,包括抽象类与...

    毕向东javase35天上课笔记

    【Java基础概述】 Java是一种广泛使用的面向对象的编程语言,由Sun Microsystems(现已被Oracle公司收购)于1995年发布。它以其跨平台、安全性强和性能优秀等特点受到全球开发者的喜爱。"毕向东javase35天上课笔记...

    mybatis复习笔记

    ### MyBatis复习笔记知识点详解 #### 一、MyBatis概述 1. **定义**:MyBatis是一个优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集的...

Global site tag (gtag.js) - Google Analytics