@Anonimista, another option is to write your own recursive DFS that will do what you need, it is very simple.
I note only that it would have the two typical small disadvantages of recursive algorithms. First, it would be slightly slower. Second, since recursive calls allocate resources in the system stack, in the case of traversals of very large trees, a stack overflow could happen during execution.Do you have any benchmark confirming the first thesis? I am interested because my own measurements usually show the opposite.
I note only that it would have the two typical small disadvantages of recursive algorithms. First, it would be slightly slower. Second, since recursive calls allocate resources in the system stack, in the case of traversals of very large trees, a stack overflow could happen during execution.Do you have any benchmark confirming the first thesis? I am interested because my own measurements usually show the opposite.
As for the second, it doesn't seem to matter much, since the TTreeNode destructor is already recursive.
Sedgewick, Wayne, "Alghorithms", 4th ed. p. 415:That's one of the books I keep at hand and, unsurprisingly, he is right. One thing that should be noted is that, recursion can often be the simplest and most intuitive way of implementing an algorithm but, that doesn't mean it is the best (read, reasonably efficient) way.
"Generally, recursive implementations are a bit easier to verify for correctness; nonrecursive implementations are a bit more efficient."
I have not revealed an absolute truth, but a typical situation:I'm not sure if your post was a result of what I posted but, I agree.
in general recursive alghorithms are slightly slower than iterative ones, because they require as many 'calls with return' as the number of recursive cycles.That's when one has to be very careful. Some solutions can be implemented efficiently and elegantly using a recursive algorithm and, when such a recursive algorithm is restructured to avoid recursion, the implementation, as you pointed out in a recent post, tends to be more complicated.
These types of operations are very expensive for the CPU.There is obviously a cost but, pushing a return address on the stack isn't a very expensive operation but, in many cases, the added complexity resulting from converting a recursive algorithm into an iterative one can be considerable. Personally, if the recursive algorithm performs well, I will forego the very small performance increase of an iterative algorithm because I don't think the small performance gain justifies the added complexity.
I mentioned authoritative sources in support of my thesis. If someone cites to me equally authoritative sources or shows me experimental evidence of the opposite sign, I am happy to change my mind, because I often use recursive algorithms. :DGenerally speaking, what you stated is true. The keyword being "generally".
... I note only that it would have the two typical small disadvantages of recursive algorithms. First, it would be slightly slower. Second, since recursive calls allocate resources in the system stack, in the case of traversals of very large trees, a stack overflow could happen during execution.
is it possible to somehow improve the situation I described above.The answer to that is _very likely_ to be, yes. The performance gain in most cases will be minimal to negligible and, the code to implement an iterative solution will be noticeably more complex. It's simply not worth it.
It seems to me that you agree.In general, yes, I agree with what you've stated.
These types of operations are very expensive for the CPU.There is obviously a cost associated with pushing "items" on a stack but, they really aren't "very expensive".
There is obviously a cost associated with pushing "items" on a stack but, they really aren't "very expensive".Well, the obvious amazes me, since stack/queue are linked list based. You can't improve on that, currently..... :'( 8-)
I'm trying to say that if we want to compare the performance of an iterative and recursive version of some algorithm, these versions should be equivalent, otherwise the comparison does not make sense. Of couse, DepthFirstTraverse does a complete traversal of the tree, without any doubt, but this is a different algorithm.
It seems to me that you agree.In general, yes, I agree with what you've stated.
However, there is one statement you've made that I disagree with, which is:These types of operations are very expensive for the CPU.There is obviously a cost associated with pushing "items" on a stack but, they really aren't "very expensive".
Recursion, as well known, is implemented using stack. Every recursive call allocates a new 'stack frame' on the top of the stack. Each frame contains, among others, following information, for each execution of recursive procedure: the parameters values passed to procedure; the return address of the caller procedure; all the values of local variables of the procedure. When recursion terminates, all these frames are deallocated, in reverse order. These operation are very expensive, since consume space of stack and time of CPU.What you stated above is correct except for the "very expensive" part.
For me, equivalence of implementations means that they perform the same steps in the same sequence. Therefore, I imagine an iterative implementation of DFS in a slightly different way:I'm trying to say that if we want to compare the performance of an iterative and recursive version of some algorithm, these versions should be equivalent, otherwise the comparison does not make sense. Of couse, DepthFirstTraverse does a complete traversal of the tree, without any doubt, but this is a different algorithm.
Indeed in my test I compared a ricorsive DFT algorithm with an iterative one
The algorithm implemented by Leledumbo is not equivalent to DFS, otherwise this thread would not have arisen.Feel free to make the correct implementation if you think mine is wrong, but in my understanding it is correct (you can find similar implementations both in theory and practice, here's one example (http://web.cs.unlv.edu/larmore/Courses/CSC477/bfsDfs.pdf)). DFS does not explicitly say how the node at the same level (siblings) must be visited, left-to-right, right-to-left, even random all are correct as long as once you visit a node and you see that node has children, then you must visit the children first before visiting that node's siblings.
...I'm not sure that the proposed implementation of iterative DFS is optimal...Yes, at least we can get rid of the Visited field in TEnumNode and reduce the number of local variables in the procedure:
When recursion terminates, all these frames are deallocated, in reverse order. These operation are very expensive, since consume space of stack and time of CPU.
Create random tree
Time Elapsed: 3468
GenRandomTree node count=20000000
Recursive DFT
Time Elapsed: 2437
Visited Nodes=20000000
Recursive DFT, rvk
Time Elapsed: 2421
Visited Nodes=20000000
Iterarive DFT
Time Elapsed: 2890
Visited Nodes=20000000
Procedural DFT, custom pile (bk)
Time Elapsed: 218
Visited Nodes=20000000
Procedural DFT _1, TStack (bk)
Time Elapsed: 797
Visited Nodes=20000000
In attachment pgmTreeDepthTrav_2.7z a test program mixing simone and rvk code.Should read :
I found that enabling range checking led to immediate errors or crashes.Ok. In the RPile.Grow, I used pointer subtraction for FillChar. The FillChar is not required so you can comment it out or replace the line with :
or using heaptrc led to immediate errors or crashes, however.This is absolutely normal if you kept the 20'000'000 nodes generation. Every heaptrc GetMem adds an overhead of ~100 bytes per memory allocation, so you get an OutOfMemory error.
@BrunoK, great, but what happens if you compare your DepthFirstTraverseBk with such a recursive procedure?@avk, your last recursive code is the WINNER, congrats !
===================================================================================
I386 -O3 20'000'000 nodes
===================================================================================
Create random tree
Time Elapsed: 3343
GenRandomTree node count=20000000
Recursive DFT
Time Elapsed: 2390
Visited Nodes=20000000
Recursive DFT (avk)
Time Elapsed: 156
Visited Nodes=20000000
Iterarive DFT
Time Elapsed: 2702
Visited Nodes=20000000
Procedural DFT, custom pile (bk)
Time Elapsed: 203
Visited Nodes=20000000
Procedural DFT _1, TStack (bk)
Time Elapsed: 781
Visited Nodes=20000000
===================================================================================
x86_64 -O3 20'000'000 nodes
===================================================================================
Create random tree
Time Elapsed: 5795
GenRandomTree node count=20000000
Recursive DFT
Time Elapsed: 1609
Visited Nodes=20000000
Recursive DFT (avk)
Time Elapsed: 172
Visited Nodes=20000000
Iterarive DFT
Time Elapsed: 1890
Visited Nodes=20000000
Procedural DFT, custom pile (bk)
Time Elapsed: 234
Visited Nodes=20000000
Procedural DFT _1, TStack (bk) skipped because it is catastrophicaly slow in x86_64
===================================================================================
x86_64 -O3 40'000'000 nodes
===================================================================================
Create random tree
Time Elapsed: 11466
GenRandomTree node count=40000000
Recursive DFT
Time Elapsed: 3327
Visited Nodes=40000000
Recursive DFT (avk)
Time Elapsed: 360
Visited Nodes=40000000
Iterarive DFT fails for too many nodes
Procedural DFT, custom pile (bk)
Time Elapsed: 406
Visited Nodes=40000000
Procedural DFT _1, TStack (bk) skipped because it is catastrophicaly slow in x86_64