赞
踩
现象描述
在树的应用中,经常要查询树中的某个结点。在树结点不多(1~10W左右)的情况,可能感受不到查询效率带来的影响,但当树结点增加到数千百万计时,查询算法的效率就直接影响到其它功能的实现。下面是本人关于算法改进的一点思路,和大家探讨。其中为我们系统中应用的算法和这里提出的解决方案提供了实现代码和算法执行效率的对照表,最后提供了另一种改进算法的思路。
缺陷原因分析
对搜索算法有一个共同的要求:遍历最少的结点找出正确的结果。搜索过程的关键点有两个:遍历和匹配。显然,想要提高算法的效率首先应该从这两点入手。在DefaultMutableTreeNode类中提供了树的两种遍历方法分别为:depthFirstEnumeration()和breadthFirstEnumeration()方法。在我们的应用系统中便是用这些遍历方法中的一种将树先展开,然后用查询关键字和展开的树结点匹配。具体实现如下:
首先利用深度优先法把树展开到Enumeration容器中。然后用关键字和Enumeration容器中的元素逐一进行匹配,取得匹配成功的结点保存在查询结果容器中。
public void treeSearch001(DefaultMutableTreeNode root,String searchValue,Vector result){
Object temp=new Object();
//用深度优先算法将树展开。
Enumeration df=root.depthFirstEnumeration();
//与关键字匹配,若匹配成功结果保存到结果集中。
while(df.hasMoreElements()){
temp=df.nextElement();
if(temp.toString().indexOf(searchValue)!=-1)
result.addElement(temp);
}
}
这种方法很容易实现,但从上面的代码中不难发现:在对树进行展开和匹配时实际上相当于对树进行了两次遍历。而且这种算法效率完全依赖于JAVA类库,如果我们能开发自己的遍历和匹配方法,就可以体现出如下好处:
1.直接在遍历中匹配,不需要对树进行两次遍历,提高了一倍的效率。
2.我们可以根据自己的业务特点有针对性的设计出更高效的方法。
解决方案及效果
编写树的深度优先遍历代码,在遍历的过程中用查询关键字和树结点进行匹配,取得匹配成功结点保存到查询结果容器中。搜索过程如图一所示:
图一 解决方案用例图示
实现代码如下:
public void treeSearch002(DefaultMutableTreeNode root,String searchValue,Vector result){
//将根结点与关键字匹配,若匹配成功结果保存到结果集中。
if(root.toString().indexOf(searchValue)!=-1){
result.addElement(root);
}
//深度优先递归。
for(int i=0;i<root.getChildCount();i++){
treeSearch001((DefaultMutableTreeNode)root.getChildAt(i), searchValue,result);
}
}
从理论上来讲上面方法的效率应该是传统方法的二倍,下面我们就来测试一下。测试原理是这样的:在开始执行查询方法的同时启动一个记数线程,方法执行完毕后停止计数。这里的计数并不代表所消耗的时间,但从计数的大小上可以很直观得看到方法效率的高低。表一是我的一次测试结果。第一行是所查询的树结点数(逐渐增多),下面的每个表单元有两个数字,分别是在测试结果中取得的最大值和最小值。(完整的测试代码附于该文章后)
表一 原算法和解决方案的效率对照表
结点数方法 | 27 | 703 | 18279 | 475255 |
原算法耗计数点: | 11017,9672 | 83054,70634 | 4342687,2180966 | 72323533,62166061 |
解决方案耗计数点: | 2076,1316 | 54749,37964 | 1770943,1203532 | 48872244,33080255 |
平均效率倍数: | 6.33 | 1.69 | 2.13 | 1.68 |
算法改进思路
根据记忆的普遍现象,人们在搜索的时候往往都是输入的目标内容的第一或前几个字符。利用这一点就可以将待搜索的数据源划分优先级(例如:用户输入为输入为"a",数据源中"a*"的数据条优先级最高,"*a*"的其次,"*a"的最后)分次将搜索的内容提供给用户。如图,如果用户输入"a",程序就把以a开头的数据放入结果集。而不需要把"ba","ca"也放入结果集。如果用户没有找到结果,那么再把"ba","ca"找出来(这个过程可以通过点击"下一页"触发)。如果用户输入"ab",搜索路径为root[0]->a[1]->"ab"然后把下面的所有数据放入结果集。
图二 改进算法图示
因为这里的搜索树本身是有序的,所以其中的每个结点的位置都是已知的,如"abc"在root[0]->a[1]->ab[2]的位置。按索引检索时间很短。
如果要一次性实现全搜索也是很容易实现的,整棵树结点的位置都是有索引的,可以直接提取。结合多线程的技术,搜索效率更好。
这个算法实现的前提是树的建立过程有序,且消耗的空间也是可观的。
经验总结
查询算法是我们很多软件功能的基础,虽然在iMap目前的应用中还没有出现对查询算法效率要求很高的需求,但如果一个算法影响功能的实现,或是成为系统运行的瓶颈,我们就必须认真考虑该算法的实现问题了。因此在以后的开发过程中应该注意到:
l 法引用简单方便,但库方法但需要分析对效率的影响
l 询、搜索算法的效率,可以根据需要在时间效率和空间效率之间进行权衡
测试代码:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;
import java.util.Date;
import com.ucan.swing.MyTimer;//自建计数器。
import com.ucan.swing.Console; //自建测试GUI框架。
public class TestTree005 extends JFrame{
JTree tree;
DefaultMutableTreeNode root;
String searchValue;
String strTable="abcdefghijklmnopqrstuvwxyz";
//!在这里设置测试树的深度
int depth=3;
Vector result=new Vector();
public TestTree005() {
final JLabel label;
final JTextField text=new JTextField(18);
JButton button=new JButton("Search");
JPanel panel=new JPanel();
//设置树的根结点
root=new DefaultMutableTreeNode("Root");
tree=new JTree(root);
//创建一棵测试树
creatTree(root,depth,0);
Container contentPane=getContentPane();
contentPane.add(new JScrollPane(tree),BorderLayout.CENTER);
label=new JLabel("Search Result :");
contentPane.add(label,BorderLayout.SOUTH);
panel.setLayout(new FlowLayout());
panel.add(text);
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
result.clear();
searchValue=text.getText();
DefaultMutableTreeNode root=(DefaultMutableTreeNode)tree.getModel().getRoot();
//取一个计数器实例
MyTimer date1=new MyTimer();
//设置计数器是否开始计数
date1.setContinueTime(true);
date1.start();
System.out.println(date1.count);
treeSearch001(root,searchValue,result);
//停止计数
date1.setContinueTime(false);
//打印计数结果
System.out.println(date1.count);
label.setText("Search Result :["+treeNodeCount(depth)+"]"+result.toString());
}
});
panel.add(button);
contentPane.add(panel,BorderLayout.NORTH);
}
//方法一:
public void treeSearch001(DefaultMutableTreeNode root,String searchValue,Vector result){
Object temp=new Object();
//用深度优先算法将树展开。
Enumeration df=root.depthFirstEnumeration();
//与关键字匹配,若匹配成功结果保存到结果集中。
while(df.hasMoreElements()){
temp=df.nextElement();
if(temp.toString().indexOf(searchValue)!=-1){
result.addElement(temp);
}
}
}
//方法二:
public void treeSearch002(DefaultMutableTreeNode root,String searchValue,Vector result){
//将根结点与关键字匹配,若匹配成功结果保存到结果集中。
if(root.toString().indexOf(searchValue)!=-1){
result.addElement(root);
}
//深度优先递归。
for(int i=0;i<root.getChildCount();i++){
treeSearch001((DefaultMutableTreeNode)root.getChildAt(i), searchValue,result);
}
}
//递归方法建立测试树。
private void creatTree(DefaultMutableTreeNode root,int depth,int depthCount){
String node;
DefaultMutableTreeNode treeNode;
depthCount++;
if(depthCount>=depth) return;
for(int i=0;i<26;i++){
if(depthCount==1)
node=String.valueOf(strTable.charAt(i));
else
node=root.toString().concat(String.valueOf(strTable.charAt(i)));
treeNode=new DefaultMutableTreeNode(node);
root.add(treeNode);
creatTree(treeNode,depth,depthCount);
depthCount--;
}
}
//计算sum=26~0+26~1+26~2+…+26~(n-1)
private int treeNodeCount(int n){
int nodeCount=0;
if(n==1)
return 1;
else
nodeCount=compute(n-1)+treeNodeCount(n-1);
return nodeCount;
}
//计算result=26~n
private int compute(int n){
if(n==1)
return 26;
else{
return 26* compute(n - 1);
}
}
public static void main(String[] args) {
Console.run(new TestTree005(),500,500);
}
}
package com.ucan.swing;
public class MyTimer extends Thread{
public int count;
public boolean continueTime;
public MyTimer() {
count=0;
continueTime=false;
}
public void run(){
while(getContinueTime()) count++;
}
public void setContinueTime(boolean continueTime){
this.continueTime=continueTime;
}
private boolean getContinueTime(){
return continueTime;
}
}
package com.ucan.swing;
import javax.swing.*;
import java.awt.event.*;
public class Console
{
//Create a title string from the class name:
public static String title(Object o)
{
String title=o.getClass().toString();
//remove the word "class":
if(title.indexOf("class") != -1)
{
title=title.substring(6);
}
return title;
}
public static void setupClosing(JFrame frame)
{
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
}
public static void run(JFrame frame,int width,int height)
{
setupClosing(frame);
frame.setSize(width,height);
frame.setVisible(true);
}
public static void run(JApplet applet,int width,int height)
{
JFrame frame=new JFrame(title(applet));
setupClosing(frame);
frame.getContentPane().add(applet);
frame.setSize(width,height);
applet.init();
applet.start();
frame.setVisible(true);
}
public static void run(JPanel panel,int width,int height)
{
JFrame frame=new JFrame(title(panel));
setupClosing(frame);
frame.getContentPane().add(panel);
frame.setSize(width,height);
frame.setVisible(true);
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。