Aşağıdan yukarıya ve yukarıdan aşağıya arasındaki fark nedir?


177

Aşağıdan yukarıya (dinamik programlamaya) yaklaşımı ilk "küçük" alt problemlerden bakarak oluşur ve sonra daha küçük sorunlara çözüm kullanarak daha büyük altproblemleri çözer.

Yukarıdan aşağıya , sorunun "doğal bir şekilde" çözülmesinden ve daha önce alt problemin çözümünü hesaplayıp hesaplamadığınızdan emin olmanız gerekir.

Biraz kafam karıştı. Bu ikisi arasındaki fark nedir?


Yanıtlar:


247

rev4: Sammaron kullanıcısı tarafından yapılan çok anlamlı bir yorum, belki de bu cevabın daha önce yukarıdan aşağıya ve aşağıdan yukarıya karıştırdığını belirtti. Başlangıçta bu cevap (rev3) ve diğer cevaplar "aşağıdan yukarıya memoization" ("alt problemleri varsayalım") dediğinde, ters olabilir (yani, "yukarıdan aşağıya" "alt problemleri varsayalım" ve " aşağıdan yukarıya "," alt problemleri oluştur "olabilir). Daha önce, hatırlamanın dinamik programlama alt türünün aksine farklı türde bir dinamik programlama olduğunu okumuştum. Abone olmamakla birlikte bu bakış açısını aktarıyordum. Literatürde uygun referanslar bulunana kadar bu cevabı terminolojiden bağımsız olarak yeniden yazdım. Bu yanıtı bir topluluk wikisine de dönüştürdüm. Lütfen akademik kaynakları tercih edin. Referans listesi:} {Edebiyat: 5 }

tekrarlamak

Dinamik programlama, hesaplamalarınızı yinelenen çalışmaların yeniden hesaplanmasını önleyecek şekilde sipariş etmekle ilgilidir. Ana bir probleminiz var (alt problemler ağacınızın kökü) ve alt problemler (alt ağaçlar). Alt problemler genellikle tekrarlanır ve çakışır .

Örneğin, favori Fibonnaci örneğini düşünün. Eğer saf bir özyinelemeli çağrı yaparsak, bu alt problemlerin tam ağacıdır:

TOP of the tree
fib(4)
 fib(3)...................... + fib(2)
  fib(2)......... + fib(1)       fib(1)........... + fib(0)
   fib(1) + fib(0)   fib(1)       fib(1)              fib(0)
    fib(1)   fib(0)
BOTTOM of the tree

(Bazı diğer nadir problemlerde, bu ağaç bazı dallarda sonsuz olabilir, bu da sonlandırılmayı temsil eder ve bu nedenle ağacın tabanı sonsuz büyük olabilir. Ayrıca, bazı problemlerde tam ağacın neye benzediğini bilmiyor olabilirsiniz. Bu nedenle, hangi alt sorunların ortaya çıkacağına karar vermek için bir strateji / algoritmaya ihtiyacınız olabilir.)


Hatırlama, Tablolama

Karşılıklı olarak dışlanmayan en az iki ana dinamik programlama tekniği vardır:

  • Memoization - Bu laissez-faire bir yaklaşımdır: Tüm alt problemleri zaten hesapladığınızı ve en uygun değerlendirme sırasının ne olduğu hakkında hiçbir fikriniz olmadığını varsayarsınız. Genellikle, kökten özyinelemeli bir çağrı (veya yinelemeli bir eşdeğer) gerçekleştirirsiniz ve ya en uygun değerlendirme sırasına yaklaşacağınızı ya da en uygun değerlendirme sırasına ulaşmanıza yardımcı olacağınıza dair bir kanıt elde edeceğinizi umarsınız. Sonuçları önbelleğe aldığınız için özyinelemeli çağrının hiçbir zaman bir alt sorunu yeniden hesaplamamasını sağlar ve böylece yinelenen alt ağaçlar yeniden hesaplanmaz.

    • Örnek: Fibonacci dizisini hesaplıyorsanız, fib(100)bunu sadece çağırırsınız ve bu fib(100)=fib(99)+fib(98), çağıracak fib(99)=fib(98)+fib(97), ... vs ... diyecektir fib(2)=fib(1)+fib(0)=1+0=1. Sonra nihayet çözülecekti fib(3)=fib(2)+fib(1), ama yeniden hesaplamaya gerek yok fib(2), çünkü önbelleğe aldık.
    • Bu ağacın tepesinden başlar ve yapraklardan / alt ağaçlardan gelen alt problemleri köke doğru değerlendirir.
  • Tablolama - Dinamik programlamayı "tablo doldurma" algoritması olarak da düşünebilirsiniz (genellikle çok boyutlu olsa da, bu 'tablo' çok nadir durumlarda Öklid olmayan geometriye sahip olabilir *). Bu, notlama gibidir ancak daha aktiftir ve bir adım daha içerir: Hesaplamalarınızı yapacağınız zamanı önceden seçmelisiniz. Bu, siparişin statik olması gerektiği anlamına gelmemeli, ancak nottan çok daha fazla esnekliğe sahip olduğunuz anlamına gelmelidir.

    • örnek: Eğer fibonacci yapıyorsanız, bu sırayla sayıları hesaplamak için tercih edebilirsiniz: fib(2), fib(3), fib(4)... daha kolay gelecek olanlar hesaplayabilir böylece her değeri önbelleğe. Ayrıca bir tabloyu doldurmanın (başka bir önbellekleme biçimi) olduğunu düşünebilirsiniz.
    • Şahsen 'tablolama' kelimesini çok fazla duymuyorum, ama çok iyi bir terim. Bazı insanlar bu "dinamik programlamayı" düşünüyor.
    • Algoritmayı çalıştırmadan önce, programcı tüm ağacı dikkate alır, daha sonra alt problemleri belirli bir sırada köke doğru değerlendirmek için bir algoritma yazar, genellikle bir tablo doldurur.
    • * dipnot: Bazen 'tablo' ızgara benzeri bağlantıya sahip dikdörtgen bir tablo değildir. Daha ziyade, bir ağaç gibi daha karmaşık bir yapıya veya sorunlu bölgeye özgü bir yapıya (örneğin, bir harita üzerinde uçuş mesafesi içindeki şehirler) veya hatta ızgara benzeri olsa da olmayan bir kafes diyagramına sahip olabilir. Örneğin, kullanıcı3290797 , bir ağaçtaki boşlukları doldurmaya karşılık gelen maksimum bağımsız kümeyi bulmak için dinamik bir programlama örneği bağladı .

Bir "dinamik programlama" paradigması içinde, en genel bulunuyor At (Ben, programcı bütün ağaç gördüğü söyleyebilirim sonraistediğiniz herhangi bir özelliği (genellikle zaman karmaşıklığı ve alan karmaşıklığı kombinasyonu) optimize edebilecek alt problemleri değerlendirmek için bir strateji uygulayan bir algoritma yazar. Stratejiniz belirli bir alt problemle bir yerden başlamalı ve belki de bu değerlendirmelerin sonuçlarına göre kendini uyarlayabilir. Genel olarak "dinamik programlama" anlamında, bu alt problemleri önbelleğe almayı deneyebilir ve daha genel olarak, belki de çeşitli veri yapılarındaki grafiklerde belirgin bir ayrım olan alt problemleri tekrar gözden geçirmekten kaçınabilirsiniz. Çoğu zaman, bu veri yapıları diziler veya tablolar gibi özünde bulunur. Artık ihtiyaç duymazsak alt problemlere çözümler atılabilir.)

[Daha önce, bu cevap yukarıdan aşağıya ve aşağıdan yukarıya terminoloji hakkında bir açıklama yapmıştı; Memoization ve Tabulation adlı bu terimlerle (tamamen olmasa da) birlikte olabilecek iki ana yaklaşım vardır. Çoğu kişinin kullandığı genel terim hala "Dinamik Programlama" dır ve bazı kişiler "Dinamik Programlama" nın bu alt türüne atıfta bulunmak için "Memoization" der. Bu cevap, topluluk akademik makalelerde uygun referansları bulana kadar hangisinin yukarıdan aşağıya ve aşağıdan yukarıya olduğunu söylemeyi reddeder. Sonuçta, terminolojiden ziyade ayrımı anlamak önemlidir.]


Lehte ve aleyhte olanlar

Kodlama kolaylığı

Memoization kodlamak çok kolaydır (genellikle * sizin için otomatik olarak yapan bir "memoizer" ek açıklama veya sarmalayıcı işlevi yazabilirsiniz) ve ilk yaklaşımınız olmalıdır. Tablolamanın dezavantajı, bir sipariş bulmanız gerektiğidir.

* (bu aslında sadece işlevi kendiniz yazıyorsanız ve / veya saf olmayan / işlevsel olmayan bir programlama dilinde kodluyorsanız kolaydır ... örneğin, bir kişi önceden derlenmiş bir fibişlev yazdıysa , mutlaka kendisine özyinelemeli çağrılar yapar ve bu özyinelemeli çağrıların yeni memoized işlevinizi çağırmasını (ve orijinal unmemoized işlevini değil) aramasını sağlamadan işlevi sihirli bir şekilde hatırlayamazsınız.

tekrarlamasinda

Hem yukarıdan aşağıya hem de aşağıdan yukarıya, yineleme veya yinelemeli tablo doldurma ile uygulanabileceğini, ancak doğal olmayabilir.

Pratik kaygılar

Not ile, ağaç çok derinse (örn. fib(10^6)), Yığın alanınız bitecek, çünkü her gecikmeli hesaplama yığına konulmalı ve 10 ^ 6 tanesine sahip olacaksınız.

Optimalliği

Alt problemleri ziyaret ettiğiniz (veya almaya çalıştığınız) sipariş, özellikle bir alt problemi hesaplamak için birden fazla yol varsa (normalde önbellekleme bunu çözebilir, ancak teorik olarak önbelleklemenin bazı egzotik durumlarda değil). Memoization genellikle zaman karmaşıklığınızı alan karmaşıklığınıza ekler (örneğin, tablo ile hesaplamaları atmak için daha fazla özgürlüğünüz vardır, örneğin Fib ile tablo kullanmak O (1) alanını kullanmanıza izin verir, ancak Fib ile not kullanmak O (N) kullanır yığın alanı).

Gelişmiş optimizasyonlar

Ayrıca son derece karmaşık bir sorun yaşıyorsanız, tablolama yapmaktan başka seçeneğiniz olmayabilir (veya en azından notu gitmesini istediğiniz yere yönlendirmede daha aktif bir rol üstlenebilirsiniz). Ayrıca, optimizasyonun kesinlikle kritik olduğu bir durumdaysanız ve optimizasyon yapmanız gerekiyorsa, tablolama, hatırlamanın aksi takdirde aklı başında bir şekilde yapmanıza izin vermeyeceği optimizasyonları yapmanıza izin verecektir. Benim düşünceme göre, normal yazılım mühendisliğinde, bu iki durumdan hiçbiri ortaya çıkmadı, bu yüzden bir şey (yığın alanı gibi) tablolamayı gerekli kılmadığı sürece sadece memoization ("cevaplarını önbelleğe alan bir işlev") kullanırım ... teknik olarak bir yığın patlamasını önlemek için 1) izin veren dillerde yığın boyutu sınırını artırabilir veya 2) yığınınızı sanallaştırmak için sabit bir ekstra iş faktörü yiyebilirsiniz (ick),


Daha karmaşık örnekler

Burada, sadece genel DP problemleri değil, aynı zamanda hatırlatma ve tablolamayı ilginç bir şekilde ayırt eden özel ilgi gösteren örnekleri listeliyoruz. Örneğin, bir formülasyon diğerinden daha kolay olabilir veya temel olarak tablolama gerektiren bir optimizasyon olabilir:

  • iki boyutlu tablo doldurma algoritmasının önemsiz bir örneği olarak ilginç olan düzenleme mesafesini hesaplamak için algoritma [ 4 ]

3
@ coder000001: python örnekleri için Google'da arama yapabilirsiniz python memoization decorator; bazı diller, not desenini kapsayan bir makro veya kod yazmanıza izin verir. Not kalıbı "işlevi çağırmak yerine, bir önbellekten değeri arayın (değer orada değilse, hesaplayın ve önce önbelleğe ekleyin)" den başka bir şey değildir.
ninjagecko

16
Kimsenin bundan bahsettiğini görmüyorum ama Top down'ın başka bir avantajı sadece arama tablosunu / önbelleği seyrek olarak oluşturacağınızı düşünüyorum. (yani gerçekten ihtiyacınız olan değerleri doldurursunuz). Bu nedenle, kolay kodlamaya ek olarak bu profesyoneller olabilir. Başka bir deyişle, her şeyi hesaplamadığınız için yukarıdan aşağıya gerçek çalışma sürenizi koruyabilirsiniz (çok daha iyi çalışma süresine sahip olabilirsiniz, ancak aynı asimptotik çalışma süresine sahip olabilirsiniz). Yine de (yine bellek tüketimini ek yığın kareleri tutmak için ek bellek gerektirir 'may' (sadece may) çift ama asimptotik durum aynı.
InformedA

2
Ben yukarıdan aşağıya örtüşen alt problemlerden önbellek çözümleri denilen bir teknik olduğunu yaklaşımları izlenimi altında değilim memoization . Bir tabloyu dolduran ve üst üste binen alt sorunların yeniden hesaplanmasını önleyen aşağıdan yukarıya tekniklere tablolama denir . Bu teknikler, çok daha büyük bir sorunu çözmek için alt problemleri çözmeyi ifade eden dinamik programlama kullanılırken kullanılabilir . Bu, bu cevabın birçok yerde tablolama yerine dinamik programlamayı kullandığı bu cevapla çelişkili görünmektedir . Kim doğru?
Sammaron

1
@Sammaron: hmm, iyi bir noktaya değindin. Kaynağımı belki de bulamadığım Wikipedia'da kontrol etmeliydim. Cstheory.stackexchange'i biraz kontrol ettikten sonra, artık "aşağıdan yukarıya" alt kısmın önceden bilindiğini (tablolama) ve "yukarıdan aşağıya" alt problemlere / alt ağaçlara çözüm olduğunu kabul edeceğinizi kabul ediyorum. Terimi belirsiz bulduğumda ve cümleleri ikili görünümde yorumladım ("aşağıdan yukarıya" alt problemlere çözüm olduğunu varsayar ve ezberler, "yukarıdan aşağıya" hangi alt problemlerle ilgili olduğunuzu bilir ve tablo haline getirebilirsiniz). Bunu bir düzenlemede ele almaya çalışacağım.
ninjagecko

1
@mgiuffrida: Yığın alanı bazen programlama diline bağlı olarak farklı şekilde işlenir. Örneğin python'da, bir memoized özyinelemeli fib gerçekleştirmeye çalışmak başarısız olacaktır fib(513). Hissettiğim aşırı yüklenmiş terminoloji burada ön plana çıkıyor. 1) Artık ihtiyaç duymadığınız alt problemleri her zaman atabilirsiniz. 2) İhtiyacınız olmayan alt problemleri hesaplamaktan her zaman kaçınabilirsiniz. 3) 1 ve 2, alt problemleri saklamak için açık bir veri yapısı olmadan kodlamak çok daha zor olabilir, VEYA, kontrol akışının işlev çağrıları arasında örülmesi gerekiyorsa (durum veya devamlara ihtiyacınız olabilir) daha zor olabilir.
ninjagecko

76

Yukarıdan aşağıya ve aşağıdan yukarıya DP aynı sorunları çözmenin iki farklı yoludur. Fibonacci sayılarını hesaplamak için not edilmiş (yukarıdan aşağıya) ve dinamik (aşağıdan yukarıya) bir programlama çözümü düşünün.

fib_cache = {}

def memo_fib(n):
  global fib_cache
  if n == 0 or n == 1:
     return 1
  if n in fib_cache:
     return fib_cache[n]
  ret = memo_fib(n - 1) + memo_fib(n - 2)
  fib_cache[n] = ret
  return ret

def dp_fib(n):
   partial_answers = [1, 1]
   while len(partial_answers) <= n:
     partial_answers.append(partial_answers[-1] + partial_answers[-2])
   return partial_answers[n]

print memo_fib(5), dp_fib(5)

Kişisel olarak hatırlamayı çok daha doğal buluyorum. Yinelemeli bir işlev alabilir ve mekanik bir işlemle (önbellekte ilk arama yanıtı ve mümkünse geri gönderebilir, aksi takdirde yinelemeli olarak hesaplayabilir ve daha sonra geri dönmeden önce, gelecekteki kullanım için hesaplamayı önbellekte kaydedebilirsiniz) dinamik programlama, çözümlerin hesaplandığı bir sırayı kodlamanızı gerektirir; böylece, bağımlı olduğu küçük sorundan önce "büyük sorun" hesaplanmaz.


1
Ah, şimdi "yukarıdan aşağıya" ve "aşağıdan yukarıya" nın ne anlama geldiğini görüyorum; aslında sadece DP vs anılara atıfta bulunuyor. Ve DP'de bahsetmek için soruyu düzenleyen kişi olduğumu düşünmek için ...
ninjagecko

memoized fib v / s normal özyinelemeli fib çalışma süresi nedir?
Siddhartha

üstel (2 ^ n) normal coz için bence bir özyineleme ağacı.
Siddhartha

1
Evet, doğrusal! Özyineleme ağacını çizdim ve hangi çağrıların önlenebileceğini gördüm ve ilk çağrının ardından memo_fib (n - 2) çağrılarının hepsinden kaçınılacağını fark ettim ve böylece özyineleme ağacının tüm sağ dalları kesilecek ve Doğrusal hale getireceğim.
Siddhartha

1
DP temel olarak her sonucun en fazla bir kez hesaplandığı bir sonuç tablosu oluşturmayı içerdiğinden, DP algoritmasının çalışma zamanını görselleştirmenin basit bir yolu tablonun ne kadar büyük olduğunu görmektir. Bu durumda, n boyutunda (her giriş değeri için bir sonuç) yani O (n). Diğer durumlarda, n ^ 2 matrisi olabilir, bu O (n ^ 2) vb.
Johnson Wong

22

Dinamik programlamanın temel bir özelliği, çakışan alt sorunların varlığıdır . Yani, çözmeye çalıştığınız sorun alt problemlere bölünebilir ve bu alt problemlerin çoğu alt problemleri paylaşır. "Böl ve fethet" gibi bir şey ama aynı şeyi birçok kez yapıyorsun. Bu konuları öğretirken veya açıklarken 2003'ten beri kullandığım bir örnek: Fibonacci sayılarını özyineli olarak hesaplayabilirsiniz .

def fib(n):
  if n < 2:
    return n
  return fib(n-1) + fib(n-2)

En sevdiğiniz dili kullanın ve kullanmayı deneyin fib(50). Çok, çok uzun zaman alacak. Kabaca fib(50)kendisi kadar zaman ! Ancak, çok fazla gereksiz iş yapılıyor. fib(50)arayacak fib(49)ve fib(48)sonra her ikisi fib(47)de değer aynı olsa bile çağrılır. Aslında, fib(47)üç kez hesaplanacaktır: doğrudan bir çağrı ile fib(49), doğrudan bir çağrı ile fib(48)ve aynı zamanda bir diğerinin doğrudan çağrısı ile fib(48), hesaplamanın ortaya çıkardığı fib(49)... Yani görüyorsunuz, üst üste binen alt problemlerimiz var .

Harika haber: Aynı değeri birçok kez hesaplamaya gerek yoktur. Bir kez hesapladığınızda, sonucu önbelleğe alın ve bir dahaki sefere önbelleğe alınan değeri kullanın! Dinamik programlamanın özü budur. Buna "yukarıdan aşağıya", "not" ya da başka ne diyebilirsiniz. Bu yaklaşım çok sezgiseldir ve uygulanması çok kolaydır. Önce özyinelemeli bir çözüm yazın, küçük testlerde test edin, not ekleyin (zaten hesaplanmış değerlerin önbelleğe alınması) ve --- bingo! --- bitirdiniz.

Genellikle özyineleme olmadan aşağıdan yukarıya çalışan eşdeğer bir yinelemeli program da yazabilirsiniz. Bu durumda bu daha doğal bir yaklaşım olacaktır: giderken tüm Fibonacci sayılarını hesaplamak için 1'den 50'ye kadar döngü.

fib[0] = 0
fib[1] = 1
for i in range(48):
  fib[i+2] = fib[i] + fib[i+1]

Herhangi bir ilginç senaryoda, aşağıdan yukarıya çözümün anlaşılması genellikle daha zordur. Ancak, bir kez anladıktan sonra, genellikle algoritmanın nasıl çalıştığına dair çok daha net bir büyük resim elde edersiniz. Pratikte, önemsiz problemleri çözerken, önce yukarıdan aşağıya yaklaşımı yazmanızı ve küçük örnekler üzerinde test etmenizi öneririm. Sonra aşağıdan yukarıya çözüm yazıp aynı şeyi aldığınızdan emin olmak için ikisini karşılaştırın. İdeal olarak, iki çözümü otomatik olarak karşılaştırın. İdeal olarak, testlerin çok üretecek küçük bir rutin yaz - tümbelirli boyutlara kadar küçük testler --- ve her iki çözeltinin de aynı sonucu verdiğini doğrulayın. Bundan sonra üretimde aşağıdan yukarıya çözüm kullanın, ancak üst-alt kodu saklayın, yorumladı. Bu, diğer geliştiricilerin ne yaptığınızı anlamasını kolaylaştıracaktır: aşağıdan yukarıya kod, yazmış olsanız bile ve ne yaptığınızı tam olarak bilseniz bile oldukça anlaşılmaz olabilir.

Birçok uygulamada, özyinelemeli çağrıların yükü nedeniyle aşağıdan yukarıya yaklaşım biraz daha hızlıdır. Yığın taşması da belirli sorunlarda bir sorun olabilir ve bunun giriş verilerine çok bağlı olabileceğini unutmayın. Bazı durumlarda, dinamik programlamayı yeterince iyi anlayamazsanız, yığın taşmasına neden olan bir test yazamayabilirsiniz, ancak bir gün bu yine de olabilir.

Şimdi, yukarıdan aşağıya yaklaşımın tek uygulanabilir çözüm olduğu problemler var çünkü sorun alanı o kadar büyük ki tüm alt problemleri çözmek mümkün değil. Bununla birlikte, "önbellekleme" hala makul bir sürede çalışır, çünkü girdinizin çözülmesi gereken alt problemlerin sadece bir kısmına ihtiyacı vardır --- ancak hangi alt problemleri çözmeniz gerektiğini açıkça tanımlamak çok zordur. çözüm. Öte yandan, tüm alt problemleri çözmeniz gerektiğini bildiğiniz durumlar vardır . Bu durumda devam edin ve aşağıdan yukarıya kullanın.

Kişisel olarak Paragraf optimizasyonu için Word wrap optimizasyonu problemi olarak en altta kullanacağım (Knuth-Plass satır kırma algoritmalarına bakın; en azından TeX bunu kullanıyor ve Adobe Systems'ın bazı yazılımları benzer bir yaklaşım kullanıyor). Hızlı Fourier Dönüşümü için aşağıdan yukarıya kullanırdım .


Merhaba!!! Aşağıdaki önerilerin doğru olup olmadığını belirlemek istiyorum. - Dinamik Programlama algoritması için, tüm değerlerin aşağıdan yukarıya doğru hesaplanması, özyineleme ve not kullanımından asimptotik olarak daha hızlıdır. - Dinamik algoritmanın zamanı daima Ο (Ρ) 'dir; burada Ρ alt sorunların sayısıdır. - NP'deki her problem üstel zamanda çözülebilir.
Mary Star

Yukarıdaki öneriler hakkında ne söyleyebilirim? Bir fikrin var mı? @osa
Mary Star

@evinda, (1) her zaman yanlıştır. Aynı veya asimptotik olarak daha yavaştır (tüm alt sorunlara ihtiyacınız olmadığında, özyineleme daha hızlı olabilir). (2) sadece O (1) 'deki her alt problemi çözebilirseniz doğrudur. (3) bir çeşit haktır. NP'deki her problem, belirsiz olmayan bir makinede (aynı anda birden fazla şey yapabilen bir kuantum bilgisayarı gibi) polinom zamanında çözülebilir: kekine sahip olun ve aynı anda yiyin ve her iki sonucu izleyin. Yani bir anlamda, NP'deki her problem normal bir bilgisayarda üstel zamanda çözülebilir. Not: P'deki her şey NP'dedir. Örneğin, iki tamsayı ekleme
osa

19

Örnek olarak fibonacci serisini ele alalım

1,1,2,3,5,8,13,21....

first number: 1
Second number: 1
Third Number: 2

Başka bir deyişle,

Bottom(first) number: 1
Top (Eighth) number on the given sequence: 21

İlk beş fibonacci sayısı durumunda

Bottom(first) number :1
Top (fifth) number: 5 

Şimdi örnek olarak özyinelemeli Fibonacci serisi algoritmasına bir göz atalım

public int rcursive(int n) {
    if ((n == 1) || (n == 2)) {
        return 1;
    } else {
        return rcursive(n - 1) + rcursive(n - 2);
    }
}

Şimdi bu programı aşağıdaki komutlarla yürütürsek

rcursive(5);

algoritmaya yakından bakarsak, beşinci sayı üretmek için 3. ve 4. sayıları gerektirir. Yani benim özyinelem aslında üstten (5) başlıyor ve daha sonra aşağı / aşağı sayılara kadar gidiyor. Bu yaklaşım aslında yukarıdan aşağıya bir yaklaşımdır.

Aynı hesaplamayı birden çok kez yapmaktan kaçınmak için Dinamik Programlama tekniklerini kullanıyoruz. Daha önce hesaplanan değeri saklıyor ve yeniden kullanıyoruz. Bu tekniğe notlama denir. Dinamik programlamada, mevcut problemi tartışmak için gerekli olmayan nottan daha fazlası var.

Yukarıdan aşağıya

Orijinal algoritmamızı yeniden yazalım ve not edilmiş teknikler ekleyelim.

public int memoized(int n, int[] memo) {
    if (n <= 2) {
        return 1;
    } else if (memo[n] != -1) {
        return memo[n];
    } else {
        memo[n] = memoized(n - 1, memo) + memoized(n - 2, memo);
    }
    return memo[n];
}

Ve biz aşağıdaki gibi bu yöntemi yürütmek

   int n = 5;
    int[] memo = new int[n + 1];
    Arrays.fill(memo, -1);
    memoized(n, memo);

Bu çözüm, algoritma en üst değerden başlayıp en üst değerimizi almak için her adımın altına inerken hala yukarıdan aşağıya doğru.

Altüst

Ama soru şu ki, ilk fibonacci numarasından olduğu gibi aşağıdan başlayıp yukarı doğru yürüyebilir miyiz. Bu teknikleri kullanarak yeniden yazalım,

public int dp(int n) {
    int[] output = new int[n + 1];
    output[1] = 1;
    output[2] = 1;
    for (int i = 3; i <= n; i++) {
        output[i] = output[i - 1] + output[i - 2];
    }
    return output[n];
}

Şimdi bu algoritmaya bakarsak, aslında daha düşük değerlerden başlar, sonra yukarı çıkar. Eğer 5. fibonacci numarasına ihtiyacım varsa aslında 1'i hesaplıyorum, sonra ikinci sonra üçüncü sayıya kadar üçüncü. Bu tekniklere aslında aşağıdan yukarıya teknikler denir.

Son iki algoritma, dinamik programlama gereksinimlerini tam olarak doldurur. Ama biri yukarıdan aşağıya, diğeri aşağıdan yukarıya. Her iki algoritma da benzer boşluk ve zaman karmaşıklığına sahiptir.


Aşağıdan yukarıya yaklaşımın genellikle özyinelemesiz bir şekilde uygulandığını söyleyebilir miyiz?
Lewis Chan

Hayır, herhangi bir döngü mantığını özyinelemeye dönüştürebilirsiniz
Ashvin Sharma

3

Dinamik Programlama'ya genellikle Memoization denir!

1.Memoizasyon yukarıdan aşağıya tekniktir (verilen problemi parçalayarak çözmeye başlayın) ve dinamik programlama aşağıdan yukarıya tekniktir (önemsiz alt problemden, verilen soruna doğru çözmeye başlayın)

2.DP, temel vaka (lar) dan başlayarak çözümü bulur ve yukarı doğru çalışır. DP tüm alt problemleri çözer, çünkü aşağıdan yukarıya yapar

Yalnızca gereken alt sorunları çözen Memoization'dan farklı olarak

  1. DP, üstel zaman kaba kuvvet çözümlerini polinom zaman algoritmalarına dönüştürme potansiyeline sahiptir.

  2. DP çok daha verimli olabilir çünkü yinelemesi

Aksine, Memoization özyineleme nedeniyle (genellikle önemli) ek yükü ödemek zorundadır.

Daha basit olmak gerekirse, Memoization sorunu çözmek için yukarıdan aşağıya yaklaşımı kullanır, yani çekirdek (ana) problemle başlar, ardından alt problemlere ayırır ve bu alt problemleri benzer şekilde çözer. Bu yaklaşımda aynı alt problem birçok kez ortaya çıkabilir ve daha fazla CPU döngüsü tüketebilir, dolayısıyla zaman karmaşıklığını artırabilir. Dinamik programlamada ise aynı alt problem birçok kez çözülmeyecek, ancak çözümü optimize etmek için önceki sonuç kullanılacaktır.


4
bu doğru değil, notlama zaman karmaşıklığını DP ile aynı şekilde kaydetmenize yardımcı olacak bir önbellek kullanır
InformedA

3

Yukarıdan aşağıya yaklaşımın alt problemleri tekrar tekrar çağırmak için özyineleme kullandığını söylemek
, aşağıdan yukarıya yaklaşımın tekini herhangi birini çağırmadan kullanması ve dolayısıyla daha etkilidir.


1

Aşağıda, yukarıdan aşağıya olan Mesafeyi Düzenle sorunu için DP tabanlı çözüm verilmiştir. Umarım ayrıca Dinamik Programlama dünyasını anlamaya yardımcı olur:

public int minDistance(String word1, String word2) {//Standard dynamic programming puzzle.
         int m = word2.length();
            int n = word1.length();


     if(m == 0) // Cannot miss the corner cases !
                return n;
        if(n == 0)
            return m;
        int[][] DP = new int[n + 1][m + 1];

        for(int j =1 ; j <= m; j++) {
            DP[0][j] = j;
        }
        for(int i =1 ; i <= n; i++) {
            DP[i][0] = i;
        }

        for(int i =1 ; i <= n; i++) {
            for(int j =1 ; j <= m; j++) {
                if(word1.charAt(i - 1) == word2.charAt(j - 1))
                    DP[i][j] = DP[i-1][j-1];
                else
                DP[i][j] = Math.min(Math.min(DP[i-1][j], DP[i][j-1]), DP[i-1][j-1]) + 1; // Main idea is this.
            }
        }

        return DP[n][m];
}

Evinizdeki özyinelemeli uygulamasını düşünebilirsiniz. Daha önce böyle bir şeyi çözmediyseniz oldukça iyi ve zorlayıcıdır.


1

Yukarıdan Aşağıya : Hesaplanan değerin şimdiye kadar izini sürün ve temel koşul karşılandığında sonucu döndürün.

int n = 5;
fibTopDown(1, 1, 2, n);

private int fibTopDown(int i, int j, int count, int n) {
    if (count > n) return 1;
    if (count == n) return i + j;
    return fibTopDown(j, i + j, count + 1, n);
}

Aşağıdan Yukarıya : Geçerli sonuç alt sorunun sonucuna bağlıdır.

int n = 5;
fibBottomUp(n);

private int fibBottomUp(int n) {
    if (n <= 1) return 1;
    return fibBottomUp(n - 1) + fibBottomUp(n - 2);
}
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.