Bir döngüyü (/ iken) özyinelemeye veya özyinelemeden öze dönüştürmenin genel yolu?


23

Bu problem temel olarak algoritmaya, belki de soyut ve daha akademik bir şeye odaklanmaktadır.

Örnek bir düşünce sunuyor, ben genel bir yol istiyorum, bu nedenle örnek yalnızca düşünceleriniz hakkında bizi daha net hale getirmek için kullanılır.

Genel olarak konuşursak, bir döngü özyinelemeli olarak dönüştürülebilir.

Örneğin:

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

Ve ilgili özyinelemeli:

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

Ve sonunda bunu basitleştirmek için kuyruk özyinelemeye ihtiyaç var:

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

Ancak, çoğu durumda cevaplaması ve analizi o kadar kolay değildir. Bilmek istediğim şey:

1) Bir döngüyü tekrarlamak için (için / süre ……) özyinelemeye dönüştürmek için "genel bir ortak yol" alabilir miyiz? Ve dönüşüm yaparken ne gibi şeylere dikkat etmeliyiz? Dönüştürme işleminin yanı sıra bazı örnekler ve persudo teorilerinizle ayrıntılı bilgi yazmak daha iyi olacaktır.

2) "Özyinelemeli" iki şekli vardır: Doğrusal özyinelemeli ve Özyinelemeli. Peki hangisini dönüştürmek daha iyidir? Hangi "kural" ı usta yapmalıyız?

3) Bazen özyinelemeli "tarihini" tutmamız gerekir, bu kolayca bir döngü ifadesinde yapılabilir:

Örneğin:

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);
   }
}

Aşağıdaki sonuç:

1'in sonucu 1.

1 + 2'nin sonucu 3.

1 + 2 + 3 sonucu 6 …………

Ancak, özyinelemeyi özyinelemede tutmanın zor olduğunu düşünüyorum, çünkü özyinelemeli tabanlı bir algoritma son sonucu elde etmeye ve geri arama dönüşü yapmaya odaklanır. Böylece bunların hepsi programlama dili tarafından tutulan yığın üzerinden otomatik olarak yığın şeklinde bellek tahsis ederek yapılır. Ve "yığın değerlerinin" her birini "elle" nasıl el ile alabiliriz ve özyinelemeli bir algoritma ile birden fazla değeri nasıl döndürebiliriz?

Peki ya "özyinelemeli bir algoritmadan bir döngüye"? Birbirlerine dönüştürülebilirler mi (teorik olarak yapılması gerektiğini düşünüyorum, ama düşüncelerimi kanıtlamak için daha doğru şeyler istiyorum) .


"persudo" ne anlama geliyor?
gnat

Yanıtlar:


30

Aslında önce işlevi kesmelisiniz:

Bir döngü birkaç parçaya sahiptir:

  1. Başlık ve döngüden önce işleme . Bazı yeni değişkenler bildirebilir

  2. Bu durum ne zaman döngüyü durdurmalı.

  3. gerçek döngü gövdesi. Üstbilginin bazı değişkenlerini ve / veya iletilen parametreleri değiştirir.

  4. kuyruk; döngü ve dönüş sonucundan sonra ne olur.

Veya yazmak için:

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

Özyinelemeli bir arama yapmak için bu blokları kullanmak oldukça basittir:

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à; herhangi bir döngünün kuyruk özyinelemeli versiyonu. Döngü gövdesindeki breaks ve continues hala ile değiştirilmeli return tailve foo_recursion(params, modified_header_vars)gerektiğinde geri döndürülmelidir , ancak bu yeterince basittir.


Diğer tarafa gitmek daha karmaşıktır; kısmen, birden çok özyinelemeli çağrı olabileceği için. Bu, bir yığın çerçeveyi her açtığımızda, devam etmemiz gereken birçok yer olabileceği anlamına gelir. Ayrıca özyinelemeli aramada ve aramanın orijinal parametreleri arasında kaydetmemiz gereken değişkenler olabilir.

Bunun üzerinde çalışmak için bir anahtar kullanabiliriz:

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'in cevabını takiben, Fibonacci fonksiyonunun Java'daki bir süre döngüsüne nasıl yeniden yazılabileceğinin bu örneğini yarattım. Fibonacci'yi bir süre döngüsü ile yeniden yazmak için daha basit (ve verimli) bir yol olduğunu unutmayın.

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;
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.