英文:
How to eliminate this type of recursion?
问题
这比简单的左递归或尾递归要复杂一些。所以我想知道如何消除这种递归。如下所示,我已经保留了自己的堆栈,所以函数不需要参数或返回值。然而,它仍然在某个特定级别上(或下)调用自身,我想将其转换为循环,但是我已经为此头疼了一段时间。
这是一个简化的测试案例,用printf("在第n级做一些事情")消息替换了所有的"真正逻辑"。这是用Go语言编写的,但是这个问题适用于大多数语言。使用循环和goto是完全可以接受的(但是我尝试过,它变得复杂、难以控制,似乎无法工作);然而,应该避免使用额外的辅助函数。我想我应该将其转换为某种简单的状态机,但是...哪种呢?;)
至于实用性,这将以每秒约2000万次的速度运行(堆栈深度可以从1增加到25)。在这种情况下,维护自己的堆栈肯定比函数调用堆栈更稳定/更快。(这个函数中没有其他函数调用,只有计算。)而且,不会产生垃圾=不会进行垃圾回收。
所以开始吧:
func testRecursion() {
var root *TMyTreeNode = makeSomeDeepTreeStructure()
// rl: 当前递归级别
// ml: 最大递归级别
var rl, ml = 0, root.MaxDepth
// node: "堆栈"
var node = make([]*TMyTreeNode, ml+1)
// 递归和非递归/迭代测试函数:
var walkNodeRec, walkNodeIt func()
walkNodeIt = func() {
log.Panicf("在这里输入你的迭代/非递归的想法")
}
walkNodeRec = func() {
log.Printf("进入级别 %v", rl)
if (node[rl].Level == ml) || (node[rl].ChildNodes == nil) {
log.Printf("退出级别 %v", rl)
return
}
log.Printf("前置操作级别 %v", rl)
for i := 0; i < 3; i++ {
switch i {
case 0:
log.Printf("前置情况 %v.%v", rl, i)
node[rl+1] = node[rl].ChildNodes[rl+i]
rl++
walkNodeRec()
rl--
log.Printf("后置情况 %v.%v", rl, i)
case 1:
log.Printf("前置情况 %v.%v", rl, i)
node[rl+1] = node[rl].ChildNodes[rl+i]
rl++
walkNodeRec()
rl--
log.Printf("后置情况 %v.%v", rl, i)
case 2:
log.Printf("前置情况 %v.%v", rl, i)
node[rl+1] = node[rl].ChildNodes[rl+i]
rl++
walkNodeRec()
rl--
log.Printf("后置情况 %v.%v", rl, i)
}
}
}
// 递归测试作为参考:
if true {
rl, node[0] = 0, root
log.Printf("\n\n=========>递归 ML=%v:", ml)
walkNodeRec()
}
// 非递归测试,输出应该相同
if true {
rl, node[0] = 0, root
log.Printf("\n\n=========>迭代 ML=%v:", ml)
walkNodeIt()
}
}
更新 - 经过一些讨论和进一步思考:
我刚刚编写了以下伪代码,理论上应该能做到我需要的:
curLevel = 0
for {
cn = nextsibling(curLevel, coords)
lastnode[curlevel] = cn
if cn < 8 {
if isleaf {
process()
} else {
curLevel++
}
} else if curLevel == 0 {
break
} else {
curLevel--
}
}
当然,填写适用于我的自定义用例的nextsibling()部分将是棘手的。但是作为一种消除内部递归同时保持我所需的深度优先遍历顺序的一般解决方案,这个大致的概述应该以某种形式实现。
英文:
This is a bit more intricate than a simple left-recursion or tail-call recursion. So I'm wondering how I can eliminate this kind of recursion. I'm already keeping my own stack as you can see below, so the function needs to no params or return values. However, it's still calling itself up (or down) to a certain level and I want to turn this into a loop, but been scratching my head over this for some time now.
Here's the simplified test case, replacing all "real logic" with printf("dostuff at level #n") messages. This is in Go but the problem is applicable to most languages. Use of loops and goto's would be perfectly acceptable (but I played with this and it gets convoluted, out-of-hand and seemingly unworkable to begin with); however, additional helper functions should be avoided. I guess I should to turn this into some kind of simple state machine, but... which?
As for the practicality, this is to run at about 20 million times per second (stack depth can range from 1 through 25 max later on). This is a case where maintaining my own stack is bound to be more stable / faster than the function call stack. (There are no other function calls in this function, only calculations.) Also, no garbage generated = no garbage collected.
So here goes:
func testRecursion () {
var root *TMyTreeNode = makeSomeDeepTreeStructure()
// rl: current recursion level
// ml: max recursion level
var rl, ml = 0, root.MaxDepth
// node: "the stack"
var node = make([]*TMyTreeNode, ml + 1)
// the recursive and the non-recursive / iterative test functions:
var walkNodeRec, walkNodeIt func ();
walkNodeIt = func () {
log.Panicf("YOUR ITERATIVE / NON-RECURSIVE IDEAS HERE")
}
walkNodeRec = func () {
log.Printf("ENTER LEVEL %v", rl)
if (node[rl].Level == ml) || (node[rl].ChildNodes == nil) {
log.Printf("EXIT LEVEL %v", rl)
return
}
log.Printf("PRE-STUFF LEVEL %v", rl)
for i := 0; i < 3; i++ {
switch i {
case 0:
log.Printf("PRECASE %v.%v", rl, i)
node[rl + 1] = node[rl].ChildNodes[rl + i]; rl++; walkNodeRec(); rl--
log.Printf("POSTCASE %v.%v", rl, i)
case 1:
log.Printf("PRECASE %v.%v", rl, i)
node[rl + 1] = node[rl].ChildNodes[rl + i]; rl++; walkNodeRec(); rl--
log.Printf("POSTCASE %v.%v", rl, i)
case 2:
log.Printf("PRECASE %v.%v", rl, i)
node[rl + 1] = node[rl].ChildNodes[rl + i]; rl++; walkNodeRec(); rl--
log.Printf("POSTCASE %v.%v", rl, i)
}
}
}
// test recursion for reference:
if true {
rl, node[0] = 0, root
log.Printf("\n\n=========>RECURSIVE ML=%v:", ml)
walkNodeRec()
}
// test non-recursion, output should be identical
if true {
rl, node[0] = 0, root
log.Printf("\n\n=========>ITERATIVE ML=%v:", ml)
walkNodeIt()
}
}
UPDATE -- after some discussion here, and further thinking:
I just made up the following pseudo-code which in theory should do what I need:
curLevel = 0
for {
cn = nextsibling(curLevel, coords)
lastnode[curlevel] = cn
if cn < 8 {
if isleaf {
process()
} else {
curLevel++
}
} else if curLevel == 0 {
break
} else {
curLevel--
}
}
Of course the tricky part will be filling out nextsibling() for my custom use-case. But just as a general solution to eliminating inner recursion while maintaining the depth-first traversal order I need, this rough outline should do so in some form or another.
答案1
得分: 1
我不太确定我理解你想做什么,因为你的递归代码看起来有点奇怪。然而,如果我理解你的TMyTreeNode的结构,这是我为非递归版本所做的。
// root是我们的根节点
q := []*TMyTreeNode{root}
processed := make(map[*TMyTreeNode]bool
for {
l := len(q)
if l < 1 {
break // 队列为空
}
curr := q[l - 1]
if !processed[curr] && len(curr.childNodes) > 0 {
// 对curr进行一些操作
processed[curr] = true
q = append(q, curr.childNodes...)
continue // 继续遍历树的下一层
} else {
// 对curr进行一些操作
processed[curr] = true
q := q[:l-2] // 从队列中移除当前节点
}
}
注意:这将任意深入结构中。如果这不是你想要的,它需要进行一些修改。
英文:
I'm not really sure I understand what it is you want to do since your recursion code looks a little strange. However if I understand the structure of your TMyTreeNode then this is what I would do for a non recursive version.
// root is our root node
q := []*TMyTreeNode{root}
processed := make(map[*TMyTreeNode]bool
for {
l := len(q)
if l < 1 {
break // our queue is empty
}
curr := q[l - 1]
if !processed[curr] && len(curr.childNodes) > 0 {
// do something with curr
processed[curr] = true
q = append(q, curr.childNodes...)
continue // continue on down the tree.
} else {
// do something with curr
processed[curr] = true
q := q[:l-2] // pop current off the queue
}
}
NOTE: This will go arbitrarily deep into the structure. If that's not what you want it will need some modifications.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论