首先,介绍一下树状结构在DB中的存储。
使用二维表,如下图,存储树状结构:
现在,我们的目标是想要把这一树状结构表示成:
由上图可以看出它们之间含有一种层级关系,查看源代码,如下:
现在,算法的思路是,先将树状结构按照list的顺序排列出来,这个顺序其实就是去掉了UL和LI标签的顺序,如:
要实现这个顺序其实很简单。
再来看看我们的树
abcdef其实就是这颗树的先序遍历的结果。
那么,如果现在我们只有一张二维表,那么我们要怎样生成这个先序遍历的结果呢?(不使用递归)
观察这张二维表:
node到fatherNode是一对一映射,而fatherNode到node之间则是一对多映射。
也就是说我们可能不太容易知道某个节点的兄弟节点(需要先确定父节点,再通过父节点query兄弟节点),但是确定某个节点的子节点是非常容易的。
如,节点a的所有子节点,只要query fatherNode=a即可。
因此,我们可以使用广搜的思想来解决这个问题。
广搜就是先遍历离根最近的第一层节点,将这些节点放入queue中,然后从queue头不断取出节点,再遍历这个节点的所有子节点,并将其子节点放入queue的队尾,依次下去,知道所有节点都遍历到为止。
拿我们的树来说,就是
但是,如果直接用广搜这个算法的话,我们得到的序列是:
abdcef
与我们所期待的不同。
所以,改进一下该算法。
为了存储我们的结构,新建一个列表。现在,我们就有两个列表了,一个是广搜用的queue,另一个是存储结果的list。
当广搜每摘掉一个头元素,并将该元素的子节点放入queue中时,就在存储的list中的相应位置插入这些子节点们。那么相应位置是哪里呢?就是被摘掉的这个头元素。
例如,现在queue的形态为 “a” ,那么现在要做的操作是把queue的头,a元素摘掉,并且将其子节点"b d"加入queue中,与此同时,在list中查找到a的位置,并将“b d”添加到其后面。此时,queue的形态变为"b d",而list则变为"a b d"。重复此步骤,现在摘掉queue中的头元素,即b,并且将其子节点(c)放入queue尾部,同时在list的b元素后面添加b的子节点(c),此时,queue的形态变为“d c”,而list的形态变为“a b c d”。依照此方式一直进行到广搜结束,即进行到queue为空。
该算法结束时,就可以得到 a b c d e f 这一我们希望见到的序列了。
但是,这一序列丢失了一项很重要的信息,就是层次信息。
所以,再对算法进行一下改进,我们引入一个结构体
<node, level>
使用 level 属性来记录某个节点所处的层次。
那么level这个属性在什么时候进行赋值呢?
在将某个元素的子元素插入list中时。
如何赋值呢?
这个问题很简单,因为是为某个元素插入子元素,因此,被插入的这些元素的level相当于其父节点的 level + 1
所以,list最后就变成了这样一个结构:
node list的顺序 “abcdef”,以及其level属性都是必须的。
现在,我们既知道了节点的顺序,也知道了节点中各个点的关系,因此就可以构建又ul和li表示的树状结构了。
我们再观察一下源代码:
发现,
(1)当level增大时,就会出现一个<ul>标签。
(2)当level减小时,就会出现</ul>标签,而</ul>标签的数量与level减小的程度有关,即减小的level是N,就应该出现N个</ul>标签。
(3)对于每一个list中的item,都由一个<li></li>标签对包围。
由上述规则,我们就可以写出如下程序:
Stack<String> stack = new Stack<String>(); String item = "</ul>"; String res = ""; ArrayList<SightLevelPair> list = this.sightTree.getList(); int lastLevel = 0; int currLevel = 0; if(list.size() != 0){ Iterator<SightLevelPair> iter = list.iterator(); while(iter.hasNext()){ SightLevelPair pair = iter.next(); currLevel = pair.getLevel(); if(currLevel > lastLevel){ res += "<ul>"; stack.push(item); }else if(currLevel < lastLevel){ int delta = lastLevel - currLevel; while(delta > 0){ res += stack.pop(); --delta; } } res += "<li>" + pair.getSight().getSightName() + "</li>"; lastLevel = currLevel; } while(0 != stack.size()){ res += stack.pop(); } }
可以看到程序中有一个stack,它是用来存储“</ul>”字串的。每当出现一个level增加时,就在最终在字串后加入一个“<ul>”,同时,将一个“</ul>”压入stack中,并且在遇到level减少时,将相应数量的"</ul>"抛出。
结果还是很理想的:
生成这个树用了自定义标签,即定义了一个SimpleTagSupport的子类,用来处理自定义标签。
制作自定义标签的方法【在此】。