루프 (while / for)를 재귀로 또는 재귀에서 루프로 변환하는 일반적인 방법은 무엇입니까?


23

이 문제는 주로 알고리즘에 초점을 맞추고 있습니다.

이 예는 생각을 제공하고 있습니다. 일반적인 방법을 원합니다. 따라서 예는 단지 당신의 생각에 대해 더 명확하게하기 위해 사용됩니다.

일반적으로 루프는 재귀로 변환 될 수 있습니다.

예 :

for(int i=1;i<=100;++i){sum+=i;}

관련 재귀는 다음과 같습니다.

int GetTotal(int number)
{
   if (number==1) return 1;   //The end number
   return number+GetTotal(number-1); //The inner recursive
}

마지막으로이를 단순화하려면 꼬리 재귀가 필요합니다.

int GetTotal (int number, int sum)
{
    if(number==1) return sum;
    return GetTotal(number-1,sum+number);
}

그러나 대부분의 경우 대답하고 분석하기가 쉽지 않습니다. 내가 알고 싶은 것은 :

1) 루프 (for / while ……)를 재귀로 변환하는 "일반적인 일반적인 방법"을 얻을 수 있습니까? 그리고 회심을 할 때 어떤 종류의 것들에주의를 기울여야합니까? 변환 과정뿐만 아니라 일부 샘플과 설득 이론으로 자세한 정보를 작성하는 것이 좋습니다.

2) "재귀"는 선형 재귀와 꼬리 재귀의 두 가지 형태가 있습니다. 그렇다면 어느 것이 변환하는 것이 더 낫습니까? 우리는 어떤 "규칙"을 마스터해야합니까?

3) 때때로 우리는 재귀의 "역사"를 유지해야 할 때가 있습니다.

예 :

List<string> history = new List<string>();
int sum=0;
for (int i=1;i<=100;++i)
{
   if(i==1) history.Add(i.ToString()+"'s result is:1.");
   else
   {
     StringBuilder sub = new StringBuilder();

      for(int j=1;j<=i;++j)
      {
          if(j==i) sbu.Append(j.ToString());
          else
          {
            sub.Append(j.ToString()+"+");
          }
      }
    sum +=i;
    sbu.Append("'s result is:"+sum+Environment.NewLine);
   }
}

아래 결과는 다음과 같습니다.

1의 결과는 1입니다.

1 + 2의 결과는 3입니다.

1 + 2 + 3의 결과는 6입니다. …………

그러나 재귀 기반 알고리즘은 마지막 결과를 얻는 데 중점을두고 콜백 반환을 수행하기 때문에 기록을 재귀로 유지하는 것이 어렵다고 생각합니다. 따라서 이러한 모든 작업은 프로그래밍 언어가 메모리를 자동으로 스택 형태로 할당하여 유지 관리하는 스택을 통해 수행됩니다. 그리고 어떻게 "수동으로"각 "스택 값"을 꺼내고 재귀 알고리즘을 통해 여러 값을 반환 할 수 있습니까?

그리고 "재귀 알고리즘에서 루프까지"는 어떻습니까? 그것들은 서로 전환 될 수 있습니까? (이론적으로 이론적으로 이루어져야한다고 생각하지만, 더 정확한 것들이 내 생각을 증명하기를 원합니다) .


"페르수도"는 무엇을 의미합니까?
gnat

답변:


30

실제로 함수를 먼저 분해해야합니다.

루프에는 몇 가지 부분이 있습니다.

  1. 헤더 및 루프 처리 . 새로운 변수를 선언 할 수있다

  2. 루프를 중지 할 때의 조건.

  3. 실제 루프 바디 헤더의 변수 및 / 또는 전달 된 매개 변수 중 일부를 변경합니다.

  4. 꼬리; 루프 및 결과 반환 후 발생하는 사항

또는 그것을 작성하십시오 :

foo_iterative(params){
    header
    while(condition){
        loop_body
    }
    return tail
}

다음 블록을 사용하여 재귀 호출을하는 것은 매우 간단합니다.

foo_recursive(params){
    header
    return foo_recursion(params, header_vars)
}

foo_recursion(params, header_vars){
    if(!condition){
        return tail
    }

    loop_body
    return foo_recursion(params, modified_header_vars)
}

Et voilà; 모든 루프의 꼬리 재귀 버전. 루프 바디의 breaks 및 continues는 여전히 필요에 따라 교체 return tail되고 반환 foo_recursion(params, modified_header_vars)되어야하지만 충분히 간단합니다.


다른 방법으로가는 것은 더 복잡합니다. 재귀 호출이 여러 개있을 수 있기 때문입니다. 즉, 스택 프레임을 팝할 때마다 계속해야 할 여러 위치가있을 수 있습니다. 또한 재귀 호출과 호출의 원래 매개 변수를 통해 저장해야 할 변수가있을 수 있습니다.

이 문제를 해결하기 위해 스위치를 사용할 수 있습니다.

bar_recurse(params){
    if(baseCase){
        finalize
        return
    }
    body1
    bar_recurse(mod_params)
    body2
    bar_recurse(mod_params)
    body3
}


bar_iterative(params){
    stack.push({init, params})

    while(!stack.empty){
        stackFrame = stack.pop()

        switch(stackFrame.resumPoint){
        case init:
            if(baseCase){
                finalize
                break;
            }
            body1
            stack.push({resum1, params, variables})
            stack.push({init, modified_params})
            break;
        case resum1:
            body2
            stack.push({resum2, params, variables})
            stack.push({init, modified_params})
            break;
        case resum2:
            body3
            break;
        }
    }
}

0

@ratchet freak의 답변에 이어 Java에서 피보나치 함수를 while 루프로 다시 쓰는 방법에 대한 예제를 만들었습니다. while 루프를 사용하여 피보나치를 다시 작성하는 훨씬 간단하고 효율적인 방법이 있습니다.

class CallContext { //this class is similar to the stack frame

    Object[] args;

    List<Object> vars = new LinkedList<>();

    int resumePoint = 0;

    public CallContext(Object[] args) {
        this.args = args;
    }

}


static int fibonacci(int fibNumber) {
    Deque<CallContext> callStack = new LinkedList<>();
    callStack.add(new CallContext(new Object[]{fibNumber}));
    Object lastReturn = null; //value of last object returned (when stack frame was dropped)
    while (!callStack.isEmpty()) {
        CallContext callContext = callStack.peekLast();
        Object[] args = callContext.args;
        //actual logic starts here
        int arg = (int) args[0];
        if (arg == 0 || arg == 1) {
            lastReturn = arg;
            callStack.removeLast();
        } else {
            switch (callContext.resumePoint) {
                case 0: //calculate fib(n-1)
                    callStack.add(new CallContext(new Object[]{arg - 1}));
                    callContext.resumePoint++;
                    break;
                case 1: //calculate fib(n-2)
                    callContext.vars.add(lastReturn); //fib1
                    callStack.add(new CallContext(new Object[]{arg - 2}));
                    callContext.resumePoint++;
                    break;
                case 2: // fib(n-1) + fib(n-2)
                    callContext.vars.add(lastReturn); //fib2
                    lastReturn = (int) callContext.vars.get(0) + (int) callContext.vars.get(1);
                    callStack.removeLast();
                    break;
            }
        }
    }
    return (int) lastReturn;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.