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