穿线树实现无限级分类

分类,分组在Web应用中到处可见,理想的原型设计,总是会遇到各种各样的新的更新需求,而来自客户的需求变更(客户简单的一句加一个下级分类),可能导致的是项目重大重构。为什么我们不从一开始就把分类做成树形,可无限扩展呢?

最简单的树形分类扩展其实很好实现,一个记录中加一个parent字段指向它的父结点就行,但这样做有很多问题实现起来很复杂,有的甚至需要用到递归来实现,对编程和数据库压力都很大,如:

1. 一个节点下有多少个子孙结点(注意是子孙)?

2. 从根到本节点的路径如何?


很幸运的是,如何你不是野路子出身,则一定学过数据结构。就应该记得在树的那一章中一个概念称做【穿线树】,它特别适应我们WEB中分类、分组这样可能出现无限层级,读取多而插入少的应用场景。结合穿线树的概念,我们改造一下,在数据训中实现无限级树形分类。

1. 树的结构

 
 
穿线树实现无限级分类_第1张图片

图中节点: 字母为节点内容,第一个数字为节点ID,第二个数字为节点父ID,通过父ID的指向,我们可以构造出无限分级的树。

2. 树的穿线

对上面已生成的树,我们给各个节点加上LFT和RGT属性,其取值规则为:

1. LFT为自己的前一个兄弟的RGT+1,如果自己是长子,则为父节点LFT+1

2. RGT为自己的最小的儿子的RGT+1,如果没有子结点,则为自己的LFT+1

穿完线后的树样子如下(节点后面新增的数字分别为LFT和RGT,原来的ID,父ID用灰色以免干扰),注意,各节点的LFT,RGT与节点的ID,父ID没有任何关系!

穿线树实现无限级分类_第2张图片

3. 树的访问:将上述树的各个节点保存到数据库中,之后可以:

    1) 选出所有叶节点(即没有子结点)

           select * from tree where RGT=LFT+1

    2)  获取本节点的所有子节点

           select * from tree where pid=me.id

    3)  获取本节点所有子孙节点

           select * from tree where LET>me.LFT and RGT

    4)  获取本节点的直系祖先(到根的路径)

           select * from tree where LFTme.RGT order by LFT desc

    5)获取我的所有兄弟们(从大到小)

           select * from tree where pid=m.pid order by LFT

    6)获取我的哥哥(我前一个节点),弟弟同理

           select * from tree where pid=me.pid and GRT=me.LFT-1

4. 树的增加、删除与移动, 穿线树利于查询,无限级都可以用简单的SQL语句来实现,但为此付出的代价是新增、删除和移动节点相当麻烦。直接上数据库结构与JFinal代码如下:

    1)数据库结构

CREATE TABLE category(
        id INT NOT NULL AUTO_INCREMENT,
        name VARCHAR(200) NOT NULL,
        father INT DEFAULT '1',
        lft INT,
        rgt INT,
        moving INT DEFAULT '0' NOT NULL,
        PRIMARY KEY (id)
    )  ENGINE=MyISAM DEFAULT CHARSET=utf8
    2) 新增、删除、移动

    public void jsTreeCreate() {
        int rgt = Db.queryInt("select rgt from category where id=?", getParaToInt("id"));
        Db.update("update category set rgt=rgt+2 where rgt>=?", rgt);
        Db.update("update category set lft=lft+2 where lft>=?", rgt);
        new Category().set("father", getParaToInt("id")).set("name", getPara("text")).set("lft", rgt).set("rgt", rgt + 1).save();
        final int id = Db.queryInt("select max(id) from category");
        CacheKit.removeAll("Memory1000");
        renderJson(new HashMap() {{
            put("id", id);
        }});
    }

    public void jsTreeDelete() {
        Record record = Db.findFirst("select lft,rgt from category where id=?", getParaToInt("id"));
        int lft = record.getInt("lft");
        int rgt = record.getInt("rgt");
        Db.update("delete from category where lft>=? and rgt<=?", lft, rgt);
        Db.update("update category set lft=lft-? where lft>?", rgt - lft + 1, rgt);
        Db.update("update category set rgt=rgt-? where rgt>?", rgt - lft + 1, rgt);
        CacheKit.removeAll("Memory1000");
        renderText("ok");
    }

    public void jsTreeMove() {
        //参数:jsTreeMove?id=76&parent=72&position=0
        // 1.标记
        Record record = Db.findFirst("select lft,rgt from category where id=?", getParaToInt("id"));
        int srcLeft = record.getInt("lft");
        int srcRight = record.getInt("rgt");
        Db.update("update category set moving=1 where lft>=? and rgt<=?", srcLeft, srcRight);
        // 2.逻辑删除
        Db.update("update category set lft=lft-? where lft>?", srcRight - srcLeft + 1, srcRight);
        Db.update("update category set rgt=rgt-? where rgt>?", srcRight - srcLeft + 1, srcRight);
        // 3.找到插入点
        Category father = Category.dao.findById(getParaToInt("parent"));
        int dstLeft = father.getInt("lft") + 1;
        List brother = Db.find("select * from category where moving=0 and  father=? order by lft", father.getInt("id"));
        if (brother.size() > 0 && getParaToInt("position") > 0) {
            dstLeft = brother.get(getParaToInt("position") - 1).getInt("rgt") + 1;
        }
        // 3.空出位置
        Db.update("update category set rgt=rgt+? where moving=0 and rgt>=?", srcRight - srcLeft + 1, dstLeft);
        Db.update("update category set lft=lft+? where moving=0 and lft>=?", srcRight - srcLeft + 1, dstLeft);
        // 4.逻辑插入
        Db.update("update category set lft=lft+?,rgt=rgt+?,moving=0 where moving=1", dstLeft - srcLeft, dstLeft - srcLeft);
        Db.update("update category set father=? where id=?", father.getInt("id"), getParaToInt("id"));
        CacheKit.removeAll("Memory1000");
        renderText("ok");
    }
    上述代码是与jsTree进行树的展示和编辑


欢迎同行批评指正





你可能感兴趣的:(穿线树实现无限级分类)