Özyinelemeyi anlama [kapalı]


225

Okulda özyinelemeyi anlamakta büyük sıkıntı yaşıyorum . Profesör ne zaman bahsediyorsa, anlıyorum ama kendi başıma denediğimde beynimi tamamen havaya uçuruyor.

Bütün gece Hanoi Kulelerini çözmeye çalışıyordum ve tamamen aklımı uçurdum. Ders kitabımın özyinelemede yalnızca 30 sayfası var, bu yüzden çok kullanışlı değil. Bu konuyu açıklığa kavuşturmaya yardımcı olabilecek kitaplar veya kaynaklar bilen var mı?


200
Özyinelemeyi anlamak için önce özyinelemeyi anlamalısınız.
Paul Tomblin

40
Özyineleme: Özyineleme bakın
Loren Pechtel

36
@ Paul: Şakayı anlıyorum, ama her zaman teknik olarak yanlış olduğunu düşündüm. Algoritmanın bitmesine neden olan temel durum nerede? Bu özyineleme için temel bir koşuldur. =)
Sergio Acosta

70
Ben bir şans vereceğim: "Özyineleme anlamak için, özyineyi anlayana kadar, özyineleme anlamak gerekir." =)
Sergio Acosta

91
Bu soruya bir göz
atabilirsiniz

Yanıtlar:


598

Beş çiçek içeren bir vazoyu nasıl boşaltırsınız?

Cevap: vazo boş değilse, bir çiçek çıkarırsınız ve sonra dört çiçek içeren bir vazoyu boşaltırsınız.

Dört çiçek içeren bir vazoyu nasıl boşaltırsınız?

Cevap: vazo boş değilse, bir çiçek çıkarırsınız ve daha sonra üç çiçek içeren bir vazoyu boşaltırsınız.

Üç çiçek içeren bir vazoyu nasıl boşaltırsınız?

Cevap: vazo boş değilse, bir çiçek çıkarırsınız ve sonra iki çiçek içeren bir vazoyu boşaltırsınız.

İki çiçek içeren bir vazoyu nasıl boşaltırsınız?

Cevap: vazo boş değilse, bir çiçek çıkarırsınız ve sonra bir çiçek içeren bir vazoyu boşaltırsınız.

Bir çiçek içeren bir vazoyu nasıl boşaltırsınız?

Cevap: vazo boş değilse, bir çiçek çıkarırsınız ve daha sonra çiçek içermeyen bir vazoyu boşaltırsınız.

Çiçek içermeyen bir vazoyu nasıl boşaltırsınız?

Cevap: Vazo boş değilse, bir çiçek çıkarırsınız, ancak vazo boştur, bu yüzden işiniz bitti.

Bu tekrarlayıcı. Genelleştirelim:

N çiçek içeren bir vazoyu nasıl boşaltırsınız ?

Cevap: vazo boş değilse, bir çiçek çıkarırsınız ve sonra N-1 çiçek içeren bir vazoyu boşaltırsınız .

Hmm, bunu kodda görebilir miyiz?

void emptyVase( int flowersInVase ) {
  if( flowersInVase > 0 ) {
   // take one flower and
    emptyVase( flowersInVase - 1 ) ;

  } else {
   // the vase is empty, nothing to do
  }
}

Hmm, bunu bir for döngüsü içinde yapamaz mıydık?

Neden, evet, özyineleme yinelemeyle değiştirilebilir, ancak genellikle özyineleme daha zariftir.

Ağaçlar hakkında konuşalım. Bilgisayar biliminde, bir ağaç düğümlerden oluşan bir yapıdır , burada her düğümün aynı zamanda düğüm veya null olan bir takım çocukları vardır. Bir ikili ağaç tam sahip düğümler yapılmış bir ağaçtır iki çocuk, genellikle "sol" ve "sağ" olarak adlandırılan; yine çocuklar düğüm veya boş olabilir. Bir kök başka düğümün çocuk olmayan bir düğümdür.

Bir düğümün, çocuklarına ek olarak bir değere, bir sayıya sahip olduğunu ve bir ağaçtaki tüm değerleri toplamak istediğimizi hayal edin.

Herhangi bir düğümdeki değeri toplamak için, düğümün değerini varsa sol alt öğesinin değerine ve varsa sağ alt öğesinin değerine eklerdik. Şimdi hatırlayın, eğer çocuklar boş değilse, aynı zamanda düğümdür.

Bu nedenle, sol çocuğu toplamak için, alt düğümün değerini, varsa, sol çocuğunun değerine ve varsa, sağ çocuğunun değerine eklerdik.

Bu nedenle, sol çocuğun sol çocuğunun değerini toplamak için, varsa çocuk düğümünün değerini, sol çocuğunun değerine ve varsa sağ çocuğunun değerine eklerdik.

Belki de bununla nereye gittiğimi tahmin ettiniz ve bazı kodlar görmek ister misiniz? TAMAM:

struct node {
  node* left;
  node* right;
  int value;
} ;

int sumNode( node* root ) {
  // if there is no tree, its sum is zero
  if( root == null ) {
    return 0 ;

  } else { // there is a tree
    return root->value + sumNode( root->left ) + sumNode( root->right ) ;
  }
}

Çocukları boş veya düğüm olup olmadıklarını görmek için açıkça sınamak yerine, yinelemeli işlevin boş bir düğüm için sıfıra dönmesini sağladığımıza dikkat edin.

Öyleyse şöyle görünen bir ağacımız var (sayılar değerler, eğik çizgiler çocuklara işaret ediyor ve @, işaretçi null'u gösteriyor):

     5
    / \
   4   3
  /\   /\
 2  1 @  @
/\  /\
@@  @@

Kökte sumNode (5 değerine sahip düğüm) çağırırsak, şunu döndüreceğiz:

return root->value + sumNode( root->left ) + sumNode( root->right ) ;
return 5 + sumNode( node-with-value-4 ) + sumNode( node-with-value-3 ) ;

Bunu yerinde genişletelim. SumNode'u gördüğümüz her yerde, bunu return ifadesinin genişletilmesiyle değiştireceğiz:

sumNode( node-with-value-5);
return root->value + sumNode( root->left ) + sumNode( root->right ) ;
return 5 + sumNode( node-with-value-4 ) + sumNode( node-with-value-3 ) ;

return 5 + 4 + sumNode( node-with-value-2 ) + sumNode( node-with-value-1 ) 
 + sumNode( node-with-value-3 ) ;  

return 5 + 4 
 + 2 + sumNode(null ) + sumNode( null )
 + sumNode( node-with-value-1 ) 
 + sumNode( node-with-value-3 ) ;  

return 5 + 4 
 + 2 + 0 + 0
 + sumNode( node-with-value-1 ) 
 + sumNode( node-with-value-3 ) ; 

return 5 + 4 
 + 2 + 0 + 0
 + 1 + sumNode(null ) + sumNode( null )
 + sumNode( node-with-value-3 ) ; 

return 5 + 4 
 + 2 + 0 + 0
 + 1 + 0 + 0
 + sumNode( node-with-value-3 ) ; 

return 5 + 4 
 + 2 + 0 + 0
 + 1 + 0 + 0
 + 3 + sumNode(null ) + sumNode( null ) ; 

return 5 + 4 
 + 2 + 0 + 0
 + 1 + 0 + 0
 + 3 + 0 + 0 ;

return 5 + 4 
 + 2 + 0 + 0
 + 1 + 0 + 0
 + 3 ;

return 5 + 4 
 + 2 + 0 + 0
 + 1 
 + 3  ;

return 5 + 4 
 + 2 
 + 1 
 + 3  ;

return 5 + 4 
 + 3
 + 3  ;

return 5 + 7
 + 3  ;

return 5 + 10 ;

return 15 ;

Şimdi, rasgele derinlik ve "dallanma" yapısını, bir kompozit şablonun tekrarlanan uygulaması olarak ele alarak nasıl fethettiğimizi görün. sumNode fonksiyonumuz aracılığıyla her seferinde tek bir düğüm / tek bir if / then dalını ve neredeyse spesifikasyonlarımızdan doğrudan kendileri yazan iki basit dönüş ifadesini kullanarak mı?

How to sum a node:
 If a node is null 
   its sum is zero
 otherwise 
   its sum is its value 
   plus the sum of its left child node
   plus the sum of its right child node

Özyineleme gücü budur .


Yukarıdaki vazo örneği kuyruk özyineleme örneğidir . Tüm bu kuyruk özyineleme , özyinelemeli fonksiyonda, eğer tekrarlarsak (yani, fonksiyonu tekrar çağırırsak), yaptığımız son şeydi.

Ağaç örneği kuyruk özyinelemeli değildi, çünkü yaptığımız son şey doğru çocuğu geri almaktı, biz yapmadan önce sol çocuğu tekrarladık.

Aslında, çocukları çağırdığımız ve mevcut düğümün değerini eklediğimiz düzen hiç önemli değildi, çünkü toplama değişmeli.

Şimdi siparişin önemli olduğu bir işleme bakalım. İkili bir düğüm ağacı kullanacağız, ancak bu sefer tutulan değer bir sayı değil bir karakter olacak.

Bizim ağaç herhangi düğüm için, onun karakteri geldiğini, özel mülkiyet olacak sonra (alfabetik sırayla) sol çocuk tarafından ve tutulan karakteri önce (alfabetik sırayla) sağ çocuk tarafından düzenlenen karakteri.

Yapmak istediğimiz şey ağacı alfabetik sıraya göre yazdırmak. Ağacın özel özelliği göz önüne alındığında bunu yapmak kolaydır. Sadece sol çocuğu, sonra düğümün karakterini, sonra sağ çocuğu yazdırıyoruz.

Biz sadece willy-nilly yazdırmak istemiyoruz, bu yüzden fonksiyonumuza yazdırılacak bir şey geçireceğiz. Bu, print (char) işlevine sahip bir nesne olacaktır; nasıl çalıştığı konusunda endişelenmemize gerek yok, sadece baskı çağrıldığında bir yere bir şey basacak.

Bunu kodda görelim:

struct node {
  node* left;
  node* right;
  char value;
} ;

// don't worry about this code
class Printer {
  private ostream& out;
  Printer( ostream& o ) :out(o) {}
  void print( char c ) { out << c; }
}

// worry about this code
int printNode( node* root, Printer& printer ) {
  // if there is no tree, do nothing
  if( root == null ) {
    return ;

  } else { // there is a tree
    printNode( root->left, printer );
    printer.print( value );
    printNode( root->right, printer );
}

Printer printer( std::cout ) ;
node* root = makeTree() ; // this function returns a tree, somehow
printNode( root, printer );

Şimdi önemli olan işlemlerin sırasına ek olarak, bu örnek bazı şeyleri özyinelemeli bir işleve geçirebileceğimizi göstermektedir. Yapmamız gereken tek şey, her yinelemeli çağrıda, onu iletmeye devam ettiğimizden emin olmaktır. Bir düğüm işaretçisini ve bir yazıcıyı işleve geçtik ve her özyinelemeli çağrıda, bunları "aşağı" geçirdik.

Şimdi ağacımız şöyle görünüyorsa:

         k
        / \
       h   n
      /\   /\
     a  j @  @
    /\ /\
    @@ i@
       /\
       @@

Ne yazdıracağız?

From k, we go left to
  h, where we go left to
    a, where we go left to 
      null, where we do nothing and so
    we return to a, where we print 'a' and then go right to
      null, where we do nothing and so
    we return to a and are done, so
  we return to h, where we print 'h' and then go right to
    j, where we go left to
      i, where we go left to 
        null, where we do nothing and so
      we return to i, where we print 'i' and then go right to
        null, where we do nothing and so
      we return to i and are done, so
    we return to j, where we print 'j' and then go right to
      null, where we do nothing and so
    we return to j and are done, so
  we return to h and are done, so
we return to k, where we print 'k' and then go right to
  n where we go left to 
    null, where we do nothing and so
  we return to n, where we print 'n' and then go right to
    null, where we do nothing and so
  we return to n and are done, so 
we return to k and are done, so we return to the caller

Yani sadece çizgilere bakarsak, basılırdık:

    we return to a, where we print 'a' and then go right to
  we return to h, where we print 'h' and then go right to
      we return to i, where we print 'i' and then go right to
    we return to j, where we print 'j' and then go right to
we return to k, where we print 'k' and then go right to
  we return to n, where we print 'n' and then go right to

Görüyoruz ki, gerçekten alfabetik sırada olan "ahijkn" yazdık.

Tek bir düğümü alfabetik sırayla nasıl yazdıracağını bilerek, tüm ağacı alfabetik sırayla yazdırmayı başarıyoruz. Bu sadece (ağacımızın alfabetik olarak sonraki değerlerin soluna değerler sipariş etme özelliğine sahip olduğu için), düğümün değerini yazdırmadan önce sol çocuğu yazdırmayı ve düğümün değerini yazdırdıktan sonra sağ çocuğu yazdırmayı bilmesi idi.

Ve bu özyinelemenin gücüdür: bütünün sadece bir parçasının nasıl yapılacağını bilerek (ve özyinelemenin ne zaman durdurulacağını bilerek) her şeyi yapabilmek.

Çoğu dilde, operatör || ("veya") ilk işlenen doğru olduğunda kısa devreler, genel özyinelemeli fonksiyon:

void recurse() { doWeStop() || recurse(); } 

Luc M yorum:

SO, bu tür bir cevap için bir rozet oluşturmalıdır. Tebrikler!

Teşekkürler Luc! Ama aslında, bu cevabı dört kereden fazla düzenlediğim için (son örneği eklemek, ancak çoğunlukla yazım hatalarını düzeltmek ve cilalamak için - küçük bir netbook klavyesine yazmak zor), bunun için daha fazla puan alamıyorum . Bu da beni gelecekteki cevaplara fazla çaba göstermekten caydırıyor.

Bu konudaki yorumuma bakın: /programming/128434/what-are-community-wiki-posts-in-stackoverflow/718699#718699


35

Beyniniz havaya uçtu çünkü sonsuz bir özyinelemeye girdi. Bu yaygın bir başlangıç ​​hatasıdır.

İster inanın ister inanmayın, özyinelemeyi zaten anlıyorsunuz, sadece bir işlev için ortak, ancak hatalı bir metafor tarafından sürükleniyorsunuz: içeri ve dışarı çıkan şeyleri içeren küçük bir kutu.

"İnternette özyineleme hakkında daha fazla bilgi edinin" gibi bir görev veya prosedür yerine düşünün. Bu özyinelemeli ve onunla bir problemin yok. Bu görevi tamamlamak için şunları yapabilirsiniz:

a) Google'ın "özyineleme" için sonuç sayfasını okuyun
b) Okuduktan sonra, üzerindeki ilk bağlantıyı takip edin ve ...
a.1) Özyineleme ile ilgili yeni sayfayı okuyun 
b.1) Okuduktan sonra, üzerindeki ilk bağlantıyı takip edin ve ...
a.2) Özyineleme ile ilgili yeni sayfayı okuyun 
b.2) Okuduktan sonra, üzerindeki ilk bağlantıyı takip edin ve ...

Gördüğünüz gibi, uzun süredir sorunsuz bir şekilde tekrarlayan şeyler yapıyorsunuz.

Ne kadar süre bu görevi yapmaya devam edersin? Beyniniz patlayana kadar sonsuza dek? Tabii ki hayır, görevi tamamladığınıza inandığınızda, belirli bir noktada duracaksınız.

Sizden "internette özyineleme hakkında daha fazla bilgi edinmenizi" isterken bunu belirtmenize gerek yoktur, çünkü siz bir insansınız ve bunu kendiniz çıkarabilirsiniz.

Bilgisayarın jakını çıkaramaz, bu nedenle açık bir son eklemeniz gerekir: "İnternette özyineleme hakkında daha fazla bilgi edinin, anlayana kadar veya en fazla 10 sayfa okudunuz ".

Ayrıca, Google'ın "özyineleme" için sonuç sayfasında başlamanız gerektiğini de belirttiniz ve yine bu bir bilgisayarın yapamayacağı bir şey. Özyinelemeli görevimizin tam açıklaması ayrıca açık bir başlangıç ​​noktası içermelidir:

"İnternette özyineleme hakkında daha fazla bilgi edinin, anlayana kadar veya en fazla 10 sayfa okuduktan ve www.google.com/search?q=recursion adresinden başlayabilirsiniz "

Her şeyi görmek için, şu kitaplardan birini denemenizi öneririz:

  • Ortak Lisp: Sembolik Hesaplamaya Nazik Bir Giriş. Bu özyinelemenin en tatlı matematiksel açıklamasıdır.
  • Küçük şema.

6
"Function = küçük I / O kutusu" metaforu, sonsuz klonlar üreten bir fabrika olduğunu ve küçük kutunuzun diğer küçük kutuları yutabileceğini hayal ettiğiniz sürece özyineleme ile çalışır.
ephemient

2
İlginç ... Yani, gelecekte robotlar bir şey google ve ilk 10 bağlantıları kullanarak kendi başlarına öğrenecek. :) :)
kumar

2
@kumar google bunu zaten internet ile yapmıyor ..?
TJ

1
harika kitaplar, tavsiye için teşekkürler
Max Koretskyi

+1 "Beyniniz havaya uçtu, çünkü sonsuz bir özyinelemeye girdi. Bu yaygın bir başlangıç ​​hatasıdır."
Stack Underflow

26

Özyinelemeyi anlamak için tek yapmanız gereken şampuan şişenizin etiketine bakmaktır:

function repeat()
{
   rinse();
   lather();
   repeat();
}

Bununla ilgili sorun, sonlandırma koşulu olmamasıdır ve özyineleme süresiz olarak veya şampuan veya sıcak su bitene kadar (yığınızı üflemeye benzer dış sonlandırma koşulları) tekrar eder.


6
Teşekkür ederim dar7yl - bu DAİMA şampuan şişeleri beni rahatsız etti. (Sanırım her zaman programlama için hedeflenmiştim). Her ne kadar ben talimatlar sonunda 'Tekrar' eklemeye karar verdi adam bahis şirket milyonlarca yaptı.
kenj0418

5
Umarım senden rinse()sonralather()
CoderDennis

@JakeWilson, kuyruk çağrısı optimizasyonu kullanılıyorsa - elbette. şu anda olduğu gibi - tamamen geçerli bir özyineleme.

1
@ dar7yl bu yüzden şampuan şişem her zaman boş ...
Brandon Ling

11

Özyinelemeyi basit bir şekilde açıklamak için iyi bir iş çıkaran bir kitap istiyorsanız, Gödel, Escher, Bach: Douglas Hofstadter, özellikle Bölüm 5'in bir Ebedi Altın Örgüsü'ne bakın . Özyinelemenin yanı sıra, bilgisayar bilimi ve matematikte anlaşılabilir bir şekilde karmaşık kavramlar, bir açıklama diğeri üzerine inşa edilmiştir. Daha önce bu tür kavramlara çok fazla maruz kalmadıysanız, oldukça akılda kalıcı bir kitap olabilir.


Ve daha sonra Hofstadter'ın diğer kitaplarında dolaşın. Şu anda en sevdiğim şiir çevirisi: Le Ton Beau do Marot . Tam olarak bir CS konusu değil, ancak çevirinin gerçekte ne anlama geldiği ve ne anlama geldiği hakkında ilginç sorunlar ortaya çıkarıyor.
RBerteig

9

Bu bir sorudan çok bir şikayettir. Özyineleme hakkında daha spesifik bir sorunuz mu var? Çarpma gibi, insanların çok şey yazdığı bir şey değil.

Çarpma işleminden bahsetmişken, bunu düşünün.

Soru:

* B nedir?

Cevap:

B 1 ise, a. Aksi takdirde, a + a * (b-1) olur.

* (B-1) nedir? Çözmenin bir yolu için yukarıdaki soruya bakın.


@Andrew Grimm: Güzel soru. Bu tanım, tamsayılar için değil, doğal sayılar içindir.
S.Lott

9

Bu çok basit yöntemin özyinelemeyi anlamanıza yardımcı olacağını düşünüyorum. Yöntem, belirli bir koşul geçerli olana kadar kendisini çağırır ve sonra döndürür:

function writeNumbers( aNumber ){
 write(aNumber);
 if( aNumber > 0 ){
  writeNumbers( aNumber - 1 );
 }
 else{
  return;
 }
}

Bu işlev, 0'a kadar besleyeceğiniz ilk sayıdaki tüm sayıları yazdırır. Böylece:

writeNumbers( 10 );
//This wil write: 10 9 8 7 6 5 4 3 2 1 0
//and then stop because aNumber is no longer larger then 0

Temel olarak ne olursa olsun, writeNumbers (10) 10 yazacak ve daha sonra 9 yazacak ve daha sonra writeNumber (8) vb. Çağıracak olan writeNumbers (9) 'u arayacaktır. popo writeNumbers (-1) 'i çağırmaz;

Bu kod aslında aynıdır:

for(i=10; i>0; i--){
 write(i);
}

Öyleyse neden bir for-loop'un aslında aynı olup olmadığını sorabileceğiniz özyineleme kullanın. Çoğunlukla döngüler için yuva yapmak istediğinizde özyineleme kullanırsınız, ancak ne kadar derin iç içe olduklarını bilmezsiniz. Örneğin, iç içe dizilerden öğeler yazdırırken:

var nestedArray = Array('Im a string', 
                        Array('Im a string nested in an array', 'me too!'),
                        'Im a string again',
                        Array('More nesting!',
                              Array('nested even more!')
                              ),
                        'Im the last string');
function printArrayItems( stringOrArray ){
 if(typeof stringOrArray === 'Array'){
   for(i=0; i<stringOrArray.length; i++){ 
     printArrayItems( stringOrArray[i] );
   }
 }
 else{
   write( stringOrArray );
 }
}

printArrayItems( stringOrArray );
//this will write:
//'Im a string' 'Im a string nested in an array' 'me too' 'Im a string again'
//'More nesting' 'Nested even more' 'Im the last string'

Bu işlev 100 seviyesine iç içe olabilen bir dizi alabilir, bir for döngüsü yazdığınızda 100 kez iç içe yerleştirmeniz gerekir:

for(i=0; i<nestedArray.length; i++){
 if(typeof nestedArray[i] == 'Array'){
  for(a=0; i<nestedArray[i].length; a++){
   if(typeof nestedArray[i][a] == 'Array'){
    for(b=0; b<nestedArray[i][a].length; b++){
     //This would be enough for the nestedAaray we have now, but you would have
     //to nest the for loops even more if you would nest the array another level
     write( nestedArray[i][a][b] );
    }//end for b
   }//endif typeod nestedArray[i][a] == 'Array'
   else{ write( nestedArray[i][a] ); }
  }//end for a
 }//endif typeod nestedArray[i] == 'Array'
 else{ write( nestedArray[i] ); }
}//end for i

Gördüğünüz gibi özyinelemeli yöntem çok daha iyi.


1
LOL - JavaScript kullandığınızı fark etmem bir saniye aldı! Ben "işlevi" gördüm ve PHP sonra değişkenleri $ ile başlamadı fark düşündüm. Sonra var kelimesinin kullanımı için C # düşündüm - ama yöntemler fonksiyonlar olarak adlandırılmaz!
ozzy432836

8

Aslında, elinizin altındaki sorunun karmaşıklığını azaltmak için özyineleme kullanırsınız. Kolayca çözülebilecek basit bir temel duruma ulaşana kadar özyineleme uygularsınız. Bununla son özyinelemeli adımı çözebilirsiniz. Ve bununla birlikte orijinal probleminize kadar tüm diğer yinelemeli adımlar.


1
Bu yanıta katılıyorum. İşin püf noktası, temel (en basit) durumu tanımlamak ve çözmek. Ve sonra sorunu en basit durum (daha önce çözdüğünüz) açısından ifade edin.
Sergio Acosta

6

Bir örnekle açıklamaya çalışacağım.

Neyi biliyorsun! anlamına geliyor? Değilse: http://en.wikipedia.org/wiki/Factorial

3! = 1 * 2 * 3 = 6

işte bazı sözde kod gidiyor

function factorial(n) {
  if (n==0) return 1
  else return (n * factorial(n-1))
}

Hadi deneyelim:

factorial(3)

n 0 mı?

Hayır!

bu yüzden özyinelememizle daha derinlere iniyoruz:

3 * factorial(3-1)

3-1 = 2

2 == 0 mı?

Hayır!

bu yüzden daha derine iniyoruz! 3 * 2 * faktöryel (2-1) 2-1 = 1

1 == 0 mı?

Hayır!

bu yüzden daha derine iniyoruz! 3 * 2 * 1 * faktöryel (1-1) 1-1 = 0

0 == 0 mı?

Evet!

önemsiz bir vakamız var

yani 3 * 2 * 1 * 1 = 6 var

umarım sana yardım eder


Bu, özyineleme hakkında düşünmenin yararlı bir yolu değildir. Başlayanlar yapmak Yaygın bir hata ne olduğunu hayal çalışmaktır içeride yerine / güvenen bu doğru cevabı dönecektir kanıtlamanın, recusive çağrısı - ve bu cevap o teşvik görünüyor.
ShreevatsaR

özyinelemeyi anlamanın daha iyi bir yolu ne olurdu? her özyinelemeli işleve bu şekilde bakmanız gerektiğini söylemiyorum. Ama nasıl çalıştığını anlamama yardımcı oldu.
Zoran Zaric

1
[-1 oy vermedim, BTW.] Böyle düşünebilirsiniz: faktöriyel (n-1) 'in doğru bir şekilde (n-1) verdiğine güvenmek ! = (N-1) * ... * 2 * 1, sonra n faktöriyel (n-1) n * olan n * (n-1) ... * 2 * 1 değerini verir. Ya da her neyse. [Özyinelemeli işlevlerin nasıl
yazıldığını

Özyinelemeyi açıklarken faktöriyeller kullandım ve bunun bir örnek olarak başarısız olmasının ortak nedenlerinden birinin, açıklayanın matematiği sevmemesi ve buna yakalanması olduğunu düşünüyorum. (Matematiği beğenmeyen birisinin kodlama yapması gerekip gerekmediği başka bir sorudur). Bu nedenle, mümkünse genellikle matematiksel olmayan bir örnek kullanmaya çalışıyorum.
Tony Meyer

5

özyineleme

Yöntem A, Yöntem A'yı çağırır. Yöntem A'yı çağırır. Sonuçta bu yöntemden biri, çağrı yapmaz ve çıkmaz, ancak bir şey kendini çağırdığı için özyineleme.

Sabit sürücüdeki her klasör adını yazdırmak istediğim özyineleme örneği: (c #)

public void PrintFolderNames(DirectoryInfo directory)
{
    Console.WriteLine(directory.Name);

    DirectoryInfo[] children = directory.GetDirectories();

    foreach(var child in children)
    {
        PrintFolderNames(child); // See we call ourself here...
    }
}

bu örnekte temel durum nerede?
Kunal Mukherjee

4

Hangi kitabı kullanıyorsunuz?

Algoritmalar hakkında aslında iyi olan standart ders kitabı Cormen & Rivest'dir. Benim deneyimim, özyinelemeyi oldukça iyi öğretmesidir.

Özyineleme, programlamayı kavramanın daha zor kısımlarından biridir ve içgüdü gerektirse de öğrenilebilir. Ancak iyi bir açıklamaya, iyi örneklere ve iyi örneklere ihtiyacı var.

Ayrıca, genel olarak 30 sayfa çok, tek bir programlama dilinde 30 sayfa kafa karıştırıcı. Genel olarak genel bir kitaptan özyinelemeyi anlamadan önce C veya Java'da özyineleme öğrenmeye çalışmayın.


4

Özyinelemeli işlev, kendisini gerektiği kadar çok çağıran bir işlevdir. Bir şeyi birden çok kez işlemeniz gerekiyorsa yararlıdır, ancak aslında kaç kez gerekli olacağından emin değilsiniz. Bir bakıma, özyinelemeli bir işlevi bir döngü türü olarak düşünebilirsiniz. Bununla birlikte, bir döngü gibi, sürecin kırılması için koşulları belirtmeniz gerekir, aksi takdirde sonsuz olur.


4

http://javabat.com özyineleme yapmak için eğlenceli ve heyecan verici bir yerdir. Onların örnekleri oldukça hafif başlar ve kapsamlı olarak çalışır (eğer o kadar ileri götürmek istiyorsanız). Not: Yaklaşımları pratik yaparak öğrenmektir. İşte bir for döngüsünü değiştirmek için yazdığım özyinelemeli bir işlev.

For döngüsü:

public printBar(length)
{
  String holder = "";
  for (int index = 0; i < length; i++)
  {
    holder += "*"
  }
  return holder;
}

İşte aynı şeyi yapmak için özyineleme. (tıpkı yukarıdaki gibi kullanıldığından emin olmak için ilk yöntemi aşırı yüklediğimize dikkat edin). Ayrıca dizinimizi korumak için başka bir yöntemimiz de var (for ifadesinin sizin için yukarıdaki gibi yaptığı gibi). Özyinelemeli işlev kendi dizinini korumalıdır.

public String printBar(int Length) // Method, to call the recursive function
{
  printBar(length, 0);
}

public String printBar(int length, int index) //Overloaded recursive method
{
  // To get a better idea of how this works without a for loop
  // you can also replace this if/else with the for loop and
  // operationally, it should do the same thing.
  if (index >= length)
    return "";
  else
    return "*" + printBar(length, index + 1); // Make recursive call
}

Uzun bir hikaye kısaltmak için, özyineleme daha az kod yazmak için iyi bir yoldur. İkinci printBar'da bir if deyimimiz olduğuna dikkat edin. Durumumuza ulaşılmışsa, özyinelemeden çıkacağız ve önceki yönteme geri dönen bir önceki yönteme geri döneceğiz. Bir printBar (8) içinde gönderirsem, ******** alırım. Ben belki bu yardımcı olacak bir for döngüsü ile aynı şeyi yapan basit bir fonksiyon örneği ile umuyorum. Yine de Java Bat'ta daha fazla pratik yapabilirsiniz.


javabat.com bir özyinelemeli düşünmek yardımcı olacak son derece yararlı bir web sitesidir. Oraya gitmeyi ve özyinelemeli sorunları kendi başına çözmeye çalışmayı öneririm.
Paradius

3

Özyinelemeli bir işlev oluşturmanın gerçek matematiksel yolu aşağıdaki gibi olacaktır:

1: f (n-1) için doğru bir fonksiyonunuz olduğunu düşünün, f (n) doğru olacak şekilde f'yi oluşturun. 2: f'yi (f (1) doğru olacak şekilde oluşturun.

Fonksiyonun matematiksel olarak doğru olduğunu ve İndüksiyon olarak adlandırıldığını nasıl kanıtlayabilirsiniz? . Farklı temel vakalara veya birden fazla değişken üzerinde daha karmaşık fonksiyonlara sahip olmak eşdeğerdir). Ayrıca f (x) 'in tüm x için doğru olduğunu hayal etmek de eşdeğerdir.

Şimdi "basit" bir örnek için. X sent yapmak için 5 kuruş ve 7 kuruş para kombinasyonunun mümkün olup olmadığını belirleyebilen bir işlev oluşturun. Örneğin, 2 x 5 x 1 x 7 x 17 sente sahip olmak mümkündür, ancak 16 sente sahip olmak imkansızdır.

Şimdi, x senti oluşturabildiğiniz sürece x sent oluşturmanın mümkün olup olmadığını söyleyen bir fonksiyonunuz olduğunu düşünün. Bu işlevi can_create_coins_small olarak adlandırın. N için işlevi nasıl yapacağınızı hayal etmek oldukça basit olmalıdır. Şimdi fonksiyonunuzu oluşturun:

bool can_create_coins(int n)
{
    if (n >= 7 && can_create_coins_small(n-7))
        return true;
    else if (n >= 5 && can_create_coins_small(n-5))
        return true;
    else
        return false;
}

Buradaki hile, can_create_coins'in n için çalıştığı gerçeğinin farkına varmaktır, can_create_coins_small yerine can_create_coins'i değiştirebileceğiniz anlamına gelir:

bool can_create_coins(int n)
{
    if (n >= 7 && can_create_coins(n-7))
        return true;
    else if (n >= 5 && can_create_coins(n-5))
        return true;
    else
        return false;
}

Yapılacak son şey, sonsuz özyinelemeyi durdurmak için temel bir davaya sahip olmaktır. Eğer 0 sent oluşturmaya çalışıyorsanız, bunun hiçbir bozuk para olmadan mümkün olduğunu unutmayın. Bu koşulu eklemek şunları sağlar:

bool can_create_coins(int n)
{
    if (n == 0)
        return true;
    else if (n >= 7 && can_create_coins(n-7))
        return true;
    else if (n >= 5 && can_create_coins(n-5))
        return true;
    else
        return false;
}

Sonsuz iniş denilen bir yöntem kullanılarak bu işlevin her zaman geri döneceği kanıtlanabilir , ancak burada gerekli değildir. F (n) 'nin yalnızca n'nin düşük değerlerini çağırdığını ve her zaman sonunda 0'a ulaşacağını hayal edebilirsiniz.

Hanoi Kulesi sorununuzu çözmek için bu bilgileri kullanmak için, hile n-1 tabletleri a'dan b'ye (herhangi bir a / b için) taşımak için bir işleve sahip olduğunuzu varsayarak n tabloları a'dan b'ye taşımaya çalışmak olduğunu düşünüyorum. .


3

Common Lisp'te basit özyinelemeli örnek :

MYMAP, listedeki her öğeye bir işlev uygular.

1) boş bir listenin öğesi yoktur, bu yüzden boş listeyi döndürürüz - () ve NIL her ikisi de boş listedir.

2) işlevi ilk listeye uygulayın, listenin geri kalanı için MYMAP'ı arayın (özyinelemeli çağrı) ve her iki sonucu da yeni bir listede birleştirin.

(DEFUN MYMAP (FUNCTION LIST)
  (IF (NULL LIST)
      ()
      (CONS (FUNCALL FUNCTION (FIRST LIST))
            (MYMAP FUNCTION (REST LIST)))))

İzlenen yürütmeyi izleyelim. Bir fonksiyona girildiğinde, argümanlar yazdırılır. Bir işlevden ÇIKIŞTIRILDIĞINDA, sonuç yazdırılır. Her özyinelemeli çağrı için, çıkış seviye girintili olacaktır.

Bu örnek, listedeki her sayıdaki SIN işlevini çağırır (1 2 3 4).

Command: (mymap 'sin '(1 2 3 4))

1 Enter MYMAP SIN (1 2 3 4)
| 2 Enter MYMAP SIN (2 3 4)
|   3 Enter MYMAP SIN (3 4)
|   | 4 Enter MYMAP SIN (4)
|   |   5 Enter MYMAP SIN NIL
|   |   5 Exit MYMAP NIL
|   | 4 Exit MYMAP (-0.75680256)
|   3 Exit MYMAP (0.14112002 -0.75680256)
| 2 Exit MYMAP (0.9092975 0.14112002 -0.75680256)
1 Exit MYMAP (0.841471 0.9092975 0.14112002 -0.75680256)

Bu bizim sonucumuz :

(0.841471 0.9092975 0.14112002 -0.75680256)

TÜM KAPAKLARDA NELER VAR? Yine de, CISP'de yaklaşık 20 yıl önce modası geçtiler.
Sebastian Krog

Bunu 17 yaşında Lisp Machine modelinde yazdım. Aslında işlevi dinleyicide biçimlendirme olmadan yazdım, bazı düzenleme yaptım ve daha sonra biçimlendirmek için PPRINT kullandım. Bu kodu CAPS'e çevirdi.
Rainer Joswig

3

Altı yaşında bir çocuğa özyinelemeyi açıklamak için, önce beş yaşında bir çocuğa açıklayın ve sonra bir yıl bekleyin.

Aslında, bu yararlı bir karşı örnektir, çünkü yinelemeli çağrınız daha basit değil, daha basit olmalıdır. Beş yaşında bir çocuğa özyinelemeyi açıklamak daha da zor olabilir ve özyinelemeyi 0'da durdurabilmenize rağmen, sıfır yaşındaki bir çocuğa özyinelemeyi açıklamak için basit bir çözümünüz yoktur.

Bir ya da daha fazla parçaya tekrarlama, birinci alt bölümlere ayırmayı kullanmaktan bir sorunu çözmek için daha basit aynı şekilde çözebileceğiniz soruna ve sonra sorun daha fazla özyineleme olmadan çözülecek kadar basit olduğunda, daha yüksek seviyelere geri dönebilirsiniz.

Aslında, bu bir sorunu özyineleme ile nasıl çözeceğinizin özyinelemeli bir tanımıydı.


3

Çocuklar dolaylı olarak özyineleme kullanır, örneğin:

Disney World'e Yolculuk

Henüz orada mıyız? (Hayır)

Henüz orada mıyız? (Yakında)

Henüz orada mıyız? (Neredeyse ...)

Henüz orada değil miyiz? (SHHHH)

Henüz varmadık mı?(!!!!!)

Çocuk hangi noktada uykuya dalar ...

Bu geri sayım işlevi basit bir örnektir:

function countdown()
      {
      return (arguments[0] > 0 ?
        (
        console.log(arguments[0]),countdown(arguments[0] - 1)) : 
        "done"
        );
      }
countdown(10);

Yazılım projelerine uygulanan Hofstadter Kanunu da geçerlidir.

İnsan dilinin özü, Chomsky'ye göre, sonlu beyinlerin sonsuz dilbilgisi olduğunu düşündüğü şeyi üretme yeteneğidir. Bununla, sadece söyleyebileceğimiz şeyde üst sınır olmadığı değil, dilimizin sahip olduğu cümle sayısında da bir üst sınır olmadığı, belirli bir cümlenin boyutunda üst sınır olmadığı anlamına gelir. Chomsky, insan dilinin tüm bu yaratıcılığının altında yatan temel aracın özyineleme olduğunu iddia etti: bir cümlenin aynı türden başka bir cümle içinde tekrarlama yeteneği. Eğer "John'un erkek kardeşinin evi" dersem, bir isim, "erkek kardeşim evi" şeklinde bir isim, "ev" var ve bu isim ifade başka bir isim deyiminde, "John'un erkek kardeşinin evi" şeklinde gerçekleşir. Bu çok mantıklı ve '

Referanslar


2

Özyinelemeli çözümlerle çalışırken, her zaman şunları yapmaya çalışırım:

  • İlk önce temel durumu oluşturun, yani faktöriyel çözümde n = 1 olduğunda
  • Diğer her durum için genel bir kural bulmaya çalışın

Ayrıca farklı özyinelemeli çözümler vardır, fraktallar ve diğerleri için yararlı olan böl ve fethet yaklaşımı vardır.

Ayrıca, sadece asmak için önce daha basit problemler üzerinde çalışabilirseniz de yardımcı olacaktır. Faktöriyeller için çözme ve nci fibonacci sayısının üretilmesi bazı örnekler.

Referanslar için, Robert Sedgewick'in Algoritmalarını tavsiye ederim.

Umarım yardımcı olur. İyi şanslar.


İlk önce genel bir kural olan yinelemeli çağrı ile başlamanın daha basit olup olmadığını merak ediyorum. O zaman temel dava, en basit durumun ne olduğuna göre açık hale gelmelidir. Bir problemi özyineli olarak çözmeyi düşünme şeklim budur.
dlaliberte

2

Ahh. Geçen yıl Hanoi Kulelerini bulmaya çalıştım. TOH ile ilgili zor olan şey, yinelemenin basit bir örneği değildir - her çağrıda kulelerin rollerini de değiştiren iç içe yinelemelere sahipsiniz. Bir anlam ifade etmenin tek yolu, halkaların hareketini zihnimin gözünde tam anlamıyla görselleştirmek ve özyinelemeli çağrının ne olacağını sözelleştirmekti. Tek bir halka ile başlayacağım, sonra iki, sonra üç. Oyunu internetten sipariş ettim. Onu almak için beynimi çatlatmam iki ya da üç gün sürdü.


1

Yinelemeli işlev, her çağrıda biraz sıkıştırdığınız bir yay gibidir. Her adımda, bir yığına biraz bilgi (geçerli bağlam) koyarsınız. Son adıma ulaşıldığında, yay tüm değerleri (bağlamları) bir kerede toplayarak serbest bırakılır!

Bu metaforun etkili olduğundan emin değilim ... :-)

Her neyse, klasik örneklerin ötesinde (verimsiz ve kolayca düzleştirildiği için en kötü örnek faktöriyel, Fibonacci, Hanoi ...) biraz yapay (nadiren, eğer gerçek programlama örneklerinde kullanıyorum) nerede kullanıldığını görmek ilginç.

Çok yaygın bir durum, bir ağacı yürümek (veya bir grafiktir, ancak ağaçlar genel olarak daha yaygındır).
Örneğin, bir klasör hiyerarşisi: dosyaları listelemek için bunları yineleyin. Bir alt dizin bulursanız, dosyaları listeleyen işlev kendisini yeni klasörle argüman olarak çağırır. Bu yeni klasörü (ve alt klasörlerini!) Listelemeye geri döndüğünde, içeriğini bir sonraki dosyaya (veya klasöre) geri alır.
Bir başka somut durum, GUI bileşenlerinin bir hiyerarşisini çizerken: bölmeler de olabilen bileşenleri veya bileşik bileşenleri vb. Tutmak için bölmeler gibi kaplara sahip olmak yaygındır. Boyama rutini, her bileşenin boya işlevini özyinelemeli olarak çağırır. sahip olduğu tüm bileşenlerin boya işlevini çağırır.

Çok net olup olmadığımdan emin değilim, ancak gerçekte öğretim materyallerinin kullanımını göstermeyi seviyorum, çünkü geçmişte tökezlediğim bir şeydi.


1

Bir işçi arı düşünün. Bal yapmaya çalışır. İşini yapar ve diğer işçi arıların balın geri kalanını yapmasını bekler. Ve petek dolduğunda durur.

Büyü olarak düşün. Uygulamaya çalıştığınız adla aynı ada sahip bir işleviniz var ve alt problemi verdiğinizde, bunu sizin için çözüyor ve yapmanız gereken tek şey, parçanızın çözümünü çözümle entegre etmektir. sana verdi.

Örneğin, bir listenin uzunluğunu hesaplamak istiyoruz. Şimdi magical_length fonksiyonumuzu ve magical_length ile büyülü yardımcımızı çağıralım. İlk öğeye sahip olmayan alt listeyi verirsek, bize alt listenin uzunluğunu sihirle vereceğini biliyoruz. O zaman düşünmemiz gereken tek şey bu bilginin işimize nasıl entegre edileceğidir. İlk öğenin uzunluğu 1'dir ve magic_counter bize n-1 alt listesinin uzunluğunu verir, bu nedenle toplam uzunluk (n-1) + 1 -> n

int magical_length( list )
  sublist = rest_of_the_list( list )
  sublist_length = magical_length( sublist ) // you can think this function as magical and given to you
  return 1 + sublist_length

Ancak bu cevap eksik çünkü boş bir liste verirsek ne olacağını düşünmedik. Listenin her zaman en az bir unsuru olduğunu düşündük. Bu nedenle, boş bir liste verilir ve cevap açıkça 0 ise, yanıtın ne olması gerektiğini düşünmemiz gerekir. Yani bu bilgiyi fonksiyonumuza ekleyin ve buna taban / kenar durumu denir.

int magical_length( list )
  if ( list is empty) then
    return 0
  else
    sublist_length = magical_length( sublist ) // you can think this function as magical and given to you
    return 1 + sublist_length
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.