我正在编写一个使用树结构的应用程序,所以当然我有一些递归方法会迭代树的每个节点并做一些事情。问题是有时需要一段时间,我宁愿向用户显示某种进度条,而不是程序停止响应一段时间。
如果我正在迭代一个平面列表,我知道列表中有多少项目开始,因此很容易跟踪循环所在的数字并相应地更新进度条。
但是使用递归方法迭代树结构时,我不一定知道树在开始时有多少个节点。我应该首先递归地读取树并在运行实际的递归方法之前计算所有节点,这样做我想做什么吗?或者可能只是在树中添加或删除节点时跟踪运行总计?有更好的选择吗?
答案 0 :(得分:3)
此问题的常见方法是显示正在更改的进度条,但不会更新实际百分比。例如,Firefox中的小加载图标就是一个旋转的东西 - 你知道它正在做某些东西,但你不知道要花多长时间。也许只是这样做并添加一条消息“这可能需要一些时间,具体取决于有多少数据...”
答案 1 :(得分:1)
你可以做几件事。您可以调整数据结构,以便每个节点存储以该节点为根的树的大小。除此之外:
如果树通常很大,那么仅仅确定树大小的单独运行在性能方面可能不是一个好主意(特别是如果树不完全适合缓存)。
如果树通常非常平衡,那么在处理了总共 n 子树的 k 子树之后,你已经遍历 k / n * 100 百分比的节点。您可以将此知识用作衡量进展的指标。
答案 2 :(得分:1)
如何使用一个状态标签来显示一个描述符,这个描述符对于正在执行循环的useras有意义...
例如,如果我在组织结构图中循环,我知道我的最终用户熟悉组织结构图中的大多数人,所以我会使用人名。举个例子,如果我向报告给Joe的人报告,他向Sue报告,当我的记录被处理时,标签会说像......
Currently processing Sue\Joe\Bob\David
因此,您更新每个节点上的状态标签的文本。只需确保在更改标签文本后调用Application.DoEvents()以便屏幕更新。如果你真的想要显示你的距离,进度条会更好,但这是一个在类似情况下对我有用的选项。它提供了一些关于正在发生的事情的反馈,用户体验才是最重要的。
然而,有一点需要注意的是,更新标签文本并调用Application.DoEvents()实际上会减慢处理速度,但通常会得到回报,因为用户可以看到正在发生的事情并知道该程序不只是“冻结”。答案 3 :(得分:1)
遇到同样的问题,如果您不知道有多少项需要处理,我认为您不能给用户准确的百分比。相反,文本状态消息可能会有所帮助。
假设您能够在树的每个级别告诉您有多少个对象(这应该是可能的,因为它是一个简单的计数),当您的消息显示时,您的消息会显示出类似的内容。处理第一个对象,第1级有50个对象:
处理:Level1 - 1/50
一旦递归调用它,并开始查看当前第1级对象的第2级,该消息将展开以添加第二级状态:
处理:Level1 - 1/50,Level2 - 1/25
当您继续循环给定级别上的对象时,属于该级别的输出消息部分将会增加。因此对于第2级的第二个对象,你得到:
处理:Level1 - 1/50,Level2 - 2/25
完成当前第1级对象的第二级后,删除该消息的最后一部分并弹回到调用级别,然后转到该级别的第二个对象:
处理:Level1 - 2/50
当您在树上上下移动时,您继续添加和删除消息部分,同时看到级别1的部分肯定会慢慢上升。它没有给出确切的剩余数量,但肯定会给用户一个大概。
如果您的树很深,此消息可能会很长。可能有一种更清洁的方法来做到这一点。
另外,我不会一直编辑邮件。我会保留一个单例(如果进度指示由几个线程更新),内部的级别列表及其状态,只是ToString()它以保持清洁。
最后,根据上述概念添加和删除子分支可能会很简单。肯定看起来比消息更容易呈现。
答案 4 :(得分:1)
我认为添加“已处理的项目”计数通常比仅具有来回进度条的沙漏更可取。
最好,如OP所建议的那样,使树容器在添加或删除项时进行必要的簿记是报告进度百分比的一个不错的选择(可能使用包装器类来执行此操作,特别是如果要存储以特定方式处理树时可能会修剪的与分支有关的信息。
如果这不是一个选择,那么下面是我使用的一种算法,该算法对于合理地均匀分布的树效果很好(否则效果很差!)。
ProcessTree最初将被称为ProcessTree(tree,0,0,100)。对于更简洁的代码,可以将itemsProcessed和percentDone参数移至成员变量。如果您的树允许您在不获取项目的情况下获得子计数,则可以省略子收集位:
Sub ProcessTree(node As Tree, ByRef itemsProcessed As Long, ByRef percentDone As Double, percentThisItem As Double)
Dim oChild As Tree
Dim oChildCollection As Collection
Dim percentPerChild As Double
ProcessNode(node)
itemsProcessed = itemsProcessed + 1
ReportProgress(itemsProcessed, percentDone)
Set oChildCollection = New Collection
For Each oChild In node.Children
oChildCollection.Add oChild
Next
If oChildCollection.count > 0 Then
percentPerChild = percentThisItem / oChildCollection.count
For Each oChild In oChildCollection
ProcessTree oChild, itemsProcessed, percentPerChild, percentDone
Next
Else
percentDone = percentDone + percentThisItem
ReportProgress(itemsProcessed, percentDone)
End If
End Sub
答案 5 :(得分:0)
良好的进度条将基于总节点数(通过递归计数)。一个烦人但可能更有效的进度条可能是随着递归的每一步增加MaxValue(使你的进度变得更小)。这些方法可以通过在开始方法之前进行粗略估计并在您进行更新时进行组合。
我认为微软的方法是使用从开始到结束的循环进度条。
答案 6 :(得分:0)
如果树遍历的成本比实际操作的成本低,则值得预先计算进度数据。
如前所述,对所有节点进行计数,然后跟踪应用操作时已完成的节点数。
足以满足99%的情况。需要注意的是,如果操作正在进行一些修剪(跳过不必要的节点等),则节点数不是进度的准确描述,因为跳转的节点对计数没有帮助。因此,完成后该条可能会从60%跳到100%。 在下面的示例中,该过程将节点跳转到B
以下 A
/ \
/ \
(B) F
/|\ /|\
C D E G H I
进度条将指示:
[Node] [True Progress] [Displayed]
A 10% 10%
B 20% 20%
F 60% 30%
G 70% 40%
H 80% 50%
I 90% 60%
- 100% 100%
您可以在初始设置期间放置地标。跟踪列表中的前200个项目。当您击中200时,放开列表上的所有其他对象(以返回100个项目)。现在,跟踪遇到的每个其他节点。当您遇到的节点总数达到400时,该列表将增长回200个对象。您再次放开了彼此,但是从这一点开始,请跟踪4个对象中的1个,等等。
这样,地标列表将在100/200个节点之间振荡。完全解析树后,列表上可能会有约120个对象,均匀分布在树上。现在您可以开始操作了。遇到节点时,检查它是否为列表。如果是,则可以将进度条快速启动到该元素的位置(如果元素为#84,则进度为84/120 = 70%
该操作可能会跳过某些节点,但是进度最终会遇到一些里程碑,因此进度将跳回适当的进度比率。
所有数字都是可调整的,因此可以增加界标列表的大小以提高精度/反应性。
使用相同的示例,列出5个地标
Node Landmarks Steps Comments
A [A----] 1 -
B [AB---] 1 -
A C [ABC--] 1 -
/ \ D [ABCD-] 1 -
/ \ E [ABCDE] 1 -
(B) F F [BDF--] 2 Cleanup of A&C&E
/|\ /|\ G [BDF--] 2 G is skipped
C D E G H I H [BDFH-] 2 -
I [BDFH-] 2 Done
我们获得了5个地标的列表,每个地标占20%
现在在执行操作时(完全跳过B),进度条将指示:
[Node] [True Progress] [Displayed]
A 10% 0%
B 20% 20%
F 60% 60%
G 70% 60%
H 80% 80%
I 90% 80%
- 100% 100%
可以跟踪两个连续地标的最低公共祖先(LCA)。 如果操作退出LCA,则可以推断其进度已跳至下一个地标,因此即使跳过了地标#85,也可以提供一些进度信息。 行为在进入和退出节点之间发生变化:您注册进入地标,然后退出LCA
Node Landmarks LCA Steps Comments
A [A----] [----] 1 -
B [AB---] [A---] 1 A is LCA of A&B
A C [ABC--] [AB--] 1 B is LCA of B&C
/ \ D [ABCD-] [ABB-] 1 B is LCA of C&D
/ \ E [ABCDE] [ABBB] 1 B is LCA of D&E
(B) F F [BDF--] [BA--] 2 Cleanup, recompute LCAs
/|\ /|\ G [BDF--] [BA--] 2 Skipped
C D E G H I H [BDFH-] [BAF-] 2 F is LCA of F&H
I [BDFHI] [BAFF] 2 Should be skipped, is Registered as last node of the tree (F is LCA of H&I)
现在在执行操作时(完全跳过B),进度条将指示:
[Node] [True Progress] [Displayed] [Tracked ] [Tracked] [Comments]
[Landmark] [ LCA ]
->A 10% 0% B - Tracking landmark B...
->B 20% 20% D B Found Tracked Landmark B, Now tracking D (LCA of D,B is B)
(B) 20% 20% D B (Process skips Nodes C/D/E)
<-B 60% 40% F A Exiting LCA B, must have jumped over Landmark D, now tracking F
A 60% 40% F A -
->F 60% 60% H F Entering Landmark F, now tracking H (LCA of F,H is F)
->G 70% 60% H F -
->H 80% 80% I F Found Tracked Landmark H, now tracking I (LCA if H, I is F)
->I 90% 100% Found Tracked Landmark I (last item), operation should be complete