预排序遍历树算法(非递归无限极分类算法)

多层数据结构估计所有的web开发者估计都不会陌生,各种软件的分类都是基于多层结构来设计的。

下面是一个典型的多层数据结构示意图:

点击查看原图

相关创建数据语句:
CREATE TABLE category(
category_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20NOT NULL,
parent INT DEFAULT NULL);


INSERT INTO category
VALUES(1,'ELECTRONICS',NULL),(2,'TELEVISIONS',1),(3,'TUBE',2),
(4,'LCD',2),(5,'PLASMA',2),(6,'PORTABLE ELECTRONICS',1),
(7,'MP3 PLAYERS',6),(8,'FLASH',7),
(9,'CD PLAYERS',6),(10,'2 WAY RADIOS',6);

SELECT * FROM category ORDER BY category_id;

在这种数据结构中,各层之间通过字段 parent 来形成邻接表,我们查询某些层级的关系的时候一般都是通过递归的方式,遍历某个层级关系的SQL的查询次数会顺着层级的增加,想想在层级有20的时候,根据某个底层节点取它到顶层节点的查询次数吧。

为了解决这个问题,人们想出了嵌套集模型(The Nested Set Model),请看下图:

点击查看原图

上图依然是表现的与图一相同的层级关系,但是却更换了一种表现形式 下面是新的关系表和数据(关系和数据与之前相同,但是表结构不一样):



CREATE TABLE nested_category (
category_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20NOT NULL,
lft INT NOT NULL,
rgt INT NOT NULL
);


INSERT INTO nested_category
VALUES(1,'ELECTRONICS',1,20),(2,'TELEVISIONS',2,9),(3,'TUBE',3,4),
(4,'LCD',5,6),(5,'PLASMA',7,8),(6,'PORTABLE ELECTRONICS',10,19),
(7,'MP3 PLAYERS',11,14),(8,'FLASH',12,13),
(9,'CD PLAYERS',15,16),(10,'2 WAY RADIOS',17,18);


SELECT * FROM nested_category ORDER BY category_id;

这里将 left,right 修改为 lft,rgt因为这两个词在MYSQL中属于关键字 下面我们将插入的数据标识在图上: 点击查看原图

同样,我们将数据标识在原来的结构上:

点击查看原图


怎么样,是不是很明确了

下面使我自己标定一种形式,方便理解

[1
      [2
           [3 4] 
           [5 6] 
           [7 8]
      9] 
      [10
           [11
                 [12 13]
           14]
           [15 16]
           [17 18]
      19]
20]

遍历整个树,查询子集 条件:左边 > 父级L, 右边 < 父级R

SELECT node.name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND parent.name = 'ELECTRONICS'
ORDER BY node.lft;

+----------------------+
| name                 |
+----------------------+
| ELECTRONICS          |
| TELEVISIONS          |
| TUBE                 |
| LCD                  |
| PLASMA               |
| PORTABLE ELECTRONICS |
| MP3 PLAYERS          |
| FLASH                |
| CD PLAYERS           |
| 2 WAY RADIOS         |
+----------------------+

- 查询所有无分支的节点 条件:右边 = 左边L + 1

SELECT name
FROM nested_category
WHERE rgt = lft + 1;

- 查询某个字节点到根节点的路径

SELECT parent.name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.name = 'FLASH'
ORDER BY parent.lft;


SELECT node.name, (COUNT(parent.name- 1AS depth
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;

- 查询子节点的深度
SELECT node.name, (COUNT(parent.name- (sub_tree.depth + 1)) AS depth
FROM nested_category AS node,
    nested_category AS parent,
    nested_category AS sub_parent,
    (
        SELECT node.name, (COUNT(parent.name- 1AS depth
        FROM nested_category AS node,
        nested_category AS parent
        WHERE node.lft BETWEEN parent.lft AND parent.rgt
        AND node.name = 'PORTABLE ELECTRONICS'
        GROUP BY node.name
        ORDER BY node.lft
    )AS sub_tree
WHERE node.lft BETWEEN parent.lft AND parent.rgt
    AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt
    AND sub_parent.name = sub_tree.name
GROUP BY node.name
ORDER BY node.lft;

 

- 插入新节点
算法详解: 
1.所有分类 左边和右边的值 > 插入节点的左边节点记录的右值 的全部 + 2
2.插入节点 左值 = 插入位置左边节点记录的右值 + 1, 右值 = 插入位置左边节点记录的右值 + 2
例子:
在 R = 9(L8, R9)与 L = 10(L10,R11) 节点之间插入一个新节点
那么所有 左值 和 右值 > 9 的节点的左值和右值需要 + 2
例如新节点右边的节点(L10,R11)左值右值都需要 + 2 那么插入后的新值为 L12 R13
新节点的左值为 9 + 1 = 10 右值为 9 + 2 = 11
SQL语句实现
LOCK TABLE nested_category WRITE;
SELECT @myRight := rgt FROM nested_category
WHERE name = 'TELEVISIONS';
UPDATE nested_category SET rgt = rgt + 2 WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft + 2 WHERE lft > @myRight;
INSERT INTO nested_category(name, lft, rgtVALUES('GAME CONSOLES', @myRight + 1, @myRight + 2);
UNLOCK TABLES;

- 删除新节点
删除节点的算法与添加一个节点的算法相反

删除一个没有子节点的节点
LOCK TABLE nested_category WRITE;
SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1
FROM nested_category
WHERE name = 'GAME CONSOLES';
DELETE FROM nested_category WHERE lft BETWEEN @myLeft AND @myRight;
UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight;
UNLOCK TABLES;

删除一个分支节点和它所有的子节点
LOCK TABLE nested_category WRITE;
SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1
FROM nested_category
WHERE name = 'MP3 PLAYERS';
DELETE FROM nested_category WHERE lft BETWEEN @myLeft AND @myRight;
UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight;
UNLOCK TABLES;


删除一个节点后移动其字节点到
LOCK TABLE nested_category WRITE;
SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1
FROM nested_category
WHERE name = 'PORTABLE ELECTRONICS';
DELETE FROM nested_category WHERE lft = @myLeft;
UPDATE nested_category SET rgt = rgt - 1, lft = lft - 1 WHERE lft BETWEEN @myLeft AND @myRight;
UPDATE nested_category SET rgt = rgt - 2 WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft - 2 WHERE lft > @myRight;
UNLOCK TABLES;


总结:

预排序遍历树算法的核心就是牺牲了写的性能来换取读取的性能

在你的开发的应用遇到此类问题的时(读压力 > 写压力),尝试下使用预排序遍历树算法来提高你的程序的性能吧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
无限级(Java递归) 2007-02-08 10:26 这几天,用java写了一个无限极,递归写的,可能代码不够简洁,性能不够好,不过也算是练习,这几天再不断改进。前面几个小图标的判断,搞死我了。 package com.nickol.servlet; import java.io.IOException; import java.io.PrintWriter; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.nickol.utility.DB; public class category extends HttpServlet { /** * The doGet method of the servlet. * * This method is called when a form has its tag value method equals to get. * * @param request the request send by the client to the server * @param response the response send by the server to the client * @throws ServletException if an error occurred * @throws IOException if an error occurred */ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("utf-8"); response.setContentType("text/html"); PrintWriter out = response.getWriter(); out .println(""); out.println(""); out.println(" Category" + "" + "body{font-size:12px;}" + "" + "" + ""); out.println(" "); out.println(showCategory(0,0,new ArrayList(),"0")); out.println(" "); out.println(""); out.flush(); out.close(); } public String showCategory(int i,int n,ArrayList frontIcon,String countCurrent){ int countChild = 0; n++; String webContent = new String(); ArrayList temp = new ArrayList(); try{ Connection conn = DB.GetConn(); PreparedStatement ps = DB.GetPs("select * from category where pid = ?", conn); ps.setInt(1, i); ResultSet rs = DB.GetRs(ps); if(n==1){ if(rs.next()){ webContent += "";//插入结尾的减号 temp.add(new Integer(0)); } webContent += " ";//插入站点图标 webContent += rs.getString("cname"); webContent += "\n"; webContent += showCategory(Integer.parseInt(rs.getString("cid")),n,temp,"0"); } if(n==2){ webContent += "\n"; }else{ webContent += "\n"; } while(rs.next()){ for(int k=0;k<frontIcon.size();k++){ int iconStatic = ((Integer)frontIcon.get(k)).intValue(); if(iconStatic == 0){ webContent += "";//插入空白 }else if(iconStatic == 1){ webContent += "";//插入竖线 } } if(rs.isLast()){ if(checkChild(Integer.parseInt(rs.getString("cid")))){ webContent += "";//插入结尾的减号 temp = (ArrayList)frontIcon.clone(); temp.add(new Integer(0)); }else{ webContent += "";//插入结尾的直角 } }else{ if(checkChild(Integer.parseInt(rs.getString("cid")))){ webContent += "";//插入未结尾的减号 temp = (ArrayList)frontIcon.clone(); temp.add(new Integer(1)); }else{ webContent += "";//插入三叉线 } } if(checkChild(Integer.parseInt(rs.getString("cid")))){ webContent += " ";//插入文件夹图标 }else{ webContent += " ";//插入文件图标 } webContent += rs.getString("cname"); webContent += "\n"; webContent += showCategory(Integer.parseInt(rs.getString("cid")),n,temp,countCurrent+countChild); countChild++; } webContent += "\n"; DB.CloseRs(rs); DB.ClosePs(ps); DB.CloseConn(conn); }catch(Exception e){ e.printStackTrace(); } return webContent; } public boolean checkChild(int i){ boolean child = false; try{ Connection conn = DB.GetConn(); PreparedStatement ps = DB.GetPs("select * from category where pid = ?", conn); ps.setInt(1, i); ResultSet rs = DB.GetRs(ps); if(rs.next()){ child = true; } DB.CloseRs(rs); DB.ClosePs(ps); DB.CloseConn(conn); }catch(Exception e){ e.printStackTrace(); } return child; } } --------------------------------------------------------------------- tree.js文件 function changeState(countCurrent,countChild){ var object = document.getElementById("level" + countCurrent + countChild); if(object.style.display=='none'){ object.style.display='block'; }else{ object.style.display='none'; } var cursor = document.getElementById("cursor" + countCurrent + countChild); if(cursor.src.indexOf("images/tree_minus.gif")>=0) {cursor.src="images/tree_plus.gif";} else if(cursor.src.indexOf("images/tree_minusbottom.gif")>=0) {cursor.src="images/tree_plusbottom.gif";} else if(cursor.src.indexOf("images/tree_plus.gif")>=0) {cursor.src="images/tree_minus.gif";} else {cursor.src="images/tree_minusbottom.gif";} var folder = document.getElementById("folder" + countCurrent + countChild); if(folder.src.indexOf("images/icon_folder_channel_normal.gif")>=0){ folder.src = "images/icon_folder_channel_open.gif"; }else{ folder.src = "images/icon_folder_channel_normal.gif"; }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值