2012-04-05 6 views
0

이것은 간단한 왼쪽 재귀 또는 꼬리 호출 재귀보다 약간 복잡합니다. 그래서 이런 종류의 재귀를 제거 할 수 있을지 궁금합니다. 아래에서 볼 수 있듯이 이미 스택을 유지하고 있으므로 함수에는 매개 변수 나 반환 값이 필요하지 않습니다. 그러나, 그것은 여전히 ​​자신을 특정 레벨까지 (또는 아래로) 부르고 있으며 이것을 루프로 바꿔 놓고 싶지만, 지금 당분간 내 머리를 긁적 거리고 있습니다.이 재귀 유형을 제거하는 방법은 무엇입니까?

단순한 테스트 케이스는 모든 "실제 로직"을 printf ("레벨 #n에서"dostuff ") 메시지로 대체합니다. 이것은 Go에 있지만 문제는 대부분의 언어에 적용됩니다. 루프와 고토의 사용은 완벽하게 받아 들여질 수 있습니다 (하지만 이걸 가지고 놀았고, 뒤죽박죽이되어 버렸습니다. 그리고 처음에는 겉으로보기에는 쓸모가 없었습니다). 그러나 추가 도우미 기능은 피해야합니다. 이걸 일종의 단순한 상태 기계로 바꾸어야한다고 생각하지만 ... 어느 쪽? ;)

실용성에 관해서는, 이것은 초당 약 2000 만회 (스택 깊이는 나중에 최대 1에서 최대 25까지 가능)입니다. 이것은 내 자신의 스택을 유지하는 것이 함수 호출 스택보다 더 안정적/빠를 수 있도록 바인딩되는 경우입니다. (이 함수에는 다른 함수 호출이 없으며 계산 만 수행됩니다.) 또한 가비지가 생성되지 않고 가비지가 수집되지 않습니다.

그래서 여기 간다 :

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가는 - 어떤 여기 토론, 추가 사고 후 :

난 그냥 다음 의사 코드를 제작하는 이론 이 필요합니다.

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()을 채울 것입니다. 그러나 필요한 깊이 우선 탐색 순서를 유지하면서 내부 재귀를 제거하는 일반적인 해결책과 마찬가지로이 대략적인 개요는 어떤 형태로든 그렇게해야합니다.

+1

자세한 내용 없이는 대답하기가 어렵습니다. 왜 for 루프는 단지 3 번입니까? 오직 세 개의 자식 노드가 있습니까? 보통 나는 root, child1, ..., childN의 대기열을 사용하여 비어있을 때까지 스택을 밀고 튕겨냅니다. 그러나 나는 당신이 원하는 것을 무언가를 놓치고있는 것처럼 느낍니다. –

+0

안녕하세요 Jeremy, 간단하게 여기에 사용 사례를 단순화하고 일반화했으나 위에 나온 해결책을 통해 실제 사례에 도움이 될 것입니다. 이것은 단지 3x 예제 루프 일 뿐이며, for 루프 반복마다 0 개 또는 하나의 재귀 호출이 있다는 사실이 중요합니다. 나중에 for 루프 반복 횟수는 최대 8이지만 더 일찍 종료 될 수 있습니다. 사실, 다음과 같이됩니다 : for (flag <8) {switch flag {case foo : flag = calc_new_flag_between_0_and_8}} – metaleap

+0

정정 : for 루프 반복마다 항상 정확하게 (적어도 많아야) 하나의 재귀 호출이 있습니다. – metaleap

답변

1

재귀 코드가 조금 이상해 보였기 때문에 당신이하고 싶은 것이 무엇인지 이해하지 못했습니다. 그러나 TMyTreeNode의 구조를 이해한다면 이것은 재귀 버전이 아닌 경우에 수행 할 작업입니다.

// 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 
    } 
} 

참고 : 이는 구조에 임의로 적용됩니다. 그것이 당신이 원하는 것이 아니라면 약간의 수정이 필요할 것입니다.

+0

아이디어를 제공해 주셔서 감사합니다! 나는 이것을 지금 실험적으로 코딩하고있다. 언뜻보기에, "for-loop-iteration의 switch-case * 재귀 호출"에서 실제로 실행해야하는 "코드"를 통합하는 방법을 아직 알지 못했지만 그 중 하나를 알아낼 수 있어야합니다. 작업이 완료되면 여기에서 전체 전환을 게시합니다. – metaleap

+0

불행히도이 특별한 경우에는 작동하지 않습니다. 동시에 처리 할 노드의 목록을 작성하고 동시에 목록을 처리하는 것이 좋습니다. 이는 좋은 생각이지만 엄격한 규칙의 필요성을 다루지는 않습니다 노드를 처리하는 여행 - 다운 - 버블 - 백업 - 순서. 예 : 루트 수준에서 처리 할 가치가있는 8 개의 하위 노드 중 3 개를 찾을 수는 있지만 나머지 2 개를 처리하기 전에 그 3 개 중 1 개를 모두 내리는 것이 중요합니다. 출구. 그래서 3 단계에서 순서는 0.0.0, 0.0.1, 0.0.2, 0.1.0, 0.1.1, 0.1.2, 0.2.0, ... – metaleap

+0

이 될 것입니다. 백만 개 정도의 노드가 있기 때문에 순서대로 순회 목록을 미리 만들 수는 없으므로 max를 처리하기 만하면됩니다. 75 (25 깊이의 경우) 노드는이 특정 순회에 실제로 필요합니다. 이렇게하려면 순차적으로 순서가 바뀌어야하며 (올바른 순서를 가져 오는 스위치 구조는 처음부터 끝까지 내려야합니다.) 다음 형제로 진행하기 전에 모든 방법을 백업해야합니다. – metaleap

관련 문제