tolpp.com
yazılım ve programlama günlüğü

Dinamik Programlama (Dynamic Programming) nedir? Jun 7, 2011

Merhaba. Bu yazıda dinamik programlamayı olabildiğince açıklayıcı şekilde anlatmaya çalışacağım.

###Dinamik Programlama Nedir?

Dinamik programlama karışık problemlerin daha basit düzeylere indirilerek çözülmesini esas alan bir optimizasyon yöntemidir. Optimizasyondaki amaç, problemdeki kısıtlayıcı koşullar altında bu problemle ilgili en iyi karara varmaktır. Bir problem üzerinde dinamik programlama uygulayabilmek için o problemin alt problemlere parçalanabilir veya bir önceki problemin karakteristiğini koruyacak şekilde çözümü daha kolay başka probleme dönüştürülebilir olması yeterlidir

Yönteme göre optimum çözüm başlangıç durumundan bağımsız olarak diğer çözümler ile çözüm sonuçlarına göre optimum çözümler ardışıklığıdır. Yani, başlangıçta alt problemlerin çözümü bulunup buradan elde edilen verilerle daha büyük alt problemler çözüldüğünde problemin kendisi de çözülmüş olmaktadır.

###Nerelerde Kullanılır?

Dinamik programlama, aynı çözümlü küçük problemlere parçalanabilen tüm problemler için uygulanabilir. Fakat brute-force ile exponential zamanda çözülebilen problemlerde gerçek değerini gösterir. En basitinden fibonacci dizisinin hesabı zaman karmaşıklığına sahipken, dinamik programlama ile problem zamanda çözülebilmektedir.

DP ile çözülebilecek birkaç problem:

  • Rod-cutting problem
  • Knapsack problem
  • Matrix-chain multiplication
  • Assembly Line Scheduling
  • Birçok string problemi(LCS, longest increasing subsequence, Levenshtein distance)

Yapısı ve Kullanımı

Başlamadan önce, birazdan sıkça göreceğiniz memoization kavramına bakmanızı öneriyorum.

####Dinamik Programlama Algoritmasının Kurulması

Bir problem için dinamik programlama algoritması yapmak için aşağıdaki dört adım uygulanır.

  1. Characterize the structure of an optimal solution (En iyi çözümün karakteristiğinin belirlenmesi)
  2. Recursively define the value of an optimal solution(En iyi çözümün özyinelemeli tanımı)
  3. Compute the value of an optimal solution,typically in bottom-up fashion(En iyi çözümün değerini top-down with memoization veya bottom-up yaklaşımdan birini kullanarak hesaplanması(genel olarak bottom-up))
  4. Construct an optimal solution from computed information(Hesaplanmış verileri kullanarak en iyi çözümü bul)

Yazılan dört adımın ilk üç adımı dinamik programlamanın temel üç adımıdır. Eğer optimal çözümün yalnızca değerine ihtiyacımız varsa(Ör: LCS probleminde eşleşen harf sayısı) yalnızca ilk üç adımı kullanmak yeterli olacaktır.

Farklı problemler için farklı algoritmalar kurulacağından, Rod-Cutting problemi üzerinden DP’yi anlatmaya çalışacağız.

Rod-Cutting problem (Çubuk kesme problemi)

Problem : uzunluğunda bir çubuğumuz olsun, bu çubuğun her cm lik parçasının gibi bir fiyatı olsun. uzunluğundaki bir çubuğun, kendinden küçük parçalarla oluşturulabilen max fiyatına da diyelim.Fiyat tablosu aşağıdaki gibi olduğuna göre, farklı uzunluğundaki çubukları kaç parçaya bölersek en fazla kar edeceğimizi bulalım.

4 birimlik çubuk için soruyu çözecek olursak;

  • olur. Ve bu durumda en pahalı satışın çubuğu iki parçaya bölerek, şeklinde yapıldığını görebiliriz.
1- Characterizing a rod-cutting

Çubuğumuzu parçalayacağımız birimler gibi tam sayılar olduğundan ayrışmanın karakteristiğini belirleyebiliriz. Örneğin dersek, 7 birim uzunluğundaki çubuğu 2 adet 2cm ve bir adet 3cm olmak üzere 3 parçaya bölmüş oluruz. değerim en küçük en büyük olabileceğinden, parça sayısı ‘yı şeklinde gösterebiliriz.

Tüm parçaların uzunluklarını toplarsak, elde ederiz.

Bir çubuğun en yüksek fiyatına şeklinde gösterebiliriz.

Burada, ‘i oluşturan her bir parça da kendini en pahalı yapacak parçalardan oluşmaktadır.

Genelleme yaparsak, maksimum çubuk fiyatı için şöyle diyebiliriz:

2- A Recursive solution

Yukarıdaki top-down recursive fonksiyon, genelleme yaparak bulduğumuz maksimum çubuk fiyatını geri döndürecektir.Dışarıdan şeklinde ücretleri tutan bir tamsayı dizisi ve çubuk uzunluğunu tutan bir tamsayısı alır. , fonksiyonun özyinelemesinin durmasını sağlayan kontroldür. Eşitlik sağlanmışsa döndürülür. Döndürülecek değerinin doğru olması için ‘ya en küçük ilk değer atanır. 5.satırda da bir for döngüsü ile her parça çağırılarak en büyük değer içine atılır. Problem çözümü için bu fonksiyonu kullanacak olursak, zaman karmaşıklığı olduğundan çok uzun zamanda bulunabilecektir.

3- Compute the maximum value of a price
A. Memoization kullanılarak Top-Down yaklaşım ile

Memoized-Cut-Rod(p,n) fonksiyonu, asıl memoization işlemini yapan Memoized-Cut-Rod-Aux(p,n,r) fonksiyonunu çağırır. Bu fonksiyon içinde tanımlanan yardımcı array, bizi her defasında alt problem hesaplamaktan kurtarır.

Memoized-Cut-Rod-Aux(p,n,r) fonksiyonu recursive tanımladığımız Top-Down çalışan Rod-Cut(p,n) fonksiyonuna çok benzer. Farklı olarak ilk adımında dizi üzerinden kontrol yapar. Eğer bir alt problem daha önceden çözülmüşse, tekrar çözmeye gerek kalmadan değer doğrudan dizi üzerinden okunur. Çözülmemiş alt problem ise çözülerek dizi üzerine yazılır.

######B. Bottom-Up yaklaşımı ile

Bottom-Up-Cut-Rod(p,n) fonksiyonunda problem boyutu alt problem boyutundan küçük olduğu sürece :

1. satırda alt problemlerin çözümünü tutabilmek için dizisi yaratılır. Hemen altındaki satırda ise, hiç parça olmaması durumunun bir fiyatı olmayacağından elemanına değeri atanır.

3. ve 6. Satırlar arasında büyüklüğüne sahip tüm at problemler çözülür. , değeriyle başlar ve olana kadar büyür. Bu sayede en küçük alt problemlerden büyüklere doğru bir çözüm elde etmiş oluruz.

6.satırda de bulunan ücretle o an kontrol edilen ücretin toplamı () ile , değerini maksimim yapacak şekilde karşılaştırılır, ve en büyük toplam ya atanır. 7. Satırda alt problem çözümleri güncellenir. 8. Satırda istenen değer geri döndürülür.


Dinamik programlama için kullandığımız Top-Down ve Bottom-Up yaklaşımların ikisi de asimptotik çalışma zamanına sahiptir. Bottom-Up-Cut-Rod fonksiyonunda iç içe iki for döngüsü olduğundan çalışma zamanına sahip denebilir. Aynı şekilde, Memoized-Cut-Rod-Aux fonksiyonunun 6 ve 7. Satırlarına bakacak olursak, for içinde fonksiyonun kendini n kere çağırdığını görebiliriz. Özyineleme gerçekleşirken her alt problem yalnızca bir kere hesaplanacağından (memoization sayesinde) 7. Satırda çağıracağımız fonksiyon da bize n gibi bir karmaşıklık getirir. Dolayısıyla Memoized-Cut-Rod için de çalışma zamanına sahip diyebiliriz.

4- Constructing a solution

Yukarıda yaptığımız hesaplamalar bize çözümü değil, yalnızca en iyi çözümün değerini getiriyordu. Eğer ihtiyacımız olan sadece bu değerse, fonksiyonu çağırmamızın ardından bize dönen değeri doğrudan kullanabiliriz. Eğer ki ihtiyacımız olan çözümün kendisiyse kullandığımız hesaplama(computing) algoritmalarından birini genişleterek çözümü de bir yerde tutacak hale getirmemiz gerekir. Bottom-Up-Cut-Rod fonksiyonun genişleterek çözüme gidelim.

Şeklinde bir genişletme ile çözüme ulaşabiliriz. Burada kullandığımız dizisi, tüm en iyi çözümleri içinde tutmaktadır.

Çözümün yazdırılmasını da aşağıdaki fonksiyonla yapalım:

Eğer fonksiyonumuzu Print-Cut-Rod-Solution( ) şeklinde verirsek aşağıdaki gibi bir çözümümüz olacaktır:

Longest Common Subsequance (En uzun ortak alt dizi)(LCS) problem

Problemin dinamik programlama ile çözümüne buradan ulaşabilirsiniz.

####Algoritmanın iyileştirilmesi Dinamik programlama sırasında birçok işlemi yeniden yapmak yerine alt problem çözümünü depoladığımızdan çözüme çok daha hızlı ulaşabiliriz. Fakat çok büyük problemler için bu bize büyük maliyette bir belleğe mal olabilir fibonacci sayısılarının bulunması, rod-cutting gibi problemler tek dizi üzerinde tutulduğundan bellek kullanırken LCS, Matrix Chain Manipulation gibi problemler bellek kullanırlar. Peki bu sorunun bir çözümü var mıdır?

Eğer bizden çözümün tamamı isteniyorsa maalesef yoktur. Fakat sadece çözümün değeri isteniyorsa çok küçük bellek kullanımlarıyla bu değer elde edilebilir. Örneğin, sayısına kadar olan tüm fibonacci sayılarını listeleyebilmek için dizinin tamamı elimizde olmalıdır. Fakat bizden değeri istenirse bunun için ve değerlerini bilmemiz yeterli olacaktır. Bu da değerine bellek kullanarak ulaşabileceğimiz anlamına gelir. Aynı şekilde LCS probleminde bizden altdizinin ne olduğu yerine altdizinin uzunluğu istenirse, yerine bellek kullanarak değeri bulabiliriz.

Kaynaklar

“Introduction to Algorithms”, Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein, The MIT Press; 3rd edition, 2009

“Algoritmalar : teoriden uygulamalara” Vasif V. Nabiyev, Seçkin Yayınları,ikinci baskı 2009

www.columbia.edu/~cs2035/courses/csor4231.F09/dynamic.pdf

http://en.wikipedia.org/wiki/Dynamic_programming

www.cs.cmu.edu/~avrim/451f09/lectures/lect1001.pdf

http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-046j-introduction-to-algorithms-sma-5503-fall-2005/video-lectures/