一次性加载树的难题 ( 积分: 100 )

  • 主题发起人 主题发起人 LJH1978
  • 开始时间 开始时间
L

LJH1978

Unregistered / Unconfirmed
GUEST, unregistred user!
以下方法能极速地将数据库关联到树,但有个问题,它的ID,PID必须是数值,如果一些字符型的编码如J01...等就不能采用这种方法,有什么办法修改以下代码,使之能适用于数字/字符等情况?


procedure TfrmTestSpeedMain.LoadTreeOf_7;
var
List :TStringList;
iIndex, iParentID, iID, iFail :integer;
sCaption :string;
Node : TTreeNode;
begin
// 循环一次加到树上,加子时查树,使用内存列表 非常快
// 数据集必须是排序的,否则子节点加不完整
{
算法出处说明: 根据数据表的内容生成TreeView树状结构,通常的做法就是从顶级开始,
然后逐项递归查询遍历生成。这种方法在实现上容易做到,也很容易想到,但是效率比
较低,因为数据库的检索(SQL语句需要解释执行,而且是对数据库文件进行操作)还是比
较耗时的,尤其是树的层次较多,节点较多的情况。这里我要介绍的方法是以空间换取
时间,只进行一次数据库检索,提取出全部数据,然后一次生成TreeView树状结构。
通过SQL语句,让返回的记录按照父节点ID、节点ID进行排序,这样保证每次当前要添加
的节点记录的父节点都已经添加到了TreeView树中,剩下的工作就是如何在TreeView树中
找到父节点。这里我采用了一个排序的TStringList列表,通过排序列表采用二分查找的
快速性能,就能够很快地查找到当前要添加节点的父节点,从而插入到TreeView树的正确
位置。
}

List := TStringList.Create;
try
List.Sorted := True; // 内存数据 排序加快查询

AdoQuery.Sql.Text := 'select top ' + IntToStr(fLoadCount) +
' FID,FParentID,FCaption from '+ TreeTable;
if chkOrderBy.Checked then
AdoQuery.Sql.Text := AdoQuery.Sql.Text + ' order by FParentID,FCaption';
AdoQuery.Active := True;
iFail := 0;
while not AdoQuery.Eof do
begin
iParentID := AdoQuery.FieldByName('FParentID').AsInteger;
iID := AdoQuery.FieldByName('FID').AsInteger;
sCaption := AdoQuery.FieldByName('FCaption').AsString;
if iParentID = 0 then // 根节点
begin
Node := tv.Items.AddObject(nil,sCaption ,Pointer(0));
Node.StateIndex := iID;
List.AddObject(IntToStr(iID),Node);
end
else // 子节点
begin
iIndex := List.IndexOf(IntToStr(iParentID)); // 查询当前记录的父是否加到节点上了
//ToDo: 排序了,应父节点都加上去了吧?
if iIndex <> -1 then
begin
Node := tv.Items.AddChildObject(TTreeNode(List.Objects[iIndex]),
sCaption ,Pointer(iID));
Node.StateIndex := iID;
List.AddObject(IntToStr(iID),Node);
end
else
Inc(iFail);
end;
if Self.Tag <> 1 then break;
lblLog.Caption := '正在加载... ' + IntToStr((tv.Items.Count+1)*100 div fLoadCount) + ' %';
Application.ProcessMessages;

AdoQuery.Next;
end;

if iFail >0 then
mLog.Lines.Add(' LoadTreeOf_7 存在不能连接的父子节点 数量:' +
IntToStr(iFail));
finally
List.Free;
end;
end;
 
表结构(编码的长度要按照1,3,5,7的格式做,这样就是说第一类可以分为99个小类,在这里关键是把编码的长度做为TREEVIEW的分类标准,长度是1的是长度是3父类,长度3是长度5的父类,依次类推,如果99个小类不够就用1,4,9这样的长度做,当然下面的CASE就要相应更改
编码 说明
1 一类
101 一类一小类
102 一类二小类
2 二类
201 二类一小类
程序里
var
node1,node2,node3,node4:Ttreenode;
begin
with adoquery1,TreeView1,Items do
begin
adoquery1.sql.clear;
adoquery1.sql.text:='select length(编码) as level,说明 from 表 order by 编码';
adoquery1.open;
AddChild()
while not eof do
begin
case fieldbyname('level').asinteger of
1: node1:= AddChild(nil,fieldbyname('说明').asstring);
3: node2:= AddChild(node1,fieldbyname('说明').asstring);
5: node3:= AddChild(node2,fieldbyname('说明').asstring);
7: node4:= AddChild(node3,fieldbyname('说明').asstring);
end;
end;
end;
end;
大致过程是这样的,有些地方你调整一下就行了
 
纠正一错误:' order by FParentID,FCaption'应该是: ' order by FParentID,FID'

boy2002cn的方法显然不是楼主要的,用id、pid的最大好处就是无限层次、无限节点个数,虽然我认为这点在实际应用中未必都用的上。如果基于这点考虑,boy2002cn的方法是值得推荐的,比如编码间隔设成1、6、11...,这样,字符型编码设置成足够宽,虽然最终也是有限制,但一般的应用也就够了(层次过千、万,一个节点下子节点过千、万的树我认为基本已经没有查看的价值了,要在上面找到一个节点只能通过代码查询)。

其实不论是数字还是字符,要产生一棵排序的树,其id必定是要能够排序的,使用字符id,在你的代码中,除了涉及到id转换到数字型的部分(Node.StateIndex := iID;)不适应外,我觉得应该没有什么问题。

无限层次的树型结构的快速加载和调整,一直是比较麻烦的事情,如果你使用两个排序的数字字段来记录id、pid的方法来加载树,还不如用我推荐的坐标记录法( http://www.delphibbs.com/keylife/iblog_show.asp?xid=6087 ),这是效率最高的加载方法了,但是如果涉及到调整树结构,那在效率上,就存在些问题,前些日子想到一个方法解决,但是也不完善。
 
无限层次的树型结构的快速加载和调整,一直是比较简单的事情;
不是麻烦!
楼上的打错了;我纠正一下!
 
好啊,那就请教了,反正我是不知道。
 
这个要用到递归函数才行做到。
E-Mail、MSN:Delphi2005@163.com
 
清新空气

讲来听听?
 
递归方法适合于小数据量很少层次的树的建立,如果分支太多,层数太多,数据量又相当大的时候,递归的效率是相当低下的.
一个变通的处理方法,建树时只考虑第一层,即只建立第一层树结点,当用户点击某个分支结点时,再去建立该结点的下一层树,如此类推. 此种方法虽然没有把一棵树一次性迅速建立,但是运行结果和效率却是用户可以接受的.
递归算法看似简练,但只适用于建小树,不适用于建大树. 你去问问用户,谁愿意显示一棵树要等待几十秒钟以上?
 
www.DELPHIBOX.com上有个按不同方式建村的程式,比较好, 可以参考一下。。。

包括,按层建树,递归建树等。。。。。
 
www.DELPHIBOX.com上有个按不同方式建村的程式,比较好, 可以参考一下。。。

包括,按层建树,递归建树等。。。。。


我的代码便来自其中最快的代码,速度还不错,让人满意,最大的问题就是它的要装ID和PID搞成数值型,如果能用字符型就OK啦
 

Similar threads

S
回复
0
查看
3K
SUNSTONE的Delphi笔记
S
S
回复
0
查看
2K
SUNSTONE的Delphi笔记
S
I
回复
0
查看
598
import
I
I
回复
0
查看
503
import
I
后退
顶部