Özyinelemeli işlevlerin nasıl çalıştığını anlama


115

Başlığın da açıkladığı gibi, henüz alay edemediğim çok temel bir programlama sorum var. Tüm (son derece akıllıca) "Özyinelemeyi anlamak için, önce özyinelemeyi anlamalısınız." çeşitli çevrimiçi konulardan yanıtlar hala tam olarak anlamıyorum.

Neyi bilmediğimizi bilmemekle karşı karşıya kaldığımızda, yanlış soruları sorma veya doğru soruları yanlış sorma eğiliminde olabileceğimizi anladığımızda, "düşündüğüm" şeyi paylaşacağım sorum, benzer bakış açısına sahip birinin bazılarını paylaşabileceği umuduyla benim için yinelemeli ampulü açmaya yardımcı olacak biraz bilgi!

İşte işlev (sözdizimi Swift ile yazılmıştır):

func sumInts(a: Int, b: Int) -> Int {
    if (a > b) {
        return 0
    } else {
        return a + sumInts(a: a + 1, b: b)
    }
}

2 ve 5'i argümanlarımız olarak kullanacağız:

println(sumInts(a: 2, b: 5))

Açıkçası cevap 14'tür. Ama bu değere nasıl ulaşıldığı konusunda net değilim.

Bunlar benim 2 takılmam:

  1. İşlev, bir koşul karşılanana kadar yinelemeli olarak çağrılır. Bu koşul a> b'dir. Bu koşul karşılandığında 0 döndürür. İlk bakışta, dönüş değerinin 0 olmasını beklerdim ki bu açıkça yanlıştır.

  2. Her yinelemede 'a' değerini yazdırmak beklediğim bir değer verir: 2, 3, 4, 5 (bu noktada 5 + 1> b ilk koşulu karşılayan: a> b) ama yine de yapmıyorum ' 14 değerine nasıl ulaşıldığını görün.

İlk düşüncem, aşağıdakine benzer bir şeyin sihirli bir şekilde gerçekleştiği:

var answer = a;
answer += a+1 until a > b;
return answer;   

Yani sihri dışlayarak, bir şey anlamıyorum. Neler olduğunu anlamaktan daha fazlasını isterim.

Eğer birisi bu tür bir işlev sırasında teknik olarak ne olduğunu ve sonucun neden 0 olmadığını ve sonunda nasıl a + sumInts(a: a + 1, b: b) = 14sonsuza kadar borcunuzda kalacağımı nazikçe açıklayabilirse .


7
Özyineleme, matematiksel terimlerle anlaşılması koddan çok daha kolay olan programlama kavramlarından biridir; burada iyi bir tanım var
blgt

5
LearnYouARecursion, birinci sınıf profesörden eksiksiz soru setleri!
recursion.ninja

15
Sadece var Google arama kutusuna "Özyinelemeyi" yazmanız çağırıyorum için. Şu Paskalya yumurtalarından biri. Sürprizi senin için bozmayacağım.
Floris


Yanıtlar:


107

Ben düşünüyorum karışıklık "Aynı işlevi" defalarca çağrıldığını olarak düşünüyordur kaynaklanmaktadır. Bunu "aynı işlevin birçok kopyası çağrılıyor" olarak düşünürseniz, o zaman daha net olabilir:

İşlevin yalnızca bir kopyası 0 değerini döndürür ve bu ilk değil (sonuncusu). Yani ilkini çağırmanın sonucu 0 değil.

İkinci bir kafa karışıklığı için, yinelemeyi İngilizce olarak hecelemenin daha kolay olacağını düşünüyorum. Bu satırı okuyun:

return a + sumInts(a + 1, b: b)

"a 'artı değerini döndür (işlevin başka bir kopyasının dönüş değeri, kopyanın' a 'artı değeri (işlevin başka bir kopyasının dönüş değeri, ikinci kopyanın değeri' ') a 'artı (... ", işlevin her kopyası, a> b koşulu karşılanana kadar 1 artırılarak kendisinin yeni bir kopyasını üretir.

A> b koşulunun doğru olduğuna ulaştığınızda, çalıştırılmanın ortasında (potansiyel olarak isteğe bağlı olarak) işlevin uzun bir kopya yığınına sahip olursunuz, hepsi bir sonraki kopyanın sonucunu beklerken bunların ne olduğunu öğrenmek için 'a'ya eklenmelidir.

(düzenleme: ayrıca, dikkat edilmesi gereken bir şey de, bahsettiğim işlevin kopyaları yığınının gerçek hafızayı kaplayan gerçek bir şey olduğudur ve çok büyük olursa programınızı çökertecektir. Derleyici bazı durumlarda onu optimize edebilir. durumlarda, ancak yorucu yığın alanı, birçok dilde özyinelemeli işlevlerin önemli ve talihsiz bir sınırlamasıdır)


7
Catfish_Man: Başardığını düşünüyorum! Bunu aynı işlevin birkaç "kopyası" olarak düşünmek tamamen mantıklı. Hala kafamı etrafına dolanıyorum ama sanırım beni doğru yola gönderdin! Yoğun bir gününüzde bir programcı arkadaşınıza yardım etmek için zaman ayırdığınız için teşekkürler! Cevabınızı doğru cevap olarak işaretleyeceğim. İyi günler!
Jason Elwood

13
Bu iyi bir benzetmedir - her bir "kopya" aslında tam olarak aynı kod olduğundan, bunu tam anlamıyla almamaya dikkat edin. Her kopya için farklı olan, üzerinde çalıştığı tüm verilerdir.
Tim B

2
Bir kopya olarak düşünmekten pek memnun değilim. Daha sezgisel bir açıklamanın, işlevin kendisini (kod, ne yaptığı) ve bir yığın çerçevesi / yürütme bağlamının ilişkilendirildiği bir işlev çağrısı (bu işlevin somutlaştırılması) ayırt etmek olduğunu buldum. İşlevin yerel değişkenleri yoktur, işlev çağrıldığında (çağrıldığında) somutlaştırılırlar. Ama sanırım bu, özyinelemeye bir giriş olacak
Thomas

5
Doğru terminoloji, işlevin birkaç çağrısı olmasıdır. Her çağrının kendi değişken örnekleri vardır ave b.
Theodore Norvell

6
Evet, bu cevaba eklenebilecek önemli miktarda hassasiyet var. "Bir işlevin örnekleri" ve "tek bir işlevin çağrılarının etkinleştirme kayıtları" arasındaki ayrımı kasıtlı olarak dışarıda bıraktım, çünkü sorunun anlaşılmasına gerçekten yardımcı olmayan ekstra kavramsal yüktü. Diğer problemleri anlamaya yardımcı olur , bu yüzden hala başka yerlerde yararlı bilgilerdir. Bu yorumlar bunun için güzel bir yer gibi görünüyor :)
Catfish_Man

130

1. İşlev, bir koşul karşılanana kadar özyinelemeli olarak çağrılır. Bu şarttır a > b. Bu koşul karşılandığında 0 döndürür. İlk bakışta, dönüş değerinin 0 olmasını beklerdim ki bu açıkça yanlıştır.

Bilgisayar bilişim yapabilseydi sumInts(2,5)şöyle düşünürdü:

I want to compute sumInts(2, 5)
for this, I need to compute sumInts(3, 5)
and add 2 to the result.
  I want to compute sumInts(3, 5)
  for this, I need to compute sumInts(4, 5)
  and add 3 to the result.
    I want to compute sumInts(4, 5)
    for this, I need to compute sumInts(5, 5)
    and add 4 to the result.
      I want to compute sumInts(5, 5)
      for this, I need to compute sumInts(6, 5)
      and add 5 to the result.
        I want to compute sumInts(6, 5)
        since 6 > 5, this is zero.
      The computation yielded 0, therefore I shall return 5 = 5 + 0.
    The computation yielded 5, therefore I shall return 9 = 4 + 5.
  The computation yielded 9, therefore I shall return 12 = 3 + 9.
The computation yielded 12, therefore I shall return 14 = 2 + 12.

Gördüğünüz gibi, fonksiyona yapılan bazı çağrılar sumIntsgerçekte 0'ı döndürür, ancak bu son değer değildir çünkü bilgisayarın son dört cümlesinde açıklandığı gibi yine de 0'a 5, sonra sonuca 4, sonra 3, sonra 2 eklemesi gerekir. bilgisayarımızın düşünceleri. Özyinelemede, bilgisayarın yalnızca özyinelemeli aramayı hesaplamak zorunda olmadığını, aynı zamanda özyinelemeli arama tarafından döndürülen değerle ne yapacağını da hatırlaması gerektiğini unutmayın. Bu tür bilgilerin kaydedildiği yığın adı verilen özel bir bilgisayar belleği alanı vardır, bu alan sınırlıdır ve çok özyinelemeli işlevler yığını tüketebilir: bu yığın taşmasıdır adını en sevdiğimiz web sitemize veren .

İfadeniz, bilgisayarın yinelemeli bir arama yaparken bulunduğu şeyi unuttuğuna dair örtük bir varsayım yapıyor gibi görünüyor, ancak unutmuyor, bu nedenle sonucunuz gözleminizle eşleşmiyor.

2. 'a' değerini her yinelemede yazdırmak beklediğim bir değer verir: 2, 3, 4, 5 (bu noktada 5 + 1> b ilk koşulu karşılayan: a> b) ama yine de 14 değerine nasıl ulaşıldığını görmüyorum.

Bunun nedeni, dönüş değerinin akendisi değil, değerinin ave özyinelemeli çağrı tarafından döndürülen değerin toplamı olmasıdır .


3
Bu harika cevabı yazmaya zaman ayırdığınız için teşekkürler Michael! 1!
Jason Elwood

9
@JasonElwood Belki sumInts“bilgisayar düşüncelerini” yazacak şekilde değiştirirseniz yardımcı olur . Bu tür işlevlerin bir elini yazdıktan sonra, muhtemelen “anladınız”!
Michael Le Barbier Grünewald

4
Ben orada olduğuna dikkat rağmen bu, iyi bir cevaptır gereklilik "yığın" olarak adlandırılan bir veri yapısı üzerinde fonksiyon aktivasyon gerçekleşmektedir söyledi. Özyineleme, devam eden geçiş stili ile uygulanabilir, bu durumda hiç yığın yoktur. Yığın sadece bir - özellikle etkilidir ve bu nedenle ortak kullanımda - devam etme kavramının somutlaştırılmasıdır.
Eric Lippert

1
@EricLippert Özyinelemeyi uygulamak için kullanılan teknikler başlı başına ilginç bir konu olsa da, "nasıl çalıştığını" anlamak isteyen OP için kullanılan çeşitli mekanizmalara maruz kalmanın yararlı olup olmayacağından emin değilim. Devam eden geçiş stili veya genişletme tabanlı diller (örneğin, TeX ve m4), özünde daha yaygın programlama paradigmalarından daha zor olmasa da, bu "egzotik" ve biraz beyaz yalan olarak etiketleyerek kimseyi rahatsız etmeyeceğim “her zaman açık olur gibi yığının” should OP'nin kavramı anlamasına yardımcı olur. (Ve her zaman bir tür yığın söz konusudur.)
Michael Le Barbier Grünewald

1
Yazılımın ne yaptığını hatırlaması, işlevi özyinelemeli olarak çağırması ve sonra geri döndüğünde o orijinal duruma geri dönmesi için bir yol olmalıdır. Bu mekanizma yığın gibi davranır, bu nedenle başka bir veri yapısı kullanılsa bile onu bir yığın olarak adlandırmak uygundur.
Barmar

48

Özyinelemeyi anlamak için problemi farklı bir şekilde düşünmelisiniz. Bir bütün olarak anlamlı olan büyük bir mantıksal adımlar dizisi yerine, büyük bir problemi alırsınız ve daha küçük problemlere ayrılırsınız ve bunları çözersiniz, alt problemlere bir cevap bulduğunuzda, alt problemlerin sonuçlarını birleştirerek daha büyük soruna çözüm. Siz ve arkadaşlarınızın büyük bir kovadaki misket sayısını saymaya ihtiyaç duyduğunuzu düşünün. Her biriniz daha küçük bir kova alıp bunları tek tek sayarsınız ve işiniz bittiğinde toplamları toplarsınız .. Pekala, eğer her biriniz bir arkadaş bulur ve kovaları daha da bölerseniz, o zaman diğer arkadaşların toplamlarını hesaplayın, her birinize geri getirin, toplayın. Ve bunun gibi.

İşlev kendini yinelemeli olarak her çağırdığında, sorunun bir alt kümesiyle yeni bir bağlam yarattığını hatırlamanız gerekir, bu parça çözüldüğünde, önceki yinelemenin tamamlanabilmesi için geri döndürülür.

Size adımları göstereyim:

sumInts(a: 2, b: 5) will return: 2 + sumInts(a: 3, b: 5)
sumInts(a: 3, b: 5) will return: 3 + sumInts(a: 4, b: 5)
sumInts(a: 4, b: 5) will return: 4 + sumInts(a: 5, b: 5)
sumInts(a: 5, b: 5) will return: 5 + sumInts(a: 6, b: 5)
sumInts(a: 6, b: 5) will return: 0

sumInts (a: 6, b: 5) yürütüldüğünde sonuçlar hesaplanabilir, böylece elde ettiğiniz sonuçlarla zinciri yedekleyin:

 sumInts(a: 6, b: 5) = 0
 sumInts(a: 5, b: 5) = 5 + 0 = 5
 sumInts(a: 4, b: 5) = 4 + 5 = 9
 sumInts(a: 3, b: 5) = 3 + 9 = 12
 sumInts(a: 2, b: 5) = 2 + 12 = 14.

Özyinelemenin yapısını temsil etmenin başka bir yolu:

 sumInts(a: 2, b: 5) = 2 + sumInts(a: 3, b: 5)
 sumInts(a: 2, b: 5) = 2 + 3 + sumInts(a: 4, b: 5)  
 sumInts(a: 2, b: 5) = 2 + 3 + 4 + sumInts(a: 5, b: 5)  
 sumInts(a: 2, b: 5) = 2 + 3 + 4 + 5 + sumInts(a: 6, b: 5)
 sumInts(a: 2, b: 5) = 2 + 3 + 4 + 5 + 0
 sumInts(a: 2, b: 5) = 14 

2
Çok iyi söyledin Rob. Bunu çok açık ve anlaşılması kolay bir şekilde ifade ettiniz. Zaman ayırdığınız için teşekkürler!
Jason Elwood

3
Bu, teoriye ve teknik detaylarına girmeden neler olup bittiğinin en net temsilidir, uygulamanın her adımını açıkça gösterir.
Bryan

2
Memnunum. :) Bunları açıklamak her zaman kolay olmuyor. İltifat için teşekkürler.
Rob

1
+1. Özellikle yapıya ilişkin son örneğinizle bunu böyle tanımlardım. Neler olduğunu görsel olarak ortaya çıkarmak faydalıdır.
KChaloux

40

Özyineleme, anlaşılması zor bir konudur ve burada bunu tam olarak yapabileceğimi sanmıyorum. Bunun yerine, burada sahip olduğunuz belirli kod parçasına odaklanmaya çalışacağım ve hem çözümün neden çalıştığına dair sezgiyi hem de kodun sonucunu nasıl hesapladığına dair mekaniği açıklamaya çalışacağım.

Burada verdiğiniz kod şu sorunu çözer: a'dan b'ye kadar olan tüm tamsayıların toplamını bilmek istiyorsunuz. Örneğiniz için, 2'den 5'e kadar olan sayıların toplamını istiyorsunuz.

2 + 3 + 4 + 5

Bir sorunu özyinelemeli olarak çözmeye çalışırken, ilk adımlardan biri, sorunu aynı yapıya sahip daha küçük bir soruna nasıl böleceğini bulmak olmalıdır. Diyelim ki 2'den 5'e kadar olan sayıları toplamak istiyorsunuz. Bunu basitleştirmenin bir yolu, yukarıdaki toplamın şu şekilde yeniden yazılabileceğini fark etmektir:

2 + (3 + 4 + 5)

Burada (3 + 4 + 5), 3 ile 5 arasındaki tüm tam sayıların toplamıdır. Diğer bir deyişle, 2 ile 5 arasındaki tüm tam sayıların toplamını bilmek istiyorsanız, 3 ile 5 arasındaki tüm tam sayıların toplamını hesaplayarak başlayın, sonra 2 ekleyin.

Peki 3 ile 5 arasındaki tüm tam sayıların toplamını nasıl hesaplarsınız? Peki, bu toplam

3 + 4 + 5

bunun yerine şu şekilde düşünülebilir

3 + (4 + 5)

Burada (4 + 5), 4 ile 5 arasındaki tüm tam sayıların toplamıdır. Dolayısıyla, 3 ile 5 arasındaki tüm sayıların toplamını hesaplamak isterseniz, 4 ile 5 arasındaki tüm tam sayıların toplamını hesaplar ve ardından 3 eklersiniz.

Burada bir model var! A ve b arasındaki tamsayıların toplamını hesaplamak istiyorsanız, aşağıdakileri yapabilirsiniz. İlk olarak, a + 1 ve b arasındaki tam sayıların toplamını hesaplayın. Ardından, bu toplama bir ekleyin. "A + 1 ve b arasındaki tam sayıların toplamını hesaplayın" ifadesinin, halihazırda çözmeye çalıştığımızla hemen hemen aynı türden bir problem olduğunu fark edeceksiniz, ancak biraz farklı parametrelerle. A'dan b'ye, her şey dahil, hesaplamak yerine, a + 1'den b'ye kadar hesaplama yapıyoruz. Bu yinelemeli adımdır - daha büyük sorunu çözmek için ("a'dan b'ye toplama, kapsayıcı"), sorunu kendisinin daha küçük bir versiyonuna indirgiyoruz ("a + 1'den b'ye toplamı dahil.").

Yukarıdaki koda bakarsanız, içinde şu adım olduğunu fark edeceksiniz:

return a + sumInts(a + 1, b: b)

Bu kod basitçe yukarıdaki mantığın bir çevirisidir - eğer a'dan b'ye (dahil) toplamak istiyorsanız, a + 1'den b'ye ( sumInts'ye özyinelemeli çağrıdır ) toplayarak başlayın , sonra ekleyin a.

Tabii ki, bu yaklaşım kendi başına gerçekten işe yaramayacak. Örneğin, 5 ile 5 arasındaki tüm tam sayıların toplamını nasıl hesaplarsınız? Şu anki mantığımızı kullanarak, 6 ile 5 arasındaki tüm tam sayıların toplamını hesaplayıp ardından 5 eklersiniz. Peki, 6 ile 5 arasındaki tüm tam sayıların toplamını nasıl hesaplarsınız? Şu anki mantığımızı kullanarak, 7 ile 5 arasındaki tüm tam sayıların toplamını hesaplayıp sonra 6'yı eklersiniz. Burada bir sorun olduğunu fark edeceksiniz - bu sadece devam ediyor!

Özyinelemeli problem çözmede, problemi basitleştirmeyi bırakmanın ve bunun yerine doğrudan çözmenin bir yolu olmalı. Tipik olarak, cevabın hemen belirlenebileceği basit bir vaka bulursunuz, ardından çözümünüzü basit vakaları ortaya çıktığında doğrudan çözecek şekilde yapılandırırsınız. Bu genellikle temel durum veya özyinelemeli temel olarak adlandırılır .

Öyleyse bu özel problemdeki temel durum nedir? A'dan b'ye kadar olan tam sayıları topladığınızda, a, b'den büyük olursa, o zaman cevap 0'dır - aralıkta hiç sayı yoktur! Bu nedenle, çözümümüzü şu şekilde yapılandıracağız:

  1. A> b ise cevap 0'dır.
  2. Aksi takdirde (a ≤ b), cevabı şu şekilde alın:
    1. A + 1 ve b arasındaki tam sayıların toplamını hesaplayın.
    2. Cevabı almak için bir ekleyin.

Şimdi bu sahte kodu gerçek kodunuzla karşılaştırın:

func sumInts(a: Int, b: Int) -> Int {
    if (a > b) {
        return 0
    } else {
        return a + sumInts(a + 1, b: b)
    }
}

Sözde kodda ana hatları verilen çözüm ile bu gerçek kod arasında neredeyse tam olarak bire bir harita olduğuna dikkat edin. İlk adım temel durumdur - boş bir sayı aralığının toplamını sorduğunuzda 0 elde edersiniz. Aksi takdirde, a + 1 ve b arasındaki toplamı hesaplayın ve sonra a ekleyin.

Şimdiye kadar, kodun arkasında sadece üst düzey bir fikir verdim. Ama çok güzel iki sorunuz daha vardı. İlk olarak, fonksiyon a> b ise 0 döndürmeyi söylediği halde bu neden her zaman 0 döndürmüyor? İkincisi, 14 aslında nereden geliyor? Sırayla bunlara bakalım.

Çok çok basit bir vakayı deneyelim. Ararsan ne olur sumInts(6, 5)? Bu durumda, kodun izini sürdüğünüzde, işlevin sadece 0 döndürdüğünü görürsünüz. Bu yapılacak doğru şeydir - aralıkta hiç sayı yoktur. Şimdi daha sert bir şey dene. Aradığınızda ne olur sumInts(5, 5)? İşte ne olacak:

  1. Sen ara sumInts(5, 5). Düşüyoruzelse“A + sumInts (6, 5) değerini döndüren dalın .
  2. İçin için sumInts(5, 5)ne olduğunu belirlemek için sumInts(6, 5), biz ne yaptığımızı durup bir çağrı yapmak gerekirsumInts(6, 5) .
  3. sumInts(6, 5)çağrılır. Şubeye girer ifve geri döner 0. Ancak, bu örneği sumIntstarafından çağrıldı sumInts(5, 5), bu nedenle dönüş değeri geri iletildisumInts(5, 5) üst düzey arayan kişiye değil, .
  4. sumInts(5, 5)şimdi 5 + sumInts(6, 5)geri dönmek için hesaplayabilir 5. Daha sonra üst düzey arayana geri gönderir.

Burada 5 değerinin nasıl oluştuğuna dikkat edin. Bir aktif çağrı ile başladık sumInts. Bu, başka bir özyinelemeli aramayı başlattı ve bu aramanın döndürdüğü değer, bilgiyi geri iletti sumInts(5, 5). Daha sumInts(5, 5)sonra yapılan çağrı, bir miktar hesaplama yaptı ve arayana bir değer geri verdi.

Bunu ile denerseniz sumInts(4, 5), olacaklar:

  • sumInts(4, 5)geri dönmeye çalışır 4 + sumInts(5, 5). Bunu yapmak için çağırıyor sumInts(5, 5).
    • sumInts(5, 5)geri dönmeye çalışır 5 + sumInts(6, 5). Bunu yapmak için çağırıyor sumInts(6, 5).
    • sumInts(6, 5)0 döndürür sumInts(5, 5).</li> <li>sumInts (5, 5) now has a value forsumInts (6, 5) , namely 0. It then returns5 + 0 = 5`.
  • sumInts(4, 5)şimdi sumInts(5, 5)5 için bir değere sahiptir . Daha sonra geri döner 4 + 5 = 9.

Başka bir deyişle, döndürülen değer, değerleri birer birer toplanarak oluşturulur, her seferinde belirli bir özyinelemeli çağrı tarafından döndürülen bir değer alınır sumIntsve geçerli değeri eklenir a. Özyineleme dibe vurduğunda, en derin çağrı 0 döndürür. Ancak, bu değer özyinelemeli çağrı zincirinden hemen çıkmaz; bunun yerine, değeri, üstündeki bir katman olan özyinelemeli çağrıya geri verir. Bu şekilde, her özyinelemeli çağrı yalnızca bir sayı daha ekler ve onu zincirin üst kısmına döndürerek genel toplamla sonuçlanır. Bir egzersiz olarak, bunun izini sürmeyi deneyin sumInts(2, 5), başlamak istediğiniz şey budur.

Bu yardımcı olur umarım!


3
Yoğun bir günden bu kadar kapsamlı bir cevabı paylaşmak için zaman ayırdığınız için teşekkürler! Burada, özyinelemeli işlevler hakkında kafamı kurmama yardımcı olan ve gelecekte bu yazıya rastlayan diğerlerine kesinlikle yardımcı olacak bir sürü harika bilgi var. Tekrar teşekkürler ve iyi günler!
Jason Elwood

22

Şimdiye kadar burada bazı iyi yanıtlarınız var, ancak farklı bir yol izleyen bir tane daha ekleyeceğim.

Öncelikle, ilginç bulabileceğiniz basit yinelemeli algoritmalar hakkında birçok makale yazdım; görmek

http://ericlippert.com/tag/recursion/

http://blogs.msdn.com/b/ericlippert/archive/tags/recursion/

Bunlar en üst sıradadır, bu yüzden en alttan başlayın.

İkincisi, şimdiye kadar tüm cevaplar, fonksiyon aktivasyonunu dikkate alarak özyinelemeli anlambilim tanımladı . Her aramanın yeni bir aktivasyon yaptığı ve özyinelemeli aramanın bu aktivasyon bağlamında yürütülmesi. Bu, onu düşünmenin iyi bir yolu, ancak eşdeğer bir yol daha var: akıllı metin ara ve değiştir .

İşlevinizi biraz daha kompakt bir biçimde yeniden yazmama izin verin; bunu belirli bir dilde olarak düşünmeyin.

s = (a, b) => a > b ? 0 : a + s(a + 1, b)

Umarım bu mantıklıdır. Koşullu operatöre aşina değilseniz, bu formdadır condition ? consequence : alternativeve anlamı netleşecektir.

Şimdi değerlendirmek isteyen s(2,5) işlevi gövdesi çağrısının yerine metinsel yaparak Biz bunu, daha sonra yerini aile 2ve bile 5:

s(2, 5) 
---> 2 > 5 ? 0 : 2 + s(2 + 1, 5)

Şimdi koşullu olanı değerlendirin. Biz metin olarak yerini 2 > 5ile false.

---> false ? 0 : 2 + s(2 + 1, 5)

Şimdi tüm yanlış koşulluları alternatifle ve tüm doğru koşulluları sonuçla metinsel olarak değiştirin. Yalnızca yanlış koşullara sahibiz, bu nedenle bu ifadeyi metinsel olarak alternatifle değiştiririz:

---> 2 + s(2 + 1, 5)

Şimdi, beni tüm bu +işaretleri yazmak zorunda bırakmaktan kurtarmak için , sabit aritmetiği metinsel olarak değeriyle değiştirin. (Bu biraz hile, ancak tüm parantezleri takip etmek istemiyorum!)

---> 2 + s(3, 5)

Şimdi ara ve değiştir, bu sefer arama gövdesi ile b 3için ave 5için. Çağrının yerini parantez içine alacağız:

---> 2 + (3 > 5 ? 0 : 3 + s(3 + 1, 5))

Ve şimdi aynı metinsel ikame adımlarını yapmaya devam ediyoruz:

---> 2 + (false ? 0 : 3 + s(3 + 1, 5))  
---> 2 + (3 + s(3 + 1, 5))                
---> 2 + (3 + s(4, 5))                     
---> 2 + (3 + (4 > 5 ? 0 : 4 + s(4 + 1, 5)))
---> 2 + (3 + (false ? 0 : 4 + s(4 + 1, 5)))
---> 2 + (3 + (4 + s(4 + 1, 5)))
---> 2 + (3 + (4 + s(5, 5)))
---> 2 + (3 + (4 + (5 > 5 ? 0 : 5 + s(5 + 1, 5))))
---> 2 + (3 + (4 + (false ? 0 : 5 + s(5 + 1, 5))))
---> 2 + (3 + (4 + (5 + s(5 + 1, 5))))
---> 2 + (3 + (4 + (5 + s(6, 5))))
---> 2 + (3 + (4 + (5 + (6 > 5 ? 0 : s(6 + 1, 5)))))
---> 2 + (3 + (4 + (5 + (true ? 0 : s(6 + 1, 5)))))
---> 2 + (3 + (4 + (5 + 0)))
---> 2 + (3 + (4 + 5))
---> 2 + (3 + 9)
---> 2 + 12
---> 14

Burada yaptığımız tek şey, basit metinsel ikameydi . Gerçekten, "2 + 1" yerine "3" ü değiştirmemeliydim ve bunu yapmam gerekene kadar, ama pedagojik olarak okumak zorlaşırdı.

İşlev etkinleştirme, işlev çağrısını çağrının gövdesiyle değiştirmekten ve biçimsel parametreleri karşılık gelen bağımsız değişkenlerle değiştirmekten başka bir şey değildir. Parantezleri akıllıca girerken dikkatli olmalısınız, ancak bunun dışında, sadece metin değiştirme.

Tabii ki, çoğu dil aslında yok uygulamak metin yedek olarak aktivasyon, ama mantıksal olarak olan budur.

Öyleyse sınırsız özyineleme nedir? Metin ikamesinin durmadığı bir özyineleme! Sonundas değiştirilecek daha fazla şeyin kalmadığı bir adıma nasıl vardığımıza dikkat edin ve o zaman sadece aritmetik için kuralları uygulayabiliriz.


İyi bir örnek, ancak daha karmaşık hesaplamalar yapmaya devam ettiğinizde kalbinizi kırıyor. Örneğin. İkili Ağaçta ortak atayı bulmak.
CodeYogi

11

Özyinelemeli bir fonksiyonun nasıl çalıştığını genellikle anlamanın yolu, temel duruma bakmak ve geriye doğru çalışmaktır. İşte bu işleve uygulanan teknik.

İlk olarak temel durum:

sumInts(6, 5) = 0

Ardından, çağrı yığınının hemen üzerindeki çağrı :

sumInts(5, 5) == 5 + sumInts(6, 5)
sumInts(5, 5) == 5 + 0
sumInts(5, 5) == 5

Ardından, çağrı yığınının hemen üzerindeki çağrı:

sumInts(4, 5) == 4 + sumInts(5, 5)
sumInts(4, 5) == 4 + 5
sumInts(4, 5) == 9

Ve bunun gibi:

sumInts(3, 5) == 3 + sumInts(4, 5)
sumInts(3, 5) == 3 + 9
sumInts(3, 5) == 12

Ve bunun gibi:

sumInts(2, 5) == 2 + sumInts(3, 5)
sumInts(4, 5) == 2 + 12
sumInts(4, 5) == 14

İşleve yönelik orijinal çağrımıza ulaştığımıza dikkat edin sumInts(2, 5) == 14

Bu çağrıların gerçekleştirilme sırası:

sumInts(2, 5)
sumInts(3, 5)
sumInts(4, 5)
sumInts(5, 5)
sumInts(6, 5)

Bu aramaların dönme sırası:

sumInts(6, 5)
sumInts(5, 5)
sumInts(4, 5)
sumInts(3, 5)
sumInts(2, 5)

Çağrıları dönme sırasına göre izleyerek işlevin nasıl çalıştığı hakkında bir sonuca vardığımızı unutmayın .


5

Bi şans tanıcam.

A + toplamları (a + 1, b) denklemini çalıştırarak, son cevabın nasıl 14 olduğunu göstereceğim.

//the sumInts function definition
func sumInts(a: Int, b: Int) -> Int {
    if (a > b) {
        return 0
    } else {
        return a + sumInts(a + 1, b)
    }
}

Given: a = 2 and b = 5

1) 2 + sumInts(2+1, 5)

2) sumInts(3, 5) = 12
   i) 3 + sumInts(3+1, 5)
   ii) 4 + sumInts(4+1, 5)
   iii) 5 + sumInts(5+1, 5)
   iv) return 0
   v) return 5 + 0
   vi) return 4 + 5
   vii) return 3 + 9

3) 2 + 12 = 14.

Başka sorunuz olursa bize bildirin.

Aşağıdaki örnekte yinelemeli işlevlerin başka bir örneğini burada bulabilirsiniz.

Bir adam üniversiteden yeni mezun oldu.

t, yıl olarak geçen süredir.

Emekli olmadan önce çalışılan toplam fiili yıl sayısı şu şekilde hesaplanabilir:

public class DoIReallyWantToKnow 
{
    public int howLongDoIHaveToWork(int currentAge)
    {
      const int DESIRED_RETIREMENT_AGE = 65;
      double collectedMoney = 0.00; //remember, you just graduated college
      double neededMoneyToRetire = 1000000.00

      t = 0;
      return work(t+1);
    }

    public int work(int time)
    {
      collectedMoney = getCollectedMoney();

      if(currentAge >= DESIRED_RETIREMENT_AGE 
          && collectedMoney == neededMoneyToRetire
      {
        return time;
      }

      return work(time + 1);
    }
}

Ve bu herkesi üzmek için yeterli olmalı, lol. ;-P


5

Özyineleme. Bilgisayar Bilimi'nde özyineleme, Sonlu Otomata başlığı altında derinlemesine ele alınmıştır.

En basit şekliyle bir öz referanstır. Örneğin, "arabam bir araba" demek yinelemeli bir ifadedir. Sorun şu ki, ifade asla bitmeyecek olan sonsuz bir özyinedir. Bir "araba" ifadesindeki tanım, bunun bir "araba" olduğudur, bu nedenle ikame edilebilir. Ancak, sonu yoktur çünkü ikame durumunda, yine de "arabam bir araba" olur.

İfade "arabam bentley. Arabam mavi" olsaydı bu farklı olabilirdi. Bu durumda, ikinci durumda araba için ikame "bentley" olabilir ve "benim bentley mavi" olur. Bu tür ikameler, Bağlamdan Bağımsız Gramerler aracılığıyla Bilgisayar Bilimi'nde matematiksel olarak açıklanmıştır. .

Gerçek ikame bir üretim kuralıdır. İfadenin S ile temsil edildiği ve arabanın bir "bentley" olabilen bir değişken olduğu göz önüne alındığında, bu ifade yinelemeli olarak yeniden yapılandırılabilir.

S -> "my"S | " "S | CS | "is"S | "blue"S | ε
C -> "bentley"

Bu, her |biri bir seçim olduğu anlamına geldiğinden , çeşitli şekillerde inşa edilebilir . Sbu seçeneklerden herhangi biri ile değiştirilebilir ve S her zaman boş başlar. εAraçlar üretim sonlandırması için yönlendirir. Tıpkı Sdeğiştirilebilir, böylece diğer değişkenler (orada sadece biridir ve öyle olabilir C"bentley" temsil olan).

Yani başlayarak Sboş olan ve ilk tercihi ile yerine "my"S Solur

"my"S

Sbir değişkeni temsil ettiği için hala ikame edilebilir. Tekrar "benim" i seçebiliriz veya bitirmek için ε olabiliriz, ama orijinal açıklamamızı yapmaya devam edelim. Sİle değiştirilen alanı seçiyoruz" "S

"my "S

Sonra C'yi seçelim

"my "CS

Ve C'nin değiştirme için yalnızca bir seçeneği var

"my bentley"S

Ve yine S için boşluk

"my bentley "S

Ve böyle devam eder "my bentley is"S, "my bentley is "S, "my bentley is blue"S,"my bentley is blue" (£ değerinin için S yerine üretime biter) ve biz yinelemeli bizim deyimi "Benim bentley mavidir" kurduk.

Özyinelemeyi bu üretimler ve değiştirmeler olarak düşünün. Süreçteki her adım, nihai sonucu üretmek için öncekinin yerini alır. 2'den 5'e kadar yinelemeli toplamın tam örneğinde, üretim ile sonuçlanırsınız

S -> 2 + A
A -> 3 + B
B -> 4 + C
C -> 5 + D
D -> 0

Bu olur

2 + A
2 + 3 + B
2 + 3 + 4 + C
2 + 3 + 4 + 5 + D
2 + 3 + 4 + 5 + 0
14

Sonlu durum otomatlarının veya bağlamdan bağımsız gramerlerin, özyineleme hakkında ilk sezginin oluşturulmasına yardımcı olabilecek en iyi örnekler olduğundan emin değilim. Güzel örneklerdir, ancak önceden CS geçmişi olmayan programcılara biraz yabancı olabilirler.
chi

4

Bence yinelemeli işlevleri anlamanın en iyi yolu, özyinelemeli veri yapılarını işlemek için yapıldıklarını fark etmektir. Ama orijinal işlevi sumInts(a: Int, b: Int)yinelemeli hesaplamamakta gelen sayıların toplamı o akadar b, bir özyinelemeli veri yapısı ... Hadi deneyin biraz değiştirilmiş versiyonu olarak görünmemektedir eklersiniz kaç sayılardır.sumInts(a: Int, n: Int)n

Şimdi, sumInts ndoğal bir sayı üzerinden özyinelemelidir . Hala yinelemeli veri değil, değil mi? Doğal bir sayı, Peano aksiyomları kullanılarak özyinelemeli bir veri yapısı olarak düşünülebilir:

enum Natural = {
    case Zero
    case Successor(Natural)
}

Yani, 0 = Sıfır, 1 = Sonraki (Sıfır), 2 = Sonraki (Sonraki (Sıfır)) vb.

Özyinelemeli bir veri yapınız olduğunda, işlev için şablonunuz olur. Yinelemeli olmayan her durum için, değeri doğrudan hesaplayabilirsiniz. Özyinelemeli durumlar için özyinelemeli fonksiyonun zaten çalıştığını varsayarsınız ve durumu hesaplamak için kullanırsınız, ancak argümanı yapısızlaştırırsınız. Natural durumunda, bunun yerine anlamına gelir Succesor(n)biz kullanacağız nyerine eşdeğer olarak ya nbiz kullanacağız n - 1.

// sums n numbers beginning from a
func sumInts(a: Int, n: Int) -> Int {
    if (n == 0) {
        // non recursive case
    } else {
        // recursive case. We use sumInts(..., n - 1)
    }
}

Artık özyinelemeli işlevi programlamak daha kolaydır. İlk olarak, temel durum,n=0 . Numara eklemek istemiyorsak ne iade etmeliyiz? Cevap elbette 0.

Özyinelemeli durum ne olacak? nİle başlayan sayılar eklemek istiyorsak ave zaten işe yarayan bir çalışma sumIntsfonksiyonumuz varsa n-1? Eh, eklemeniz gerekir ave daha sonra çağırmak sumIntsile a + 1biz ile sona böylece:

// sums n numbers beginning from a
func sumInts(a: Int, n: Int) -> Int {
    if (n == 0) {
        return 0
    } else {
        return a + sumInts(a + 1, n - 1)
    }
}

Güzel olan şey, şimdi düşük seviyede özyineleme düşünmenize gerek olmamasıdır. Sadece şunları doğrulamanız gerekiyor:

  • Özyinelemeli verilerin temel durumları için, özyinelemeyi kullanmadan yanıtı hesaplar.
  • Özyinelemeli verilerin özyinelemeli durumları için, yok edilen veriler üzerinde özyinelemeyi kullanarak yanıtı hesaplar.

4

Nisan ve Schocken'in işlevler uygulaması ilginizi çekebilir . Bağlantılı pdf, ücretsiz bir çevrimiçi kursun parçasıdır. Öğrencinin sanal makine dilinden makine diline derleyici yazması gereken sanal makine uygulamasının ikinci bölümünü açıklar. Önerdikleri işlev gerçeklemesi, yığın tabanlı olduğu için özyineleme yeteneğine sahiptir.

Sizi işlev uygulamasına tanıtmak için: Aşağıdaki sanal makine kodunu düşünün:

görüntü açıklamasını buraya girin

Swift bu sanal makine diline derlediyse, aşağıdaki Swift kodu bloğu:

mult(a: 2, b: 3) - 4

aşağı derlemek

push constant 2  // Line 1
push constant 3  // Line 2
call mult        // Line 3
push constant 4  // Line 4
sub              // Line 5

Sanal makine dili, küresel bir yığın etrafında tasarlanmıştır .push constant nbu global yığına bir tamsayı iter.

Satır 1 ve 2'yi çalıştırdıktan sonra, yığın şöyle görünür:

256:  2  // Argument 0
257:  3  // Argument 1

256ve 257hafıza adresleridir.

call mult dönüş satırı numarasını (3) yığına iter ve işlevin yerel değişkenleri için alan ayırır.

256:  2  // argument 0
257:  3  // argument 1
258:  3  // return line number
259:  0  // local 0

... ve etikete gider function mult. İçindeki kod multçalıştırılır. Bu kodu çalıştırmanın bir sonucu olarak, fonksiyonun 0. yerel değişkeninde saklanan 2 ve 3'ün çarpımını hesaplıyoruz.

256:  2  // argument 0
257:  3  // argument 1
258:  3  // return line number
259:  6  // local 0

Mult'dan hemen önce returnşu satırı fark edeceksiniz:

push local 0  // push result

Ürünü yığının üzerine iteceğiz.

256:  2  // argument 0
257:  3  // argument 1
258:  3  // return line number
259:  6  // local 0
260:  6  // product

Döndüğümüzde aşağıdakiler olur:

  • Yığın üzerindeki son değeri 0. argümanın hafıza adresine (bu durumda 256) açın. Burası onu yerleştirmek için en uygun yer.
  • Yığın üzerindeki her şeyi 0. argümanın adresine kadar atın.
  • Dönüş satır numarasına gidin (bu durumda 3) ve sonra ilerleyin.

Döndükten sonra 4. satırı çalıştırmaya hazırız ve yığımız şuna benzer:

256:  6  // product that we just returned

Şimdi 4'ü yığının üzerine itiyoruz.

256:  6
257:  4

subsanal makine dilinin ilkel bir işlevidir. İki argüman alır ve sonucunu olağan adreste döndürür: 0. argümanınki.

Şimdi sahibiz

256:  2  // 6 - 4 = 2

Artık bir işlev çağrısının nasıl çalıştığını bildiğinize göre, özyinelemenin nasıl çalıştığını anlamak görece basittir. Sihir yok , sadece bir yığın.

sumIntsİşlevinizi bu sanal makine dilinde uyguladım :

function sumInts 0     // `0` means it has no local variables.
  label IF
    push argument 0
    push argument 1
    lte              
    if-goto ELSE_CASE
    push constant 0
    return
  label ELSE_CASE
    push constant 2
    push argument 0
    push constant 1
    add
    push argument 1
    call sumInts       // Line 15
    add                // Line 16
    return             // Line 17
// End of function

Şimdi onu arayacağım:

push constant 2
push constant 5
call sumInts           // Line 21

Kod çalışır ve ltegeri döndüğü durma noktasına kadar ulaşırız false. Yığın bu noktada şuna benziyor:

// First invocation
256:  2   // argument 0
257:  5   // argument 1
258:  21  // return line number
259:  2   // augend
// Second
260:  3   // argument 0
261:  5   // argument 1
262:  15  // return line number
263:  3   // augend
// Third
264:  4   // argument 0
265:  5   // argument 1
266:  15  // return line number
267:  4   // augend
// Fourth
268:  5   // argument 0
269:  5   // argument 1
270:  15  // return line number
271:  5   // augend
// Fifth
272:  6   // argument 0
273:  5   // argument 1
274:  15  // return line number
275:  0   // return value

Şimdi özyinememizi "gevşetelim". return0, satır 15'e gidin ve ilerleyin.

271:  5
272:  0

16. Satır: add

271:  5

Satır 17: return5 ve satır 15'e gidin ve ilerleyin.

267:  4
268:  5

16. Satır: add

267:  9

Satır 17: return9 ve satır 15'e gidin ve ilerleyin.

263:  3
264:  9

16. Satır: add

263:  12

Satır return17:12 ve satır 15'e gidin ve ilerleyin.

259:  2
260:  12

16. Satır: add

259:  14

Satır return17:14 ve satır 21'e gidin ve ilerleyin.

256:  14

İşte aldın. Özyineleme: Glorified goto.


4

Özyinelemeyi öğrenmek ve gerçekten anlamak konusunda karşılaştığım gerçekten iyi bir ipucu, özyineleme dışında herhangi bir döngü yapısı olmayan bir dili öğrenmek için biraz zaman harcamaktır. Bu şekilde, pratik yoluyla özyinelemeyi nasıl kullanacağınız konusunda harika bir fikir edineceksiniz.

Bir Scheme öğreticisi olmanın yanı sıra mimari ve tasarım açısından programların nasıl tasarlanacağına dair harika bir giriş olan http://www.htdp.org/ adresini takip ettim .

Ama temelde biraz zaman ayırmanız gerekiyor. Özyinelemenin 'sağlam' bir kavrayışı olmadan, geri izleme gibi belirli algoritmalar size her zaman 'zor' ve hatta 'sihirli' görünecektir. Yani, sebat edin. :-D

Umarım bu yardımcı olur ve İyi Şanslar!


3

Zaten birçok iyi cevap var. Yine de deniyorum.
Bir işlev çağrıldığında, arayan işlevinin bellek alanı üzerine yığılmış bir bellek alanı tahsis edilir . Bu hafıza alanında fonksiyon, kendisine aktarılan parametreleri, değişkenleri ve değerlerini tutar. Bu hafıza alanı , fonksiyonun biten dönüş çağrısıyla birlikte kaybolur. Yığın fikri ilerledikçe, hafıza alanı arayan işlevinin artık aktif hale gelir.

Özyinelemeli çağrılar için, aynı işlev üst üste yığılmış birden çok bellek alanı alır . Bu kadar. Yığının bir bilgisayarın belleğinde nasıl çalıştığına dair basit fikir, uygulamada özyinelemenin nasıl olduğu fikrini size ulaştıracaktır.


3

Biraz konu dışı, biliyorum ama ... Google'da özyinelemeye bakmayı dene ... Bunun ne anlama geldiğini örnekle göreceksin :-)


Google'ın önceki sürümleri aşağıdaki metni döndürdü (bellekten alıntı):

özyineleme

Özyinelemeyi Gör

10 Eylül 2014'te özyineleme ile ilgili şaka güncellendi:

özyineleme

Şunu mu demek istediniz: Recursion


Başka bir yanıt için bu yanıta bakın .


3

Özyinelemeyi çoklu klonlar olarak düşünün aynı şeyi yapan ...

[1] 'i kopyalamak istiyorsun: "2 ile 5 arasındaki sayıları toplamı"

+ clone[1]               knows that: result is 2 + "sum numbers between 3 and 5". so he asks to clone[2] to return: "sum numbers between 3 and 5"
|   + clone[2]           knows that: result is 3 + "sum numbers between 4 and 5". so he asks to clone[3] to return: "sum numbers between 4 and 5"
|   |   + clone[3]       knows that: result is 4 + "sum numbers between 5 and 5". so he asks to clone[4] to return: "sum numbers between 5 and 5"
|   |   |   + clone[4]   knows that: result is 5 + "sum numbers between 6 and 5". so he asks to clone[5] to return: "sum numbers between 6 and 5"
|   |   |   |   clone[5] knows that: he can't sum, because 6 is larger than 5. so he returns 0 as result.
|   |   |   + clone[4]   gets the result from clone[5] (=0)  and sums: 5 + 0,  returning 5
|   |   + clone[3]       gets the result from clone[4] (=5)  and sums: 4 + 5,  returning 9
|   + clone[2]           gets the result from clone[3] (=9)  and sums: 3 + 9,  returning 12
+ clone[1]               gets the result from clone[2] (=12) and sums: 2 + 12, returning 14

ve voilá !!


2

Yukarıdaki cevapların çoğu çok iyi. Yinelemeyi çözmek için yararlı bir teknik, ilk önce ne yapmak istediğimizi açıklamak ve bir insanın çözeceği gibi kodlamaktır. Yukarıdaki durumda, ardışık tamsayılar dizisini özetlemek istiyoruz (yukarıdaki sayıları kullanarak):

2, 3, 4, 5  //adding these numbers would sum to 14

Şimdi, bu satırların kafa karıştırıcı (yanlış değil ama kafa karıştırıcı) olduğuna dikkat edin.

if (a > b) {
    return 0 
}

Neden test a>b? Ve nedenreturn 0

Bir insanın yaptığını daha yakından yansıtmak için kodu değiştirelim

func sumInts(a: Int, b: Int) -> Int {
  if (a == b) {
    return b // When 'a equals b' I'm at the most Right integer, return it
  }
  else {
    return a + sumInts(a: a + 1, b: b)
  }
}

Bunu daha da insani yapabilir miyiz? Evet! Genellikle soldan sağa doğru toplarız (2 + 3 + ...). Ancak yukarıdaki özyineleme sağdan sola doğru toplanıyor (... + 4 + 5). Kodu yansıtacak şekilde değiştirin ( -Biraz korkutucu olabilir, ancak çok fazla değil)

func sumInts(a: Int, b: Int) -> Int {
  if (a == b) {
    return b // When I'm at the most Left integer, return it
  }
  else {
    return sumInts(a: a, b: b - 1) + b
  }
}

Bazıları bu işlevi daha kafa karıştırıcı bulabilir, çünkü 'en uç' uçtan başlıyoruz, ancak pratik yapmak onu doğal hissettirebilir (ve bir başka iyi 'düşünme' tekniğidir: Bir özyineleme çözerken 'her iki' tarafı da denemek). Ve yine, fonksiyon bir insanın (en çok?) Yaptığı şeyi yansıtır: Soldaki tüm tam sayıların toplamını alır ve 'sonraki' sağ tamsayıyı ekler.


2

Özyinelemeyi anlamakta zorlanıyordum, sonra bu blogu buldum ve bu soruyu zaten gördüm, bu yüzden paylaşmam gerektiğini düşündüm. Bu blogu okumalısınız, bunu son derece yararlı buldum, yığınla açıklıyor ve hatta iki özyinelemenin yığınla nasıl çalıştığını adım adım açıklıyor. Öncelikle yığının nasıl çalıştığını anlamanızı tavsiye ederim ki burada çok iyi açıklıyor: yığına yolculuk

then now you will understand how recursion works now take a look of this post: Özyinelemeyi adım adım anlayın

görüntü açıklamasını buraya girin

Bu bir program:

def hello(x):
    if x==1:
        return "op"
    else:
        u=1
        e=12
        s=hello(x-1)
        e+=1
        print(s)
        print(x)
        u+=1
    return e

hello(3)

görüntü açıklamasını buraya girin görüntü açıklamasını buraya girin


2

Özyineleme, başkalarının onun hakkında söylediklerini okumayı bırakıp onu kaçınabileceğim bir şey olarak görmeyi bırakıp sadece kod yazdığımda bana anlamlı gelmeye başladı. Bir çözümle ilgili bir sorun buldum ve çözümü bakmadan kopyalamaya çalıştım. Çözüme sadece çaresizce takılıp kaldığım zaman baktım. Sonra onu kopyalamaya çalıştım. Özyinelemeli bir problemi nasıl tanımlayıp çözeceğime dair kendi anlayışımı ve hissimi geliştirene kadar bunu birden fazla problem üzerinde tekrar yaptım. Bu seviyeye geldiğimde problemler oluşturmaya ve çözmeye başladım. Bu bana daha çok yardımcı oldu. Bazen şeyler ancak kendi başınıza deneyerek ve mücadele ederek öğrenilebilir; "anlayana" kadar.


0

Fibonacci serisinden bir örnek ile anlatayım, Fibonacci

t (n) = t (n - 1) + n;

eğer n = 0 ise 1

böylece tekrarlama çalışmaları, sadece yerine görelim niçinde t(n)olan n-1ve böyle devam eder. görünüyor:

t (n-1) = t (n - 2) + n + 1;

t (n-1) = t (n - 3) + n + 1 + n;

t (n-1) = t (n - 4) + n + 1 + n + 2 + n;

.

.

.

t (n) = t (nk) + ... + (nk-3) + (nk-2) + (nk-1) + n;

eğer bildiğimiz t(0)=(n-k)için eşittir 1sonra n-k=0bu yüzden n=kbiz değiştirin kile n:

t (n) = t (nn) + ... + (n-n + 3) + (n-n + 2) + (n-n + 1) + n;

atlarsak n-n:

t (n) = t (0) + ... + 3 + 2 + 1 + (n-1) + n;

yani 3+2+1+(n-1)+n doğal sayıdır. olarak hesaplarΣ3+2+1+(n-1)+n = n(n+1)/2 => n²+n/2

fib için sonuç: O(1 + n²) = O(n²)

Yinelemeli ilişkiyi anlamanın en iyi yolu bu

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.