`
zhanglun1225
  • 浏览: 56319 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

java树形结构 算法

    博客分类:
  • java
阅读更多

最近看到一个有意思的树形结构,为每个节点添加了lftrgt两个属性。这样查找该节点的子节点、查找该节点所有父节点,就不用去递归查询,只需要用betweenand语句就可以实现。下面以创建一个栏目树为例,以下是我的理解。

  一般来讲,我们创建栏目树的时候,大多只需要一个外键parentid来区分该节点属于哪个父节点。数据库的设计如下图:
这样一来,

1.查找该节点的所有子节点,则需要采用sql的递归语句:

 

select * from tableName connect by prior id=sj_parent_id start with  id=1

 

 (oracle 写法,mysql目前不支持,如果mysql想查找树形,可以利用存储过程).

2.查找该节点的父节点的sql递归语句:

 

select * from tableName connect by prior sj_parent_id =id start with  id=1

 

 如果数据量过大或者层次太多,那么这样操作是会影响性能的。

 

  “任何树形结构都可以用二叉树来表示”。其实我们创建的栏目树就是一个简型的二叉树。根据数据结构里面二叉树的遍历,再稍微修改下,将数据库设计如下图所示:

 


这样我们查找该节点的所有子节点,则只需要查找idlftrgt之间的所有节点即可。

1.查找该节点的所有子节点的Sql语句为:

<!--EndFragment-->

 

<!--EndFragment-->

select * from tb_subject s,tb_subject t where s.lft between t.lft and t.rgt and t.id=1

 

2.查找该节点的所有父节点的sql语句为:

<!--EndFragment-->

select s.* from tb_subject s,tb_subject t where s.lft<t.lft and (s.rgt-s.lft)>1 and s.rgt>t.rgt and t.id=1

 

 下面来详细讲解下,怎么用java来实现这种算法。

<!--EndFragment-->

 1. 新增节点

 新增节点比较简单,基本步骤为

 A. 查找当前插入节点的父节点的lft

 B. 将树形中所有lftrgt节点大于父节点左值的节点都+2

 C. 将父节点左值+1,左值+2分别作为当前节点的lftrgt

 因为项目中采用的是struts2+hibernate3.2+spring2.5的框架,代码如下:

<!--EndFragment-->

public boolean onSave(Object entity, Serializable id, Object[] state,
			String[] propertyNames, Type[] types) {
		if (entity instanceof HibernateTree) {
			HibernateTree tree = (HibernateTree) entity;
			Long parentId = tree.getParentId();
			String beanName = tree.getClass().getName();
			Session session = getSession();
			FlushMode model = session.getFlushMode();
			session.setFlushMode(FlushMode.MANUAL);
			Integer myPosition = new Integer(0);
			//查找父节点的左值
			if (parentId != null) {
				String hql = "select b.lft from " + beanName
						+ " b where b.id=:pid";
				myPosition = (Integer) session.createQuery(hql).setLong("pid",
						parentId).uniqueResult();
			}
			//将树形结构中所有大于父节点左值的右节点+2
			String hql1 = "update " + beanName
					+ " b set b.rgt = b.rgt + 2 WHERE b.rgt > :myPosition";
			//将树形结构中所有大于父节点左值的左节点+2
			String hql2 = "update " + beanName
					+ " b set b.lft = b.lft + 2 WHERE b.lft > :myPosition";
			if (!StringUtils.isBlank(tree.getTreeCondition())) {
				hql1 += " and (" + tree.getTreeCondition() + ")";
				hql2 += " and (" + tree.getTreeCondition() + ")";
			}
			session.createQuery(hql1).setInteger("myPosition", myPosition)
					.executeUpdate();
			session.createQuery(hql2).setInteger("myPosition", myPosition)
					.executeUpdate();
			session.setFlushMode(model);
			//定位自己的左值(父节点左值+1)和右值(父节点左值+2)
			for (int i = 0; i < propertyNames.length; i++) {
				if (propertyNames[i].equals(HibernateTree.LFT)) {
					state[i] = myPosition + 1;
				}
				if (propertyNames[i].equals(HibernateTree.RGT)) {
					state[i] = myPosition + 2;
				}

			}
			return true;
		}
		return false;
	}

 

 2. 修改节点

  修改的时候比较麻烦,具体步骤为:

  在修改lftrgt之前,当前节点的父节点id已经改变

a. 查出当前节点的左右节点(nodelftnodergt),并nodergt-nodelft+1 = span,获取父节点的左节点parentlft

b. 将所有大于parentlftlft(左节点)rgt(右节点)的值+span

c. 查找当前节点的左右节点(nodelftnodergt),并parentlft-nodelft+1 = offset

d. 将所有lft(左节点) between nodelft and nodergt的值+offset

e. 将所有大于nodergtlft(左节点)rgt(右节点)的值-span

 Java代码如下:

<!--EndFragment-->

public void updateParent(HibernateTree tree, HibernateTree preParent,
			HibernateTree curParent) {
		if (preParent != null && preParent != null
				&& !preParent.equals(curParent)) {
			String beanName = tree.getClass().getName();
			// 获得节点位置
			String hql = "select b.lft,b.rgt from " + beanName
					+ " b where b.id=:id";
			Object[] position = (Object[]) super.createQuery(hql).setLong(
					"id", tree.getId()).uniqueResult();
			System.out.println(hql+"| id = "+tree.getId()); 
			int nodeLft = ((Number) position[0]).intValue();
			int nodeRgt = ((Number) position[1]).intValue();
			int span = nodeRgt - nodeLft + 1;
			// 获得当前父节点左位置
			hql = "select b.lft from " + beanName + " b where b.id=:id";
			int parentLft = ((Number) super.createQuery(hql).setLong("id",
					curParent.getId()).uniqueResult()).intValue();

			System.out.println(hql+"| id = "+curParent.getId());
			// 先空出位置
			String hql1 = "update " + beanName + " b set b.rgt = b.rgt + "
					+ span + " WHERE b.rgt > :parentLft";
			String hql2 = "update " + beanName + " b set b.lft = b.lft + "
					+ span + " WHERE b.lft > :parentLft";
			if (!StringUtils.isBlank(tree.getTreeCondition())) {
				hql1 += " and (" + tree.getTreeCondition() + ")";
				hql2 += " and (" + tree.getTreeCondition() + ")";
			}
			super.createQuery(hql1).setInteger("parentLft", parentLft)
					.executeUpdate();
			super.createQuery(hql2).setInteger("parentLft", parentLft)
					.executeUpdate();

			System.out.println(hql1+"| parentLft = "+parentLft);
			System.out.println(hql2+"| parentLft = "+parentLft);
			
			// 再调整自己
			hql = "select b.lft,b.rgt from " + beanName + " b where b.id=:id";
			position = (Object[]) super.createQuery(hql).setLong("id",
					tree.getId()).uniqueResult();
			System.out.println(hql+"| id = "+tree.getId());
			nodeLft = ((Number) position[0]).intValue();
			nodeRgt = ((Number) position[1]).intValue();
			int offset = parentLft - nodeLft + 1;
			hql = "update "
					+ beanName
					+ " b set b.lft=b.lft+:offset, b.rgt=b.rgt+:offset WHERE b.lft between :nodeLft and :nodeRgt";
			if (!StringUtils.isBlank(tree.getTreeCondition())) {
				hql += " and (" + tree.getTreeCondition() + ")";
			}
			super.createQuery(hql).setParameter("offset", offset)
					.setParameter("nodeLft", nodeLft).setParameter("nodeRgt",
							nodeRgt).executeUpdate();
			System.out.println(hql+"| offset = "+offset+" | nodelft = "+nodeLft+" | nodergt = "+ nodeRgt);
			// 最后删除(清空位置)
			hql1 = "update " + beanName + " b set b.rgt = b.rgt - " + span
					+ " WHERE b.rgt > :nodeRgt";
			hql2 = "update " + beanName + " b set b.lft = b.lft - " + span
					+ " WHERE b.lft > :nodeRgt";
			if (tree.getTreeCondition() != null) {
				hql1 += " and (" + tree.getTreeCondition() + ")";
				hql2 += " and (" + tree.getTreeCondition() + ")";
			}
			super.createQuery(hql1).setParameter("nodeRgt", nodeRgt)
					.executeUpdate();
			super.createQuery(hql2).setParameter("nodeRgt", nodeRgt)
					.executeUpdate();
			System.out.println(hql1+"| nodeRgt = "+nodeRgt);
			System.out.println(hql2+"| nodeRgt = "+nodeRgt);
			
		}
	}

 

 3. 删除节点

 删除节点也比较简单,具体步骤为:

 A. 查找要删除节点的lft

 B. 将所有lftrgt大于删除节点lft值的都-2

 Java代码如下:

<!--EndFragment-->

 

 

<!--EndFragment-->
public void onDelete(Object entity, Serializable id, Object[] state,
			String[] propertyNames, Type[] types) {
		if (entity instanceof HibernateTree) {
			HibernateTree tree = (HibernateTree) entity;
			String beanName = tree.getClass().getName();
			Session session = getSession();
			FlushMode model = session.getFlushMode();
			session.setFlushMode(FlushMode.MANUAL);
		//查找要删除的节点的左值
			String hql = "select b.lft from " + beanName + " b where b.id=:id";
			Integer myPosition = (Integer) session.createQuery(hql).setLong(
					"id", tree.getId()).uniqueResult();
//将所有大于删除节点左值的rgt都-2
			String hql1 = "update " + beanName
					+ " b set b.rgt = b.rgt - 2 WHERE b.rgt > :myPosition";
//将所有大于删除节点左值的lft都-2
			String hql2 = "update " + beanName
					+ " b set b.lft = b.lft - 2 WHERE b.lft > :myPosition";
			if (tree.getTreeCondition() != null) {
				hql1 += " and (" + tree.getTreeCondition() + ")";
				hql2 += " and (" + tree.getTreeCondition() + ")";
			}
			session.createQuery(hql1).setInteger("myPosition", myPosition)
					.executeUpdate();
			session.createQuery(hql2).setInteger("myPosition", myPosition)
					.executeUpdate();
			session.setFlushMode(model);
		}
	}

 
 

 

 

 

<!--EndFragment-->

 

 

<!--EndFragment-->

 

<!--EndFragment-->

 

<!--EndFragment-->

 

 

<!--EndFragment-->

 

 

 

 

<!--EndFragment-->
  • 大小: 8.5 KB
  • 大小: 12.6 KB
分享到:
评论
43 楼 woweiwokuang 2012-04-16  
woweiwokuang 写道
   我找这你的做了,修改节点的时候有问题的.   我特意用图画了一下..


不好意思. 是我搞错了.. 但是这些数据如何类维护呢? .  我现在是用parent 来建立了一个树,但是已经要几百条数据了.  如果在原数据的基础上添加这两个编号呢?
42 楼 woweiwokuang 2012-04-13  
   我找这你的做了,修改节点的时候有问题的.   我特意用图画了一下..
41 楼 waainli 2012-04-06  
可以支持多个子节点吗?
40 楼 zzpxingfu 2010-06-08  
看起来还不错 只是还没有研究过
Java不错算法
39 楼 xyflash 2010-06-08  
我要是想显示树形列表 sql 又该如何书写 ?


如:
根目录
  目录1
  目录2
    目录2.1
38 楼 bloodwolf_china 2010-03-18  
我的算法是用节点深度加parentID解决
01
  0101
      010101
  0102
02
  0201
      020101
中等数据规模,100级以下

 
37 楼 cqwww 2010-03-03  
加一个字段orders ,平级排序,记录上级ID,记录子级所有ID, 一条SQL搞定。不用规递
36 楼 jv520jv 2010-03-03  
受用了..
35 楼 zhanglun1225 2010-03-02  
andsofish 写道
新的问题

            1 A 14
        /        \
    2  B 9      10 C 13
    /    \       \ 
  3 D 6  7 E 8    11 F 12 
  /
4 G 5

我要删除B,按照增加算法,删除后为
            1 A 12
        /        \
              8 C 11
    /    \       \ 
  1 D 4  5 E 6    9 F 10 
  /
2 G 3 

不知是否可以删除树枝?
从sql查询来看,还是满足条件的,但是二叉树被破坏了。



这个不是纯粹的算法,只是为了方便我们项目的一个工具,在项目中栏目树不能删除树枝,所以暂时未加考虑。。呵呵。不好意思。
不过您提出的问题,值得思考。。
34 楼 zhanglun1225 2010-03-02  
andsofish 写道
zhanglun1225 写道
andsofish 写道
还有一个问题:你的id是自增长编号么?
如何保证,between lft 和 rgt 不会包含兄弟的子节点啊?

          1
      /    \
     2      3
    / \    /
   4   5  6 
  /
7

2节点的lft和rgt分别是?


这个id是数据库的主键,不一定是自动增长的,根据自己选择的数据库类型来定,只要唯一就可以了。
“如何保证,between lft 和 rgt 不会包含兄弟的子节点啊?”这个问题,查询的sql语句我已经给出来了,你可以测试一下。

            1 A 14
        /        \
    2  B 9      10 C 13
    /    \       \ 
  3 D 6  7 E 8    11 F 12 
  /
4 G 5

其中A、B、C等字母代表节点,而数字分别代表lft和rgt,比如A的lft=1,rgt=14


谢谢,我基本看懂了

新的问题

            1 A 14
        /        \
    2  B 9      10 C 13
    /    \       \ 
  3 D 6  7 E 8    11 F 12 
  /
4 G 5

我要为G增加一个兄弟,按照增加算法,增加后为
            1 A 16
        /        \
    2  B 11      12 C 15
    /    \       \ 
  3 D 8  9 E 10    13 F 14 
  /    \
6 G 7 4 X 5 

遍历路径似乎不太对了吧。 
也许还是我对LFT何RGT的由来有点疑问导致的,到底这两个值是如何计算出来的?
是通过前序遍历算法得来的么?


这个遍历路径是不准确了。不过,这个算法是在新闻发布栏目树里面用到的,只是方便N层栏目的查询,而得到的数据,我们可以根据别的字段来进行排序,达到想要的结果。如果再去考虑遍历的路径,个人觉得有点多余了。
不过您提到的问题,值得思考。谢谢
33 楼 quysc 2010-03-02  
            1 A 14
        /        \
    2  B 9      10 C 13
    /    \       \ 
  3 D 6  7 E 8    11 F 12 
  /
4 G 5

这样的树形结构是怎么生成的?
以你的算法生成树应该是
           1 A 6
        /        \
    4 B 4     2 C 3
才对   
32 楼 曾经de迷茫 2010-03-01  
left / right 如何维护?
31 楼 andsofish 2010-03-01  
新的问题

            1 A 14
        /        \
    2  B 9      10 C 13
    /    \       \ 
  3 D 6  7 E 8    11 F 12 
  /
4 G 5

我要删除B,按照增加算法,删除后为
            1 A 12
        /        \
              8 C 11
    /    \       \ 
  1 D 4  5 E 6    9 F 10 
  /
2 G 3 

不知是否可以删除树枝?
从sql查询来看,还是满足条件的,但是二叉树被破坏了。
30 楼 andsofish 2010-03-01  
zhanglun1225 写道
andsofish 写道
还有一个问题:你的id是自增长编号么?
如何保证,between lft 和 rgt 不会包含兄弟的子节点啊?

          1
      /    \
     2      3
    / \    /
   4   5  6 
  /
7

2节点的lft和rgt分别是?


这个id是数据库的主键,不一定是自动增长的,根据自己选择的数据库类型来定,只要唯一就可以了。
“如何保证,between lft 和 rgt 不会包含兄弟的子节点啊?”这个问题,查询的sql语句我已经给出来了,你可以测试一下。

            1 A 14
        /        \
    2  B 9      10 C 13
    /    \       \ 
  3 D 6  7 E 8    11 F 12 
  /
4 G 5

其中A、B、C等字母代表节点,而数字分别代表lft和rgt,比如A的lft=1,rgt=14


谢谢,我基本看懂了

新的问题

            1 A 14
        /        \
    2  B 9      10 C 13
    /    \       \ 
  3 D 6  7 E 8    11 F 12 
  /
4 G 5

我要为G增加一个兄弟,按照增加算法,增加后为
            1 A 16
        /        \
    2  B 11      12 C 15
    /    \       \ 
  3 D 8  9 E 10    13 F 14 
  /    \
6 G 7 4 X 5 

遍历路径似乎不太对了吧。 
也许还是我对LFT何RGT的由来有点疑问导致的,到底这两个值是如何计算出来的?
是通过前序遍历算法得来的么?
29 楼 andsofish 2010-03-01  
抛出异常的爱 写道
askyuan 写道
给一个树形结构的例子,包括前台jsp+后台java代码+数据库,写完整咯

呸....


28 楼 抛出异常的爱 2010-03-01  
askyuan 写道
给一个树形结构的例子,包括前台jsp+后台java代码+数据库,写完整咯

呸....
27 楼 hcqenjoy 2010-03-01  
night_stalker 写道
参见 Pro Activerecord Database (2007) 102 页
现在已经有数十种 acts_as_nested_set 和 acts_as_tree 了 ……

Pro Activerecord Database 这个本书没找到 能提供更多信息吗
26 楼 askyuan 2010-02-28  
给一个树形结构的例子,包括前台jsp+后台java代码+数据库,写完整咯
25 楼 night_stalker 2010-02-27  
参见 Pro Activerecord Database (2007) 102 页
现在已经有数十种 acts_as_nested_set 和 acts_as_tree 了 ……
24 楼 抛出异常的爱 2010-02-27  
fxyc 写道
我也用过,查询是很快,但是添加和删除就会变得有点复杂。

不如把数据全抓出来重作

相关推荐

    基于JAVA建立树形结构的算法优化.pdf

    基于JAVA建立树形结构的算法优化.pdf

    Java递归算法构造JSON树形结构

    Java递归算法构造JSON树形结构,Java递归算法构造JSON树形结构Java递归算法构造JSON树形结构

    Java创建树形结构算法实例代码

    本篇文章主要介绍了Java创建树形结构算法实例代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。

    基于JAVA建立树形结构的算法优化.zip

    基于JAVA建立树形结构的算法优化

    java数据结构排序算法之树形选择排序详解

    主要介绍了java数据结构排序算法之树形选择排序,结合具体实例形式分析了java树形选择排序的原理、实现技巧与相关注意事项,需要的朋友可以参考下

    递归形成树形结构.txt

    java递归形成树形结构

    树形结构算法

    比较详细的树形无递归技术文档,可以选择参考。

    使用递归算法结合数据库解析成Java树形结构的代码解析

    主要介绍了使用递归算法结合数据库解析成Java树形结构的代码解析的相关资料,需要的朋友可以参考下

    尚硅谷Java数据结构与java算法(Java数据结构与算法).zip

    逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何...

    JAVA_SQL递归树形

    JAVA_SQL递归树形,用递归算法结合数据库对J2EE实现树结构

    Java经典算法教程:深度优先搜索(DFS)算法

    无论您是初学者还是有经验的Java开发者,通过学习DFS算法,您将获得解决各种图形和树形结构问题的能力。 深度优先搜索(DFS)算法是解决图形和树形结构问题的重要工具,具有广泛的应用。本教程将深入介绍DFS算法的...

    java算法与数据结构

    3.树形结构 (1)二叉树的性质及存储结构 (2)二叉树的遍历 (3)线索二叉树 (4)树的存储结构 (5)树、二叉树与森林的转化方法 (6)哈夫曼树 (7)二叉排序树及平衡化 (8)堆排序树 (9)B-树 4.图形结构 ...

    2019尚硅谷java版数据结构算法学习.zip

    逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何...

    数据结构算法Java实现。关于Java《数据结构算法》核心技术学习积累的例子,是初学者及核心技术巩固的最佳实践。.zip

    逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何...

    java数据结构算法.zip

    逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何...

    java 数据结构算法.zip

    逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何...

    java数据结构与算法.zip

    逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何...

    java数据结构+算法.zip

    逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何...

    【尚硅谷】数据结构与算法(Java数据结构与算法).zip

    逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何...

    java数据结构与算法学习.zip

    逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何...

Global site tag (gtag.js) - Google Analytics