Özyinelemeden yinelemeye geçmenin yolu


349

Basit sorunları çözmek için uzun yıllara dayanan programlamamda özyineleme kullandım, ancak bazen bellek / hız sorunları nedeniyle yinelemeye ihtiyacınız olduğunu tamamen biliyorum.

Bu yüzden, çok uzak bir zamanda, ortak bir özyineleme yaklaşımını yinelemeye dönüştürmenin herhangi bir "kalıp" veya ders kitabı yolu olup olmadığını bulmaya çalıştım ve hiçbir şey bulamadım. Ya da en azından hatırlayabileceğim hiçbir şey yardımcı olmaz.

  • Genel kurallar var mı?
  • Bir "desen" var mı?

Yanıtlar:


334

Genellikle, özyinelemeli bir algoritmayı yinelemeli işleve geçirilecek parametreleri normalde bir yığına iterek yinelemeli bir algoritma ile değiştiririm. Aslında, program yığınını kendinizinkilerle değiştiriyorsunuz.

Stack<Object> stack;
stack.push(first_object);
while( !stack.isEmpty() ) {
   // Do something
   my_object = stack.pop();

  // Push other objects on the stack.

}

Not: İçinde birden fazla yinelemeli çağrı varsa ve çağrıların sırasını korumak istiyorsanız, bunları yığına ters sırayla eklemeniz gerekir:

foo(first);
foo(second);

ile değiştirilmesi gerekiyor

stack.push(second);
stack.push(first);

Düzenleme: Yığınlar ve Özyineleme Yok Etme (veya Makale Yedekleme bağlantısı ) makalesi , bu konuda daha fazla ayrıntıya girer.


4
Yığınızı bir Kuyruk ile değiştirirseniz, ekleme siparişini tersine çevirme sorununu çözmez misiniz?
SamuelWarren

2
Kağıt üzerinde çalıştım ve iki farklı şey. Bunları eklediğiniz sırayı tersine çevirirseniz, her zamanki gibi ileriye doğru ilerlemenizi sağlar, ancak geçişiniz hala derinlikte ilk aramadır. Ama şimdi her şeyi bir kuyruğa çevirirseniz, önce derinlik-ilk geçişinden ziyade önce genişlik yaparsınız.
pete

1
Ben sadece son zamanlarda düğüm ziyareti fonksiyonunu değiştirerek, genel bir şekilde bunu (node)->()birlikte (node)->[actions]hareket orada () -> [actions]. Sonra dışarıda, sadece bir eylem / devam yığını yığına, uygulamak / yürütmek, sırayla döndüğü eylemleri ters sırayla itin ve tekrarlayın. Koşullu / kompleks dolaşımları, sadece o size yakın üzerinde thunks referans sayılan işaretçiler yerel yığın değişkenleri olurdu, daha sonraki thunks önceki alt dolaşımları vb sonuçlarına şarta ne olabilir yakalamak
experquisite

6
Bazen yığın akışını önlemek için özyinelemeden kaçınırız. Ancak kendi yığınımızı korumak da yığın akışına neden olur. Peki neden kendi yığımızla özyineleme uygulamak istiyoruz?
Zhu Li

8
@ZhuLi Eğer kullanırsak newyığın üzerinde yığın yerine bir nesne oluşturabiliriz. Yığının aksine, öbekte bellek kısıtlamaları yoktur. Bkz gribblelab.org/CBootCamp/7_Memory_Stack_vs_Heap.html
yuqli

77

Gerçekten, bunu yapmanın en yaygın yolu kendi yığını tutmaktır. İşte C'de yinelemeli hızlı sıralama işlevi:

void quicksort(int* array, int left, int right)
{
    if(left >= right)
        return;

    int index = partition(array, left, right);
    quicksort(array, left, index - 1);
    quicksort(array, index + 1, right);
}

Kendi yığınımızı koruyarak bunu tekrarlamalı hale getirebildik:

void quicksort(int *array, int left, int right)
{
    int stack[1024];
    int i=0;

    stack[i++] = left;
    stack[i++] = right;

    while (i > 0)
    {
        right = stack[--i];
        left = stack[--i];

        if (left >= right)
             continue;

        int index = partition(array, left, right);
        stack[i++] = left;
        stack[i++] = index - 1;
        stack[i++] = index + 1;
        stack[i++] = right;
    }
}

Açıkçası, bu örnek yığın sınırlarını kontrol etmez ... ve gerçekten de sol ve sağ değerlerde verilen en kötü duruma göre yığını boyutlandırabilirsiniz. Ama fikri anladınız.


1
Belirli bir özyineleme için ayrılacak maksimum yığını nasıl çalıştıracağınız konusunda herhangi bir fikriniz var mı?
lexicalscope

@lexicalscope, yinelemeli bir algoritmaya sahip olduğunuzu varsayar O(N) = O(R*L), burada L"katman r" için karmaşıklık toplamıdır, örneğin bu durumda O(N)bölümleri yapan her adımda çalıştığınız için, yinelemeli derinlik O(R), yani en kötü durumda O(N), ortalama bir durumdur O(logN).
Caleth

48

Hiç kimse, özyinelemeli fonksiyonun kendini vücutta bir kereden fazla çağırdığı ve özyinelemede belirli bir noktaya (yani ilkel özyinelemeli olmayan) geri döndüğü ele alınmamıştır. Her özyinelemenin yinelemeye dönüştürülebileceği söylenir , bu yüzden bunun mümkün olması gerektiği görülür.

Ben sadece bunun nasıl yapılacağı bir C # örneği ile geldi. Bir postorder geçişi gibi davranan ve AbcTreeNode'un a, b, c işaretçileri olan 3 ary bir ağaç olduğunu varsayalım.

public static void AbcRecursiveTraversal(this AbcTreeNode x, List<int> list) {
        if (x != null) {
            AbcRecursiveTraversal(x.a, list);
            AbcRecursiveTraversal(x.b, list);
            AbcRecursiveTraversal(x.c, list);
            list.Add(x.key);//finally visit root
        }
}

Yinelemeli çözüm:

        int? address = null;
        AbcTreeNode x = null;
        x = root;
        address = A;
        stack.Push(x);
        stack.Push(null)    

        while (stack.Count > 0) {
            bool @return = x == null;

            if (@return == false) {

                switch (address) {
                    case A://   
                        stack.Push(x);
                        stack.Push(B);
                        x = x.a;
                        address = A;
                        break;
                    case B:
                        stack.Push(x);
                        stack.Push(C);
                        x = x.b;
                        address = A;
                        break;
                    case C:
                        stack.Push(x);
                        stack.Push(null);
                        x = x.c;
                        address = A;
                        break;
                    case null:
                        list_iterative.Add(x.key);
                        @return = true;
                        break;
                }

            }


            if (@return == true) {
                address = (int?)stack.Pop();
                x = (AbcTreeNode)stack.Pop();
            }


        }

5
Gerçekten yararlı, n-kez kendini çağıran yinelemenin yinelemeli versiyonunu yazmak zorunda kaldım, yazınız sayesinde yaptım.
Wojciech Kulik

1
Bu yöntem içinde birden çok özyinelemeli çağrı yapılıyor durumlar için çağrı yığını özyineleme taklit gördüğüm en iyi örnek olması gerekir. İyi iş.
CCS

1
Bana "Görünüşe göre, özyinelemeli fonksiyonun kendini vücutta birden fazla çağırdığı ve özyinelemede belirli bir noktaya geri dönen kolları ele almadığı anlaşıldı" ve sonra zaten iptal ettim. Tamam, şimdi cevabınızın geri kalanını okuyacağım ve erken çıkarımımın haklı olup olmadığını göreceğim. (Çünkü bunun cevabını umutsuzca bilmem gerekiyor).
mydoghasworms

1
@mydoghasworms - çok uzun zaman sonra bu soruya dönersek, hatta aldı beni ne düşündüğümü hatırlıyorum için bir an. Umarım cevap yardımcı olmuştur.
T. Webster

1
Bu çözüm fikrini beğendim, ama bana kafa karıştırıcı görünüyordu. Python'da
azurkin

33

Özyineli çağrınızı Kuyruk Özyineleme (son ifadenin özyinelemeli çağrı olduğu özyineleme) yapmaya çalışın . Bunu yaptıktan sonra, yinelemeye dönüştürmek genellikle oldukça kolaydır.


2
Bazı JIT dönüşüm kuyruğu özyineleme: ibm.com/developerworks/java/library/j-diag8.html
Liran Orevi

Çok sayıda tercüman (yani, Şema en iyi bilinenidir) kuyruk tekrarını iyi optimize edecektir. GCC'nin belirli bir optimizasyonla kuyruk özyineleme yaptığını biliyorum (C böyle bir optimizasyon için garip bir seçim olsa da).
yeni123456

19

Genel olarak, özyineleme yalnızca bir depolama değişkeni kullanılarak yineleme olarak taklit edilebilir. Özyineleme ve yinelemenin genellikle eşdeğer olduğunu unutmayın; biri neredeyse her zaman diğerine dönüştürülebilir. Kuyruk özyinelemeli fonksiyon çok kolay bir şekilde yinelemeli bir fonksiyona dönüştürülür. Akümülatör değişkenini yerel bir değişken yapın ve tekrarlamak yerine tekrarlayın. İşte C ++ 'da bir örnek (C, varsayılan bir argümanın kullanımı için değildi):

// tail-recursive
int factorial (int n, int acc = 1)
{
  if (n == 1)
    return acc;
  else
    return factorial(n - 1, acc * n);
}

// iterative
int factorial (int n)
{
  int acc = 1;
  for (; n > 1; --n)
    acc *= n;
  return acc;
}

Beni tanıyarak, muhtemelen kodda bir hata yaptım, ama fikir orada.


14

Yığını kullanmak bile yinelemeli algoritmayı yinelemeye dönüştürmez. Normal özyineleme işlev tabanlı özyineleme olup yığın kullanırsak yığın tabanlı özyineleme olur. Ama yine de özyineleme.

Özyinelemeli algoritmalar için, uzay karmaşıklığı O (N) ve zaman karmaşıklığı O (N) 'dir. Yinelemeli algoritmalar için uzay karmaşıklığı O (1) ve zaman karmaşıklığı O (N) 'dir.

Ancak yığın işleri karmaşıklık açısından kullanırsak aynı kalır. Bence sadece kuyruk özyineleme yinelemeye dönüştürülebilir.


1
İlk kısmınıza katılıyorum, ama sanırım ikinci paragrafı yanlış anlıyorum. Bir diziyi sadece bellek copy = new int[size]; for(int i=0; i<size; ++i) copy[i] = source[i];alanını ve zaman karmaşıklığını kopyalayarak verilerin boyutunu temel alarak O (N) olarak kopyalamayı düşünün , ancak açıkça yinelemeli bir algoritmadır.
Ponkadoodle

13

Yığınları ve tekrarlama eleme makale yakalar yığın yığın çerçevesini dünyanızı dışa fikir, ama bir sağlamaz basit ve tekrarlanabilir dönüştürmek için bir yol. Aşağıda bir tane.

Yinelemeli koda dönüştürülürken, yinelemeli çağrının keyfi olarak derin bir kod bloğundan olabileceğinin farkında olunmalıdır. Bu sadece parametreler değil, aynı zamanda yürütülecek mantığa ve sonraki koşullara katılan değişkenlerin durumuna da önemli olan nokta. Aşağıda, en az değişiklikle yinelemeli koda dönüştürmenin çok basit bir yolu bulunmaktadır.

Şu özyinelemeli kodu düşünün:

struct tnode
{
    tnode(int n) : data(n), left(0), right(0) {}
    tnode *left, *right;
    int data;
};

void insertnode_recur(tnode *node, int num)
{
    if(node->data <= num)
    {
        if(node->right == NULL)
            node->right = new tnode(num);
        else
            insertnode(node->right, num);
    }
    else
    {
        if(node->left == NULL)
            node->left = new tnode(num);
        else
            insertnode(node->left, num);
    }    
}

Yinelemeli kod:

// Identify the stack variables that need to be preserved across stack 
// invocations, that is, across iterations and wrap them in an object
struct stackitem 
{ 
    stackitem(tnode *t, int n) : node(t), num(n), ra(0) {}
    tnode *node; int num;
    int ra; //to point of return
};

void insertnode_iter(tnode *node, int num) 
{
    vector<stackitem> v;
    //pushing a stackitem is equivalent to making a recursive call.
    v.push_back(stackitem(node, num));

    while(v.size()) 
    {
        // taking a modifiable reference to the stack item makes prepending 
        // 'si.' to auto variables in recursive logic suffice
        // e.g., instead of num, replace with si.num.
        stackitem &si = v.back(); 
        switch(si.ra)
        {
        // this jump simulates resuming execution after return from recursive 
        // call 
            case 1: goto ra1;
            case 2: goto ra2;
            default: break;
        } 

        if(si.node->data <= si.num)
        {
            if(si.node->right == NULL)
                si.node->right = new tnode(si.num);
            else
            {
                // replace a recursive call with below statements
                // (a) save return point, 
                // (b) push stack item with new stackitem, 
                // (c) continue statement to make loop pick up and start 
                //    processing new stack item, 
                // (d) a return point label
                // (e) optional semi-colon, if resume point is an end 
                // of a block.

                si.ra=1;
                v.push_back(stackitem(si.node->right, si.num));
                continue; 
ra1:            ;         
            }
        }
        else
        {
            if(si.node->left == NULL)
                si.node->left = new tnode(si.num);
            else
            {
                si.ra=2;                
                v.push_back(stackitem(si.node->left, si.num));
                continue;
ra2:            ;
            }
        }

        v.pop_back();
    }
}

Kod yapısının özyinelemeli mantık için nasıl doğru kaldığına ve değişikliklerin asgari düzeyde olduğuna ve daha az hataya neden olduğuna dikkat edin. Karşılaştırma için, değişiklikleri ++ ve - ile işaretledim. V.push_back dışındaki yeni eklenen blokların çoğu dönüştürülmüş yinelemeli mantık için ortaktır

void insertnode_iter(tnode *node, int num) 
{

+++++++++++++++++++++++++

    vector<stackitem> v;
    v.push_back(stackitem(node, num));

    while(v.size())
    {
        stackitem &si = v.back(); 
        switch(si.ra)
        {
            case 1: goto ra1;
            case 2: goto ra2;
            default: break;
        } 

------------------------

        if(si.node->data <= si.num)
        {
            if(si.node->right == NULL)
                si.node->right = new tnode(si.num);
            else
            {

+++++++++++++++++++++++++

                si.ra=1;
                v.push_back(stackitem(si.node->right, si.num));
                continue; 
ra1:            ;    

-------------------------

            }
        }
        else
        {
            if(si.node->left == NULL)
                si.node->left = new tnode(si.num);
            else
            {

+++++++++++++++++++++++++

                si.ra=2;                
                v.push_back(stackitem(si.node->left, si.num));
                continue;
ra2:            ;

-------------------------

            }
        }

+++++++++++++++++++++++++

        v.pop_back();
    }

-------------------------

}

Bu bana çok yardımcı oldu, ama bir sorun var: stackitemnesneler için bir çöp değeri ile ayrılır ra. Her şey hala en çok benzer durumda çalışır, ancak ratesadüf 1 veya 2 olması durumunda yanlış davranışlar elde edersiniz. Çözüm, ra0 olarak başlatmaktır .
JanX2

@ JanX2, stackitembaşlatmadan itilmemelidir . Ancak evet, 0 değerine sıfırlamak hataları yakalar.
Chethan

Neden her iki dönüş adresi de v.pop_back()ifadeye ayarlanmamış ?
is7s

7

Google'da "Devam tarzı" ifadesini arayın. Kuyruk özyinelemeli stile dönüştürmek için genel bir prosedür vardır; kuyruk özyinelemeli fonksiyonları döngülere dönüştürmek için genel bir prosedür de vardır.


6

Sadece zaman öldürmek ... Tekrarlayan bir fonksiyon

void foo(Node* node)
{
    if(node == NULL)
       return;
    // Do something with node...
    foo(node->left);
    foo(node->right);
}

dönüştürülebilir

void foo(Node* node)
{
    if(node == NULL)
       return;

    // Do something with node...

    stack.push(node->right);
    stack.push(node->left);

    while(!stack.empty()) {
         node1 = stack.pop();
         if(node1 == NULL)
            continue;
         // Do something with node1...
         stack.push(node1->right);             
         stack.push(node1->left);
    }

}

Yukarıdaki örnek ikili arama ağacında yinelemeli dfs özyinelemesine bir örnektir :)
Amit

5

Genellikle yığın taşmasını önleme tekniğine özyinelemeli işlevler için Java devs tarafından yaygın olarak kabul edilen trambolin tekniği denir.

Bununla birlikte, C # için burada , mantık değiştirmeye veya kodu anlaşılmaz hale getirmeye gerek kalmadan, yinelemeli işlevinizi yinelemeye dönüştüren küçük bir yardımcı yöntem vardır . C # o kadar güzel bir dildir ki inanılmaz şeyler mümkündür.

Yöntemin bazı kısımlarını bir yardımcı yöntemle sararak çalışır. Örneğin, aşağıdaki özyinelemeli işlev:

int Sum(int index, int[] array)
{
 //This is the termination condition
 if (int >= array.Length)
 //This is the returning value when termination condition is true
 return 0;

//This is the recursive call
 var sumofrest = Sum(index+1, array);

//This is the work to do with the current item and the
 //result of recursive call
 return array[index]+sumofrest;
}

Dönüşür:

int Sum(int[] ar)
{
 return RecursionHelper<int>.CreateSingular(i => i >= ar.Length, i => 0)
 .RecursiveCall((i, rv) => i + 1)
 .Do((i, rv) => ar[i] + rv)
 .Execute(0);
}

4

Aslında bir yığın gerektiren şeyleri düşünmek:

Özyineleme modelini şöyle düşünürsek:

if(task can be done directly) {
    return result of doing task directly
} else {
    split task into two or more parts
    solve for each part (possibly by recursing)
    return result constructed by combining these solutions
}

Örneğin, klasik Hanoi Kulesi

if(the number of discs to move is 1) {
    just move it
} else {
    move n-1 discs to the spare peg
    move the remaining disc to the target peg
    move n-1 discs from the spare peg to the target peg, using the current peg as a spare
}

Bu, açık bir yığın üzerinde çalışan bir döngüye çevrilerek şu şekilde yeniden yazılabilir:

place seed task on stack
while stack is not empty 
   take a task off the stack
   if(task can be done directly) {
      Do it
   } else {
      Split task into two or more parts
      Place task to consolidate results on stack
      Place each task on stack
   }
}

Hanoi Kulesi için bu:

stack.push(new Task(size, from, to, spare));
while(! stack.isEmpty()) {
    task = stack.pop();
    if(task.size() = 1) {
        just move it
    } else {
        stack.push(new Task(task.size() -1, task.spare(), task,to(), task,from()));
        stack.push(new Task(1, task.from(), task.to(), task.spare()));
        stack.push(new Task(task.size() -1, task.from(), task.spare(), task.to()));
    }
}

Burada yığınızı nasıl tanımladığınız konusunda önemli esneklik var. Yığınızı, Commandkarmaşık şeyler yapan nesnelerin bir listesi haline getirebilirsiniz . Ya da zıt yöne gidebilir ve daha basit türlerin bir listesini yapabilirsiniz (örneğin, "görev", bir yığındaki intbir öğe yerine bir yığındaki 4 öğe olabilir Task).

Tüm bunlar, yığının belleğinin Java yürütme yığınından ziyade yığınta olduğu, ancak bunun üzerinde daha fazla kontrole sahip olmanızda yararlı olabileceği anlamına gelir.


3

Aranacak bir desen, işlevin sonunda bir özyineleme çağrısıdır (kuyruk özyineleme olarak adlandırılır). Bu kolayca bir süre ile değiştirilebilir. Örneğin, foo işlevi:

void foo(Node* node)
{
    if(node == NULL)
       return;
    // Do something with node...
    foo(node->left);
    foo(node->right);
}

foo çağrısı ile biter. Bu ile değiştirilebilir:

void foo(Node* node)
{
    while(node != NULL)
    {
        // Do something with node...
        foo(node->left);
        node = node->right;
     }
}

ikinci özyinelemeli çağrıyı ortadan kaldırır.


3
Hala bana yinelemeli görünüyor ... :)
nathan

2
Evet, - ama özyinelemenin yarısı kadar. Diğer özyinelemeden kurtulmak için başka bir teknik kullanmanız gerekecek ...
Mark Bessey

2

Bunun bir kopyası olarak kapatılmış bir sorunun çok özel bir veri yapısı vardı:

resim açıklamasını buraya girin

Düğüm aşağıdaki yapıya sahipti:

typedef struct {
    int32_t type;
    int32_t valueint;
    double  valuedouble;
    struct  cNODE *next;
    struct  cNODE *prev;
    struct  cNODE *child;
} cNODE;

Özyinelemeli silme işlevi şuna benziyordu:

void cNODE_Delete(cNODE *c) {
    cNODE*next;
    while (c) {
        next=c->next;
        if (c->child) { 
          cNODE_Delete(c->child)
        }
        free(c);
        c=next;
    }
}

Genel olarak, kendini bir kereden fazla (hatta bir kez) çağıran özyinelemeli işlevler için bir yığından kaçınmak her zaman mümkün değildir. Bununla birlikte, bu özel yapı için mümkündür. Fikir, tüm düğümleri tek bir listede düzleştirmektir. Bu, geçerli düğümleri en childüst satır listesinin sonuna koyarak yapılır .

void cNODE_Delete (cNODE *c) {
    cNODE *tmp, *last = c;
    while (c) {
        while (last->next) {
            last = last->next;   /* find last */
        }
        if ((tmp = c->child)) {
            c->child = NULL;     /* append child to last */
            last->next = tmp;
            tmp->prev = last;
        }
        tmp = c->next;           /* remove current */
        free(c);
        c = tmp;
    }
}

Bu teknik, belirli bir topolojik sıralama ile bir DAG'a indirgenebilecek herhangi bir veri bağlantılı yapıya uygulanabilir. Mevcut düğümler yeniden düzenlenir, böylece son çocuk diğer tüm çocukları evlat edinir. Ardından geçerli düğüm silinebilir ve çapraz geçiş kalan alt öğeye yinelenebilir.


1

Özyineleme, bir işlevin diğerinden çağrılması işleminden başka bir şey değildir, yalnızca bu işlem, bir işlev tek başına çağrılarak yapılır. Bir fonksiyonun diğer fonksiyonu çağırdığı zaman bildiğimiz gibi, ilk fonksiyon durumunu (değişkenlerini) kaydeder ve daha sonra kontrolü çağrılan fonksiyona geçirir. Aranan işlev, fun1 (a) fun2 (a) çağıran değişkenlerin aynı adı kullanılarak çağrılabilir. Özyinelemeli çağrı yaptığımızda yeni bir şey olmaz. Bir işlev, ad değişkenlerinde aynı tür ve benzerleri geçirerek kendisini çağırır (ancak değişkenlerde depolanan değerler farklıdır, yalnızca ad aynı kalır). Ancak her çağrıdan önce işlev durumunu kaydeder ve bu tasarruf süreci devam eder. TASARRUF BİR YÜKDE YAPILIR.

ŞİMDİ OYUN OYNA.

Bu nedenle, yinelemeli bir program yazar ve durumu her defasında bir yığına kaydeder ve ardından gerektiğinde yığındaki değerleri çıkarırsanız, özyinelemeli bir programı yinelemeli bir programa başarıyla dönüştürdünüz!

Kanıt basit ve analitiktir.

Özyinelemede bilgisayar bir yığını tutar ve yinelemeli sürümde yığını elle tutmanız gerekir.

Bir düşünün, sadece bir derinlik ilk aramayı (grafiklerde) özyinelemeli programı bir dfs yinelemeli programa dönüştürün.

Herşey gönlünce olsun!


1

Yığın kullanarak özyinelemeli işlevi yinelemeli işleve dönüştürmenin bir başka basit ve eksiksiz örneği.

#include <iostream>
#include <stack>
using namespace std;

int GCD(int a, int b) { return b == 0 ? a : GCD(b, a % b); }

struct Par
{
    int a, b;
    Par() : Par(0, 0) {}
    Par(int _a, int _b) : a(_a), b(_b) {}
};

int GCDIter(int a, int b)
{
    stack<Par> rcstack;

    if (b == 0)
        return a;
    rcstack.push(Par(b, a % b));

    Par p;
    while (!rcstack.empty()) 
    {
        p = rcstack.top();
        rcstack.pop();
        if (p.b == 0)
            continue;
        rcstack.push(Par(p.b, p.a % p.b));
    }

    return p.a;
}

int main()
{
    //cout << GCD(24, 36) << endl;
    cout << GCDIter(81, 36) << endl;

    cin.get();
    return 0;
}

0

Bir sistemin özyinelemeli işlevi nasıl aldığını ve bir yığın kullanarak nasıl yürüttüğünü gösteren kabaca bir açıklama:

Bu, fikri ayrıntılar olmadan göstermeyi amaçladı. Bir grafiğin düğümlerini yazdıracak bu işlevi göz önünde bulundurun:

function show(node)
0. if isleaf(node):
1.  print node.name
2. else:
3.  show(node.left)
4.  show(node)
5.  show(node.right)

Örneğin grafik: A-> B A-> C gösterisi (A) B, A, C yazdıracaktır

İşlev çağrıları, yerel durumu ve devam noktasını kaydetmek anlamına gelir, böylece geri dönebilir ve sonra aramak istediğiniz işlevi atlayabilirsiniz.

Örneğin, şovun (A) yayınlanmaya başladığını varsayalım. 3. satırdaki işlev çağrısı show (B) anlamına gelir - Yığına "yerel değişken durum düğümü = A değişkeniyle 2. satıra devam etmeniz gerekir" anlamına gelir - 0 düğümü = B düğümü ile git.

Kod yürütmek için, sistem talimatlar aracılığıyla çalışır. Bir işlev çağrısıyla karşılaşıldığında, sistem geri gelmesi gereken bilgiyi iter, işlev kodunu çalıştırır ve işlev tamamlandığında, devam etmek için nereye gitmesi gerektiği hakkında bilgi açar.


0

Bu bağlantı biraz açıklama sağlar ve "konum" un birkaç özyinelemeli çağrı arasındaki tam yere ulaşabilmesi için tutulması fikrini önerir:

Bununla birlikte, tüm bu örnekler, yinelemeli bir çağrının sabit bir sayıda yapıldığı senaryoları açıklamaktadır . Gibi bir şeye sahip olduğunuzda işler daha zorlaşır:

function rec(...) {
  for/while loop {
    var x = rec(...)
    // make a side effect involving return value x
  }
}


0

Örneklerim Clojure'da, ancak herhangi bir dile tercüme edilmesi oldukça kolay olmalı.

StackOverflowBüyük n değerleri için bu fonksiyon göz önüne alındığında :

(defn factorial [n]
  (if (< n 2)
    1
    (*' n (factorial (dec n)))))

kendi yığınını kullanan bir sürümü aşağıdaki şekilde tanımlayabiliriz:

(defn factorial [n]
  (loop [n n
         stack []]
    (if (< n 2)
      (return 1 stack)
      ;; else loop with new values
      (recur (dec n)
             ;; push function onto stack
             (cons (fn [n-1!]
                     (*' n n-1!))
                   stack)))))

burada returnşu şekilde tanımlanır:

(defn return
  [v stack]
  (reduce (fn [acc f]
            (f acc))
          v
          stack))

Bu, ackermann işlevi gibi daha karmaşık işlevler için de geçerlidir :

(defn ackermann [m n]
  (cond
    (zero? m)
    (inc n)

    (zero? n)
    (recur (dec m) 1)

    :else
    (recur (dec m)
           (ackermann m (dec n)))))

dönüştürülebilir:

(defn ackermann [m n]
  (loop [m m
         n n
         stack []]
    (cond
      (zero? m)
      (return (inc n) stack)

      (zero? n)
      (recur (dec m) 1 stack)

      :else
      (recur m
             (dec n)
             (cons #(ackermann (dec m) %)
                   stack)))))
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.