BÖLÜM

advertisement
C# İle Bilgisayar Programlama Temelleri
327
BÖLÜM
Özyineleme
10
10
Bölümün İçindekileri
Karışık kombinasyon problemlerini ve konfigürasyonlarını analiz ederken, permütas-
yon ve türevlerini üretirken ve iç içe geçmiş döngüleri analiz ederken özyineleme ve uygulamalarını kullanacağız. Metot içinden yine metotun kendisi için yapılan çağrı veya çağrıları
temsil eden güçlü programlama tekniklerine özyinelemeli denir. Bu bölümde özyinelemenin doğru ve yanlış kullanımları ile ilgili pek çok örnek göstermeye ve nasıl yararlı olabileceği hakkında doğru yolu göstermeye sizi ikna etmeliyiz. Özyineleme tekniğini kullanmak
için yola çıkıyoruz.
10.2 Özyineleme Nedir?
Özyineleme, belirli bir sorunu çözmek için bir yöntemin kendisini çağırdığı bir programlama
tekniğidir. Metotun kendisi için yapılan bir çağrının belirli konudaki bir sorunu çözmesi olarak
da tanımlanabilir.
Özyineleme nesnenin kendisini içermesi yada kendi kendisi tarafından tanımlanmasıdır.
Doğru kullanıldığında belirli problemlere zarif çözümler getiren bir programlama tekniğidir.
Kullanımı, programlama kodunu ve okunabilirliğini bazı durumlar için kolay hale getirir. Bu
programlama teknikleri genellikle kodun okunulabilirliğini artırır.
10.3 Özyineleme – Örnek
Aşağıda bir Fibonacci sayı dizisinin bazı elemanlarını görüyorsunuz.
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
328
Verilen bir Fibonacci dizisinin her elemanı iki önceki elemanın toplamından oluşur. Birinci
ve ikinci elemanlar tanım gereği 1 değerini alırken, diğer elemanların diziliş kuralı aşağıda
verilmiştir:
F1 = F2 = 1
Fi = Fi-1 + Fi-2 (for i > 2)
Tanıma göre n’inci elemanın hesaplanması için aşağıdaki özyineleme metodunu kullanırız:
int
2
return 1
return
1
2
Bu örnek bir çözümün uygulanması için özyineleme metotunun nasıl kullanılacağını gösteriyor.
Öte yandan, özyineleme ile programlama yaparken sezgisel olmalısınız, çünkü programın
verimliği ve hızını etkileyen pek çok faktör vardır. Özyinelemenin avantaj ve dezavantajları
için bkz. bu bölümün sonraki alt başlıkları.
10.4 Doğrudan ve Dolaylı Özyineleme
Bir metot gövdesi içinden aynı metotun kendisine çağrı yapmışsa, bu doğrudan özyineleme
veya direkt özyinelemedir.
Verilen bir A metotu herhangi bir B metotunu çağırıyor ve bu B metotu da verilen bir C başka
metotunu çağırıyor ve bu C metotu da gerisin geriye tekrar A metotunu çağırmışsa, bunun
adı literatürde dolaylı özyineleme veya A, B ve C metotları aralarında indirekt özyineleme
vardır demeklerdir.
Dolaylı özyineleme çağrılarında birden fazla metot zinciri var olabilir, ancak koşullara veya
verilere, parametre değerlerine bağlı olarak bir metot bir başka metota özyineleme çağrısı
yaparken, yine başka nedenlerle bir başka metota özyineleme çağrısı yapabilir.
www.nakov.com
329
C# İle Bilgisayar Programlama Temelleri
10.5 Özyinelemenin Sonlandırılması
Özyinelemeli programlamada dikkat edilmesi gereken en önemli programlama tekniği, özyineleme zincirlerinin son bulması gerektiği, yada somut sayıda tekrar ettikten sonra sonucu
döndürmesidir. Bir başka deyişle, sonsuz sayıda özyineleme yoktur, bu durum yanlış programlamaya yol açar.
Bu nedenle özyinelemeyi sonlandırmak için bir yada daha fazla programlama yapısının metot içinde kullanılarak sonuç durumlarının programlanması programcının ana görevidir.
Yapılması gerekli bu eylem özyinelemenin sonlandırılması olarak da tanınır.
Fibonacci sayıları örneğinde, özyinelemenin sonlandırılma şartı n sayısının 2 veya daha az
olmasıdır. Sonlandırılma şartının özyinelemeyi sonlandırmasından emin olmalıyız. Sonlandırılmaya gelen bir metot, daha fazla özyinelemeli çağrı yapmadan içinde bulunduğu çağrıyı
sona erdirecek sonucu döndürmelidir. Kodda yapılan bir değerlendirme metotun özyinelemeli sonuç döndüren bir program mı, sonsuz döngü mü yapıldığını ortaya çıkaracaktır. Bu
nedenle n=1 ve n=2 için sadece 1 döner.
10.6 Özyinelemeli Metot Oluşturulması
Özyinelemeli metotun her tekrarında elde edilen sonuç toplanarak gerçek sonuç oluşturulur.
Her özyinelemeli çağrı sonunda problem daha küçük alt birimlere bölünür. Bu küçük problemlere ait alt çözümler bilgisayar tarafından saklanır ve dönüş sağlandığında birleştirilir.
Birleştirme işlemi için özyinelemenin sonlandırılması gerekir.
10.7 Faktoriyelin Özyinelemeli Hesaplanması
Özyineleme kullanımını klasik bir örnek ile göstereceğiz – faktoriyelin özyinelemeli hesaplanması.
n faktöriyel (n! olarak gösterilir) 1 ve n (dahil) arasındaki her tamsayının çarpımlarından
ibaret bir sayı olarak tanımlıdır. 0! = 1 kabul edilir.
n! = 1.2.3…n
www.nakov.com
330
C# İle Bilgisayar Programlama Temelleri
10.7.1 Özyinelemeli Tanım
Faktoriyel hesaplanması için bir çözüm oluştururken faktoriyelin özyinelemeli tanımına başvurmak yerinde olacaktır:
n! = 1, n = 0 için
n! = n.(n-1)!, n > 0 için
10.7.2 Özyinelemeli Bağlılık Tespiti
Özyinelemeli bağlılık tespiti için uzun uğraş gerekebilir. Faktoriyel örneğimizde, problem
analizi ve ilk birkaç durum için faktoriyel değerlerinin hesaplanması yeterlidir.
Buradan kolayca tekrarlayan bağlılıkları görebilirsiniz:
10.7.3 Gerçekleştirilen Algoritma
Özyinelemenin sonlandırılma koşulu 1 değerine sahip n=0 şartıdır.
Diğer durumlarda n=n-1 için problemi çözmek zorundayız ve bu alt problem için dönen sonucun n ile çarpımını n=n-1 için döndürmeliyiz. Bu algoritma, belirli bir sayıdaki adım sonrasında özyinelemenin sonuna erişir ve 1…n arasındaki somut sayıdaki tamsayıların çarpımını
hesaplar.
Bu önemli şartlar ile birlikte, faktoriyel hesaplayan metotu yazabiliriz:
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
331
static
int
// The bottom of the recursion
0
1
// Recursive call: the method calls itself
1
Bu metotu kullanarak konsoldan bir tamsayı okuyan, bu tamsayının faktoriyelini hesaplayan ve elde edilen değeri yazdıran bir uygulama geliştirebiliriz.
RecursiveFactorial
static
Console
int
int
Console
“n = “
Console
“{0}! = {1}”
static
int
// The bottom of the recursion
0
return 1
// Recursive call: the method calls itself
return
1
n=5 için uygulamanın çalıştırılması sonucu aşağıda verilmiştir:
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
332
10.8 Özyineleme veya Yineleme?
Özyineleme kavramı öğretilirken genellikle faktoriyel hesaplama örnek olarak seçilmiştir,
ancak diğerleri gibi bu durumda da özyineleme en iyi seçim değildir.
Verilen bir problem için yinelenen (ardışık) bir çözüm her zaman açık olmayabilir. Sıklıkla
eğer bir problemin özyinelemeli tanımı verilmişse, özyinelemeli çözüm sezgisel ve çok zor
olmamalıdır.
Faktoriyel örnek için yinelemeli çözümün gerçekleştirilmesi özyineleme kadar kısa, basit ve
hatta biraz daha verimli görünmektedir.
int
for
1
1
return
Bu bölümde daha sonra özyineleme ve yineleme kullanmanın avantaj ve dezavantajlarını
dikkate alacağız.
Şu andan itibaren özyinelemenin gerçekleştirilmesi ile devam etmeden önce şunu da hatırlatmamız gerekir ki, yinelenen bir değişebilir şart üzerinde düşünmeli ve bu şart sonrasında
daha iyi bir çözüm bulmalıyız.
Özyineleme kullanılarak çözülen bir probleme göz atalım. Bu örnek için yinelemeli bir çözümü de dikkate alacağız.
10.9 İç içe N Döngünün Simülasyonu
Sık sık iç içe döngüler yazmak zorundayız. İki, üç adet veya daha önceden belirli bir sayı
kadar döngünün kodlanması kolaydır. Ancak sayılar önceden bilinmiyorsa, farklı bir yaklaşım
düşünmekte fayda var.
N sayısının 1..K defa tekrarlanarak gösterimini gerçekleştiren aşağıdaki örneği göz önüne
alınız: N ve K kullanıcı tarafından girilmelidir.
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
333
for
for
for
1
1
1
Console
1
“{0} {1} {2} … {N}”
Örneğin N=2 ve K=3 için (1..3 defa tekrarlanarak gösterimi), ve N=3 ve K=2 için (1..2 defa
tekrarlanarak gösterimi), çıktılar aşağıdaki gibidir:
Bu problemi çözen algoritmayı önceki örnekte olduğu gibi açıkça anlayamayabilirsiniz. Bunun için biri özyinelemeli, diğeri yinelemeli iki çözüm geliştirebiliriz.
Sonucun her satırında, N adet sayının bir sıralı dizilimini görüyorsunuz. Birinci değişken birinci döngü sayacının geçerli değerini temsil eder. İkinci değişken – ikinci döngü sayacının
geçerli değerini temsil eder, vs. Her sayı 1 ve K arasında değer alabilir. N ve K verildiğinde
görevimiz, N elemanın sıralı dizilimlerini bulmaktır.
10.9.1 İç içe Döngüler – Özyinelemeli Yaklaşım
Eğer problem için özyinelemeli çözüm arıyorsanız, ilk yaklaşımınız özyinelemeli bağlılık bulmaktır. Örnekte tanımlı olan probleme biraz daha dikkatli bakalım.
N=2 için cevabı hesaplasaydık, birinci sayı olarak K koyarak (örneğimizde K=3) N=3 için de
cevabı elde edecektik, çünkü diğer sayılar N=2 için hali hazırda yazılı olacaktı. Bu sonlanma
koşulu N>3 için de geçerlidir.
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
334
Bu şekilde aşağıdaki bağımlılığı yakaladık – ilk pozisyondan itibaren geçerli her konuma 1..K
arası değerleri sırasıyla yerleştirdikten sonra bir sonraki pozisyon için özyinelemeli olarak
kendimizi çağırdık. Özyineleme N defa kendisini çağırdığında sonlanır ve sayılar K değerine
kadar sıralı bir şekilde ard arda yazılır. C# metotu aşağıda gösterilmiştir:
return
for
1
1
Sayı dizisi loops adlı bir dizide saklanırken, PrintLoops() metoduyla özyineleme sonlandığında bu sayılar basılacaktır.
NestedLoops(…) metotu sayıların yerleştirileceği pozisyonları belirten bir parametre almaktadır.
Bir sonraki pozisyon için NestedLoops(…) metotunu özyinelemeli olarak çağırdıktan sonra
döngü içinde adlandırdığımız olası her değer yazdırılır. numberOfIterations değişkeni kullanıcı tarafından girilen K değeri için oluşturulmuştur.
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
335
Geçerli konum N olduğunda en son satır yazılmıştır ve özyineleme sonlanır. numberOfLoops
değişkeni kullanıcı tarafından girilen K değeri için oluşturulmuştur.
Geçerli konum N olduğunda en son satır yazılmıştır ve özyineleme sonlanır. Bu noktada tüm
pozisyonlarda değerlerimiz vardır ve diziyi yazdırırız.
Özyinelemeli olarak iç içe döngü ile ilgili bir çözümün tam bir uygulaması aşağıda verilmiştir:
RecursiveNestedLoops
static int
static int
static int
static
Console
“N = “
int
“K = “
Console
int
0
Console
int
Console
static
return
for
1
1
static
for
Console
Console
www.nakov.com
0
“{0} “
C# İle Bilgisayar Programlama Temelleri
336
N=2 ve K=4 için uygulama çıktısı aşağıdaki gibidir:
10.9 İç içe Döngüler - Yinelemeli Yaklaşım
İç içe geçmiş döngülerin yinelemeli bir çözümünün uygulanması için, bir sonraki sayı dizisini
bulan ve her yinelemede yazdıran aşağıdaki algoritmayı kullanabiliriz:
1.
Her pozisyon başına 1 sayısını yerleştirin.
2.
Mevcut sayı dizisini bastırın.
3.
N pozisyon numarasını 1 artırdıktan sonra elde ettiğiniz değer K’yı aşmışsa, onu tekrar 1’e eşitleyin ve N-1 pozisyonundaki değeri 1 ile artırın. Bu değer de K’yı aşmışsa, onu
tekrar 1’e eşitleyin ve N-2 pozisyonundaki değeri 1 ile artırın, ve bu böyle devam eder.
4.
İlk pozisyon değeri K’yı aştığı takdirde, algoritma sonlanır.
5.
Adım 2 ile devam edin.
Açıkladığımız yinelemeli iç içe döngüler algoritmasının uygulaması aşağıda sunulmuştur:
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
337
using
class IterativeNestedLoops
int
void
Console
“N = “
int
“K = “
Console
int
Console
int
Console
void
int
while
true
1
1
1
if
0
return
1
void
int
0
1
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
338
static
int
Console
0
0
Console
Main() ve PrintLoops() metotları özyinelemeli uygulamadaki gibidir.
NestedLoops() metotu yinelemeli ve parametresiz olarak çalıştığı için farklı uygulanmalıdır.
Metotun en başında, InitLoops() metotuna bir çağrı yapılarak dizinin her pozisyonu için 1
atanır.
Algoritmanın adımları çalışırken sonsuz döngüden metotların return deyimi ile çıkar.
Algoritmanın 3.adımını uygulamak oldukça ilginçtir. K’nın üzerindeki değerlerin kontrolü, 1’e
eşitlenmeleri, ve bir önceki pozisyonun değerinin 1 ile artırılması (aynı kontrolü onun için de
gerçekleştirdikten sonra) ardından K’yı aşan değerler için while döngüsüne girilir.
Mevcut pozisyon değeri bu amaçla 1’e eşitlenir. Bunun sonrasında bir önceki pozisyon geçerli olur. Ardından geçerli pozisyonun değeri 1’e eşitlenir ve döngünün başlangıcına dönülür. Bu eylemler mevcut pozisyon değeri K veya daha az iken devam eder. K’yı aşınca (numberOfIterations değişkeni K değerine erişince) sona erer.
İlk pozisyon değeri K’yı aşınca, döngü K defa çalışmıştır. Çıktının devamlılığını sağlayabilmek için (sonsuz döngüye devam etmek için) sayı dizisinin bir sonraki elemanı tekrar 1’e
eşitlenir ve bu anda bu alt döngünün yinelenme sayısı K’yı tükettiği için geçerli pozisyon
(currentPosition) 0’dan az ve negatif bir değer almıştır. Dizi endeksi negatif değer alamayacağı için bir if-kontrol deyimi içindeki return ile alt döngüden çıkılır.
N=3 ve K=2 için uygulamayı test edebiliriz:
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
339
10.9.3 Hangisi Daha İyi: Özyineleme veya Yineleme?
Eğer problem çözme algoritması özyinelemeli ise, özyinelemeli çözüm uygulaması aynı problem için geliştirilebilecek bir yinelemeli çözüm uygulamasından çok daha okunaklıdır ve tercih edilir.
Eşdeğer bir algoritmanın tanımlanması ve algoritmaların eşdeğerliliklerini kanıtlamak bazen
zor olabilir.
Özyineleme kullanarak belirli durumlarda çok daha basit, daha kısa ve kolay çözümler sunmayı başarabiliriz.
Öte yandan, özyinelemeli çağrılar çok daha fazla kaynak tüketebilir (CPU zamanı ve bellek).
Her özyinelemeli çağrı için ayrıca yığında, metota giren ve/veya çıkan argümanlar, yerel değişkenler ve döndürülen değerleri saklayan yeni bellek alanları ayrılmalıdır. Çok fazla özyinelemeli çağrılar bir yan etki olarak bellek akıntısına neden olabilir.
Özyinelemeli çözümler, bazı durumlarda, eşdeğer yinelemeli çözümlerinden daha zor okunabilir ve anlaşılabilir.
Özyineleme güçlü bir programlama tekniğidir, ancak dikkatli analiz edilmelidir. Yanlış kullanımında, çözümlerin anlaşılması ve bakımı zorlaşır.
!
Eğer özyineleme kullanarak daha basit, daha kısa ve daha kolay bir çözüme varırsanız
tercih edilebilir. Aksi takdirde, yinelemeli bir çözüm düşününüz.
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
340
Aşağıda özyinelemenin doğru kullanımını gösteren bir uygulama verilmiştir:
RecursiveFibonacciMemoization
static long
static
Console
int
int
long
Console
static
1
2
1
1
“n = “
Console
long
2
“fib({0}) = {1}”
int
1
return
www.nakov.com
2
341
C# İle Bilgisayar Programlama Temelleri
Main() ve PrintLoops() metotları özyinelemeli uygulamadaki gibidir.
NestedLoops() metotu yinelemeli ve parametresiz olarak çalıştığı için farklı uygulanmalıdır.
Metotun en başında, InitLoops() metotuna bir çağrı yapılarak dizinin her pozisyonu için 1
atanır.
Algoritmanın adımları çalışırken sonsuz döngüden metotların return deyimi ile çıkar.
Algoritmanın 3.adımını uygulamak oldukça ilginçtir. K’nın üzerindeki değerlerin kontrolü, 1’e
eşitlenmeleri, ve bir önceki pozisyonun değerinin 1 ile artırılması (aynı kontrolü onun için de
gerçekleştirdikten sonra) ardından K’yı aşan değerler için while döngüsüne girilir.
Mevcut pozisyon değeri bu amaçla 1’e eşitlenir. Bunun sonrasında bir önceki pozisyon geçerli
olur. Ardından geçerli pozisyonun değeri 1’e eşitlenir ve döngünün başlangıcına dönülür. Bu
eylemler mevcut pozisyon değeri K veya daha az iken devam eder. K’yı aşınca (numberOfIterations değişkeni K değerine erişince) sona erer.
İlk pozisyon değeri K’yı aşınca, döngü K defa çalışmıştır. Çıktının devamlılığını sağlayabilmek
için (sonsuz döngüye devam etmek için) sayı dizisinin bir sonraki elemanı tekrar 1’e eşitlenir
ve bu anda bu alt döngünün yinelenme sayısı K’yı tükettiği için geçerli pozisyon (currentPosition) 0’dan az ve negatif bir değer almıştır. Dizi endeksi negatif değer alamayacağı için bir
if-kontrol deyimi içindeki return ile alt döngüden çıkılır.
N=3 ve K=2 için uygulamayı test edebiliriz:
Gözle görülür bir fark vardır. Önceki örnekte n=100 için sonsuza giden bir hesaplama nedeniyle sonuç uzun zaman sonra hesaplanırken, optimize edilmiş çözüm ile sonuç hemen hesaplanıyor. Daha sonraki “Veri Yapıları ve Algoritma Karmaşıklığı” Bölümü’nde not edileceği
üzere, birinci çözüm üssel, ikinci çözüm doğrusal zamanda çalışmaktadır.
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
342
10.9.4 Fibonacci Sayıları – Özyinelemenin Yanlış Kullanımı
Bir Fibonacci dizisinin n.elemanını bulduğumuz örneğe yeniden göz atalım ve özyinelemeli
çözüme daha dikkatli olarak yaklaşalım.
int
2
return 1
return
1
2
Bu çözüm, sezgisel, kısa ve anlaşılırlığı yüksektir. İlk bakışta özyinelemenin iyi uygulanabileceği bir örnekmiş gibi görünüyor. Ancak gerçekte, özyinelemenin kullanımına dair doğru
olmayacak bir örnektir. Nedenini açıklamak gerekirse, özyinelemeli uygulamayı aşağıdaki
şekilde geliştirdiğimizi düşünelim:
RecursiveFibonacci
static
Console
int
int
long
Console
“n = “
Console
“fib({0}) = {1}”
static
int
2
return 1
return
www.nakov.com
1
2
343
C# İle Bilgisayar Programlama Temelleri
n=100 için, hesaplamalar kimsenin beklemeyi göze almayacağı kadar uzun süreceği için uygulama son derece verimsiz olacaktır. Özyinelemeli her çağrı için iki özyinelemeli çağrı daha
yapılıyor. Çağrılar, bu nedenle, aşağıdaki şekilde gösterildiği gibi katlanarak büyümektedir.
fib(100) hesaplanması için gerekli adımlar 100 üssü 1,6 mertebesindedir, matematiksel
olarak bu sayı kanıtlanmıştır. Buna rağmen, çözüm doğrusal olsaydı, bu sayı 100 olacaktı.
Nedeni, çok fazla hesap gerektirmesindendir. Aşağıdaki Fibonacci ağacından da görülebileceği gibi, fib(2) sayısı birden fazla yerde göze çarpmaktadır.
10.9.5 Fibonacci Sayıları – Özyinelemenin Doğru Kullanımı
Fibonacci sayılarının hesaplanması için özyinelemeli metotu optimize etmenize yardımcı olabiliriz. Dizide zaten hesaplanan sayıları hatırlayarak (saklayarak) ve ancak eğer sayı henüz
hesaplanmamışsa özyinelemeli çağrıyı yapmaya hazırlanmakla optimizasyon sağlanabilir.
Bilgisayar bilimlerinde dinamik optimizasyon veya memoizasyon olarak bilinen bu optimizasyon tekniği sayesinde (hafıza teknikleri ile karıştırmayınız), özyinelemeli çözüm adımları
doğrusal bir sayı kadar yinelendikten sonra sona erer.
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
344
Aşağıda özyinelemenin doğru kullanımını gösteren bir uygulama verilmiştir:
RecursiveFibonacciMemoization
static long
static
Console
int
int
long
Console
static
1
2
1
1
“n = “
Console
long
2
“fib({0}) = {1}”
int
1
2
return
Gözle görülür bir fark vardır. Önceki örnekte n=100 için sonsuza giden bir hesaplama nedeniyle sonuç uzun zaman sonra hesaplanırken, optimize edilmiş çözüm ile sonuç hemen hesaplanıyor. Daha sonraki “Veri Yapıları ve Algoritma Karmaşıklığı” Bölümü’nde not edileceği
üzere, birinci çözüm üssel, ikinci çözüm doğrusal zamanda çalışmaktadır.
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
345
10.9.6 Fibonacci Sayıları – Yinelemeli Çözüm
Fibonacci sayılarının ardışık hesaplanmasıyla özyineleme kullanmadan problemi çözmenin
de mümkün olabileceğini fark etmişsinizdir. Bu amaçla dizinin sadece son iki elemanı saklanır ve bir sonraki elemanın değerini hesaplamak için saklanan değerler kullanılır. Fibonacci
sayılarını yinelemeli hesaplayan algoritmanın bir uygulaması aşağıda verilmiştir:
IterativeFibonacci
static
Console
int
int
long
Console
“n = “
Console
“fib({0}) = {1}”
static
long
long
long
for
int
0
2
1
1
return
Bu çözüm kısa ve zarif görünüyor, verimlidir ve fazla bellek gerektirmez, ancak yine de özyineleme için varolan riskleri tamamen yok etmiyor.
Bir başka öneriyle önceki örnekleri sonuçlandıralım:
!
Özyineleme güçlü bir programlama tekniğidir, fakat çalışması ve etki alanı hakkında yeterli bilgiye sahip olunmadığı sürece ne olduğunu fark edemeyebilir ve kendi bindiğiniz dalı
kesebilirsiniz. Bu da yanlış programlamaya neden olacağı için dikkatle kullanmalısınız!
www.nakov.com
346
C# İle Bilgisayar Programlama Temelleri
Eğer bu kuralı uygularsanız, özyinelemenin yanlış kullanım olasılığı azalır ve sonuçlarından
etkilenmezsiniz.
10.9.7 Özyineleme ve Yineleme – Daha Fazlası
Doğrusal hesaplama gerektiren süreçler için özyineleme kullanmak genellikle zorunlu değildir, çünkü yinelemeyle sonuçlar kolayca bir araya getirilebilir, bu basit ve etkili bir hesaplama
tekniğidir. Örneğin, doğrusal işlem gerektiren faktöriyel hesaplama probleminde, bir sonraki
elemanın değeri sadece önceki gelen tüm elemanlara bağlıdır.
Doğrusal hesaplama işlemlerinin karakteristik özelliği, her adımda sadece bir kere özyineleme çağrısı ile hesabın sadece bir yönde ilerlenerek tamamlanabilmesidir. Doğrusal hesaplama sürecini şematik olarak şu şekilde tanımlayabiliriz:
void
Metot gövdesinde sadece bir kere özyinelemeli çağrı yapılmışsa özyineleme kullanmak gerekli değildir, çünkü yineleme anlaşılır ve barizdir.
Bununla birlikte, bazen, dallanmış bir hesaplama süreci (ağaç gibi) ile yüz yüze kalırız. Örneğin, N iç içe geçmiş döngülerin taklit edilmesi, yinelemeyle kolayca ifade edilemez. Muhtemelen, iç içe geçmiş döngüleri taklit eden yinelemeli algoritmamızın tamamen farklı bir
prensipte çalıştığını fark etmişsinizdir. Aynı şeyi yineleme olmadan uygulamayı deneyin ve
kolay olmadığını göreceksiniz.
Normalde, her özyineleme, (programın yürütülmesiyle oluşturulan) çağrı yığınını kullanarak
yinelemeye dönüşebilir, ancak bu karmaşıktır ve bunu yapmanın hiçbir yararı yoktur. Özyineleme, belirgin bir yinelemeli çözüme sahip olmadığımız bir problem için basit, anlaşılması
kolay ve etkili bir çözüm sağlaması durumunda kullanılmalıdır.
Yinelemenin her adımında ağaç benzeri (dallı) hesaplama süreçlerinde birkaç özyinelemeli
çağrı yapılır ve hesaplamanın şeması ağaç olarak görülebilir (doğrusal hesaplamalarda olduğu gibi bir liste değil). Örneğin, Fibonacci sayılarını hesapladığımızda özyinelemeli çağrı
ağacının nasıl olacağını gördük.
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
347
Ağaç benzeri dallara sahip bir hesaplama sürecinin şematik gösterimi aşağıdaki gibidir:
void
Ağacın hesaplama süreçleri (doğrusal süreçlerin aksine) doğrudan özyinelemeli olarak ifade
edilemez. Fibonacci için durum basittir, Çünkü her bir sonraki sayı önceki sayıya bakarak hesaplanabilir. Bazen, her sonraki sayı, yalnızca önceki yoluyla hesaplanmaz, bir sonraki sayı
da gereklidir, ve özyinelemeli bağımlılık o kadar basit değildir. Bu durumda memoizasyon
tekniğini uygulayarak yinelenen hesaplamalardan kaçındığınız takdirde, özyineleme doğru
olarak uygulanırsa, çok verimli sonuç verir.
!
Dallı özyinelemeli hesaplamalar için özyineleme kullanın (ve her bir değerin yalnızca bir
kez hesaplandığından emin olun). Doğrusal özyinelemeli hesaplamalar için özyinelemeyi tercih edin.
Son ifadeyi klasik bir örnekle göstereceğiz.
10.9.8 Labirentte İz Sürmek – Örnek
N x M kapıdan oluşan dikdörtgen şekilli bir labirent veriliyor. Kapıların her biri geçilir veya
geçilmez özelliklidir. Sol üst köşesinden labirente giren bir kimse, sağ alt köşeye ulaşmak
istiyor. Kişi yukarı, aşağı, sağa veya sola yöne bir seferde bir kapı hareket edebilir. Bir kapıdan geçildiğinde, geri dönüş yapılmaksızın tekrar aynı kapıdan geçilmesi yasaktır. Ancak
kapı serbest hale gelebilirse geçilebilir. Sağ alt köşeye ulaşıldığına yol tamamlanıyor.
Labirente giren bir kişinin girişten çıkışa kadar izlediği tüm yolları görüntülemek istiyoruz.
www.nakov.com
348
C# İle Bilgisayar Programlama Temelleri
Özyineleme kullanılarak çözülebilen problemlere tipik bir örnektir. Bu problemin yineleme ile
çözümü daha karmaşık ve uygulaması zor olacaktır.
Şekiller üzerinden problemi anlamaya çalışınız:
Belirtimleri yerine getiren girişten çıkışa 3 ayrı yol olduğunu görebilirsiniz. (sadece geçerli
kapılardan geçilmiştir ve aynı kapıdan iki kere geçilmemiştir). Bu 3 yol aşağıda izlenmiştir:
Yukarıdaki şekillerde sayılar yolu izlerken kaç defa dönüş yapıldığına işaret ediyor.
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
349
10.9.9 Labirent Yolları – Özyinelemeli Algoritma
Bu problemi nasıl çözeriz? Labirentin herhangi bir pozisyonundan sonuna kadar arama işlemini bir özyinelemeli süreç olarak aşağıda gibi tanımlayabiliriz:
1.
Başlangıç pozisyonu (0,0) olmak üzere, labirentin geçerli konumu (row, col) olsun.
2.
Mevcut konum (N-1, M-1) ise hedefe varılmıştır. Aranan çıkış sağlanmıştır.
3.
Kapı geçilmezse, geri gideriz (durma hakkımız yoktur).
4.
Pozisyon daha önce ziyaret edilmişse, geri gideriz (iki kere geçme hakkımız yoktur).
5.
Bunların dışında, 4 özyineleme sözkonusudur:
•
Sağa gidiş: (row, col + 1)
•
Aşağı gidiş: (row + 1, col)
•
Yukarı gidiş: (row – 1, col)
•
Sola gidiş: (row, col - 1)
Bu algoritma çözümünü analiz ederken özyinelemeli düşünmeliyiz. Problem cümlesi “verilen
bir pozisyondan çıkışa giden yolu bulmaktır”. Alt problemlere bölünmesi gerekirse:
•
geçerli konumun bir sağındaki pozisyondan çıkışa giden yolu aramak
•
geçerli konumun bir aşağısındaki pozisyondan çıkışa giden yolu aramak
•
geçerli konumun bir yukarısındaki pozisyondan çıkışa giden yolu aramak
•
geçerli konumun bir solundaki pozisyondan çıkışa giden yolu aramak
Ulaştığımız her olası konumdan dört muhtemel yönü kontrol edersek ve bir daire içerisinde
hareket etmezsek (daha önce üzerine adım attığımız konumlardan geçmekten kaçınarak) er
yada geç çıkış yolunu bulmalıyız (varsa).
Bu sefer özyineleme önceki problemler kadar basit değildir. Her adımda çıkışa ulaşıp ulaşmadığımızı ve yasak bir konumda olup olmadığımızı kontrol etmeliyiz; Bundan sonra, bulunduğumuz pozisyonu ziyaret edilmiş olarak işaretlemeliyiz ve dört yönde aramayı özyinelemeli olarak çağırmalıyız. Özyinelemeli aramalardan döndükten sonra, başlangıç noktasını
ziyaret edilmemiş olarak işaretlemeliyiz. Bu gibi gezinme, geriye doğru arama (backtracking) olarak bilinir.
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
350
10.9.10 Labirent Yolları – Uygulama
Algoritma uygulaması için uygun bir şekilde labirenti temsil etmeliyiz. İki boyutlu bir
karakter dizisi kullanacağız. Geçilir pozisyonlar ‘ ‘ boşluk karakteri ile, çıkış pozisyonu ‘e
‘ ile, geçilmez pozisyonlar ‘*’ yıldız karakteri ile gösterilecek. Başlangıç pozisyonu geçilir
pozisyondur. Ziyaret ettiğimiz pozisyonları ‘s’ ile işaretleyeceğiz. Bir labirent temsili aşağıda verilmiştir:
char
‘ ‘
‘*’
‘ ‘
‘ ‘
‘ ‘
‘ ‘
‘*’
‘ ‘
‘*’
‘ ‘
‘ ‘
‘ ‘
‘ ‘
‘*’
‘ ‘
‘*’
‘*’
‘ ‘
‘*’
‘ ‘
‘ ‘
‘ ‘
‘ ‘
‘*’
‘ ‘
‘ ‘
‘*’
‘ ‘
‘*’
‘ ‘
‘ ‘
‘ ‘
‘ ‘
‘ ‘
‘e’
Labirent yollarının bulunması için özyinelemeli metotun uygulaması şöyle olacaktır:
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
351
char
‘ ‘
‘*’
‘ ‘
‘ ‘
‘ ‘
‘ ‘
‘*’
‘ ‘
‘*’
‘ ‘
‘ ‘
‘ ‘
‘ ‘
‘*’
‘ ‘
‘*’
‘*’
‘ ‘
‘*’
‘ ‘
‘ ‘
‘ ‘
‘ ‘
‘*’
‘ ‘
‘ ‘
‘*’
‘ ‘
‘*’
‘ ‘
‘ ‘
‘ ‘
‘ ‘
‘ ‘
‘e’
0
0
1
// We are out of the labyrinth
return
// Check if we have found the exit
‘e’
‘ ‘
// Mark the current cell as visited
‘s’
// Invoke recursion to explore all possible directions
1
// left
1
// up
1
// right
1
// down
// Mark back the current cell as free
‘ ‘
0
www.nakov.com
0
352
C# İle Bilgisayar Programlama Temelleri
Uygulama yukarıdaki tanımı aynen gerçekleştirir. Bu durumda labirentin boyutları N ve M
değişkenlerinde saklanmaz, ancak iki-boyutlu bir lab dizisi yardımıyla, labirentin sütun sayısı lab.GetLength(1) ve satır sayısı lab.GetLength(0) olmak üzere elde edilir.
Arama yapan özyinelemeli metotun girişinde ilk koşul labirentin dışına çıkıp çıkmadığımızın
kontrolüdür. Bu yasak durumda, labirentin sınırları dışına çıkılmaması için metot sonlandırıldı.
Bundan sonra, çıkışı bulup bulmadığımızı kontrol etmeliyiz. Bulduysak, uygun bir mesaj yazdırıyoruz ve bulunduğunuz konumdan ileriye doğru aramayı sonlandırıyoruz.
Sonra, mevcut karenin kullanılabilir olup olmadığını kontrol ediyoruz. Eğer pozisyon geçerliyse, ve önceki adımların bazılarında bu kareye basmadıysak (başlangıç pozisyonundan
labirentin mevcut hücresine kadar olan mevcut yolun bir parçası değilse), mevcut kare kullanılabilir.
Eğer hücre mevcutsa, üzerine basarız. Tanım gereği bu hücreyi ‘s’ karakteriyle işaretleyerek
ziyaret edilmiş olarak kaydetmeliyiz. Bundan sonra dört olası yönde özyinelemeli olarak bir
yol ararız. Bu dört özyinelemeli arama çağrısından geri dönüldüğünde, geçerli hücreden geri
adım atarak, mevcut hücre ‘ ‘ ile işaretlenerek serbest bırakılır.
Geçerli konumun serbest bırakılması önemlidir, çünkü geri döndüğümüzde mevcut yolun bir
parçası değildir. Bu belirtimi atlarsanız, yolların hepsi değil, ancak bazıları bulunmuş olabilir.
Labirentin çıkış yollarını bulmak için uygulanan özyinelemeli metot bu şekilde özetlenmiştir.
Şimdi sadece bu metotu (0, 0) başlangıç pozisyonundan başlatarak Main() metotu içinden
çağırmalıyız.
Program yürütüldüğünde aşağıdaki sonucu döndürür:
Çıkışın tam üç kez bulunduğunu görebilirsiniz. Algoritma düzgün çalışıyor gibi görünüyor;
ancak daha anlamlı olabilmesi için çıkış yolunu yazdırmamız gerekiyor.
www.nakov.com
353
C# İle Bilgisayar Programlama Temelleri
10.9.11 Labirent Yolları – İz Kayıtları
Özyinelemeli algoritma ile bulduğumuz yolları yazdırmak için, her adımda atılan adım yönünü saklayacak bir diziye ihtiyacımız var: sağ – R (right), aşağı – D (down), yukarı – U
(up), sol – L (left). Bu dizi her an için labirentin başından itibaren geçerli yolu tutacak.
Bir karakter dizisi ve sayaç yardımıyla, şu anki özyineleme derinliğini, yani özyinelemeli
olarak kaç pozisyon ilerlediğimizi tutabiliriz. Özyinelemeye her girişte sayaç 1 artırılmalıdır.
Dönüşte 1 azaltılmalıdır. Çıkış yolu bulunduğunda, yazdırılması için gerekli veriler hazırdır (0
ile başlayan ve sayaç endeksine kadar olan dizinin tüm karakterleri).
Dizinin boyutları ne olmalıdır? Bu sorunun cevabı kolaydır; bir hücreye en fazla bir kere girilebildiğine göre en uzun yol N * M kadardır. Örneğimizdeki labirent 7 * 5 boyutlarında olduğu
için dizinin en fazla uzunluğu 35 kabul edilmelidir.
Not: List<T> veri yapısını biliyorsanız, karakter dizisi yerine List<char> kullanmanız daha
uygun olabilir. Listeleri daha detaylı olarak “Doğrusal Veri Yapıları” Bölümü’nde öğreneceksiniz.
Açıklanan fikrin uygulanması aşağıda verilmiştir:
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
354
char
‘ ‘
‘*’
‘ ‘
‘ ‘
‘ ‘
‘ ‘
‘*’
‘ ‘
‘*’
‘ ‘
0
‘ ‘
‘ ‘
‘ ‘
‘*’
‘ ‘
‘*’
‘*’
‘ ‘
‘*’
‘ ‘
‘ ‘
‘ ‘
‘ ‘
‘*’
‘ ‘
‘ ‘
‘*’
‘ ‘
‘*’
‘ ‘
‘ ‘
‘ ‘
‘ ‘
‘ ‘
‘e’
0
1
// We are out of the labyrinth
return
// Append the direction to the path
// Check if we have found the exit
‘e’
‘ ‘
// The current cell is free. Mark it as visited
‘s’
// Invoke recursion to explore all possible directions
1, ‘L’
// left
‘U’
// up
1, ‘R’
// right
1
‘D’
// down
// Mark back the current cell as free
‘ ‘
1
// Remove the last direction from the path
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
355
static void
char[]
Console
int
Found path to the exit:
Console
Console.
void
‘S’
Labirentin çıkış yolunu arayan özyinelemeli metota bir parametre daha ekledik: geçerli konuma ulaşmak için kullandığımız yönü belirtir imleç : R, D, U, L. Bu parametre başlangıç
pozisyonundan geçerken S değerine sahiptir. Ancak bu bir anlam taşımaz. Yolu yazdırırken,
bu ilk elemanı atlarız.
Programı başlatırsak, labirentin başından sonuna kadar üç yol bulunur:
10.9.12 Labirent Yolları – Programın Test Edilmesi
Algoritma düzgün çalışıyor gibi görünüyor. Biraz daha fazla örnek ile test etmek hatasız
çalıştığını gösterecektir.
1x1 boş labirent ile programı test edebilirsiniz, 3 x 3 boş labirent ile, yada çıkış yolu olmayan
bir labirent ile ve birçok yola sahip boyutları büyük bir labirent ile test edebilirsiniz.
Bu testler her durumda programın düzgün çalıştığına sizi ikna edecektir.
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
356
Örnek girdi (1 x 1 boş labirenti):
char
‘e’
Örnek çıktı:
Çıktıdaki yol beklendiği gibi boştur (uzunluğu 0’dır), çünkü başlangıç pozisyonu çıkışla aynıdır. Bu durumda görselleştirmeyi geliştirmek isteyebilirsiniz (“Yol boştur” yazdırmak, vs.)
Örnek girdi ( 3 x 3 boş labirenti):
char
‘ ‘
‘ ‘
‘ ‘
www.nakov.com
‘ ‘
‘ ‘
‘ ‘
‘ ‘
‘ ‘
‘e’
C# İle Bilgisayar Programlama Temelleri
357
Yukarıdaki örnek labirentin çıktısı:
Çıktının doğru olduğunu kabul edebilirsiniz - bunlar tüm çıkış yollarıdır.
Bir başka örnek girdi (çıkışı olmayan 5x3 labirenti):
char
‘ ‘
‘*’
‘*’
‘ ‘
‘ ‘
‘ ‘
‘ ‘
‘ ‘
‘*’
‘ ‘
‘*’
‘ ‘
‘ ‘
‘*’
‘e’
Çıktısı:
(çıkış yok)
Çıktının doğru olduğunu görebilirsiniz, ancak yine de herhangi bir çıktı yerine daha anlamlı
bir mesaj ekleyebilirsiniz (örneğin, “Çıkış yok!”).
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
358
Şimdi çok büyük bir labirent için programa bakalım: Örnek girdi (15x9 labirenti):
char
Program çalıştırıldığında çıkış yollarını yazdırmaya başlar, ancak sonlanmayacaktır çünkü çıkış yolları oldukça fazladır. Birkaç yol fazlası aşağıda verilmiştir:
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
359
Şimdi, son bir örnek için programı çalıştıralım:
Örnek girdi (çıkışı olmayan 15 x 9 labirenti):
char
‘
‘
‘
‘
‘
‘
‘
‘
‘
‘
‘
‘
‘
‘
‘
‘
‘
‘
’*’
’ ‘
’ ‘
’ ‘
’ ‘
’ ‘
’*’
’ ‘
’ ‘
’ ‘
’*’
’ ‘
’ ‘
’ ‘
’ ‘
’*’
’ ‘
’ ‘
’ ‘
’ ‘
’ ‘
’ ‘
’ ‘
’ ‘
’*’
’ ‘
’ ‘
’
’
’
’
’
’
’
’
’
‘
‘
‘
‘
‘
‘
‘
‘
‘
’ ‘
’ ‘
’ ‘
’ ‘
’*’
’*’
’*’
’*’
’*’
’*’
’ ‘
’ ‘
’*’
’ ‘
’ ‘
’ ‘
’ ‘
’ ‘
’
’
’
’
’
’
’
’
’
‘
‘
‘
‘
‘
‘
‘
‘
‘
’
’
’
’
’
’
’
’
’
‘
‘
‘
‘
‘
‘
‘
‘
‘
’
’
’
’
’
’
’
’
’
‘
‘
‘
‘
‘
‘
‘
‘
‘
’
’
’
’
’
’
’
’
’
‘
‘
‘
‘
‘
‘
‘
‘
‘
’*’
’ ‘
’ ‘
’ ‘
’ ‘
’ ‘
’*’
’*’
’ ‘
’*’
’ ‘
’ ‘
’ ‘
’ ‘
’ ‘
’*’
’*’
’ ‘
’ ‘
’ ‘
’ ‘
’ ‘
’ ‘
’ ‘
’*’
’ ‘
’*’
’ ‘
’ ‘
’ ‘
’ ‘
’ ‘
’ ‘
’*’
’ ‘
’e’
Program çalıştırıldığında çıktı yazdırmadan kilitleniyor. Çok uzun zamandır çalıştığı için sorun
olabilir.
Sorun nedir? Ortalama boyutu 20 olan bir labirent için 4 kere özyinelemeli çağrı vardır. Bunun anlamı 420 kere özyineleme çağrılır. Bu da programın aramasını tamamlayıp sonlanması
için oldukça büyük bir rakamdır. Belki sayılar tam kesinlikte değildir, ancak programın çalışma zamanı hakkında bize bir fikir veriyor.
Sonuç nedir? Değişkenler çok fazla olduğunda geriye doğru izleme yöntemi işe yaramaz. Bu
sorun büyük bir labirentin tüm çıkış yollarını bulma problemi için geçerlidir. Çalışma zamanı
sorununu çözmek için daha fazla enerji tüketmenizi istemeyeceğiz. Ancak şunu belirtmekte
fayda vardır ki, problem tanımımızı çok az değiştirerek, yani örneğin sadece bir yol bulunmasını isteyerek, çalışma zamanı sorunundan sizi kurtarabiliriz. Programda geri dönülen
yollar için özyinelemenin mevcut geçerli pozisyonunu serbest bırakmayarak bu değişikliği
gerçekleştirebiliriz. Bu, aşağıdaki satırları koddan silmek demektir:
// Mark back the current cell as free
‘ ‘
Bunun sonrasında program çok hızlı olarak çıkış olup olmadığına karar verir, ve varsa onu
çabucak bulur. En kısa veya en uzun değil, sadece ilk yolu muhakkak bulacaktır.
www.nakov.com
360
C# İle Bilgisayar Programlama Temelleri
10.10 Özyineleme Kullanımı – Sonuçlar
Labirentte çıkış yolu arama probleminden genel sonuç zaten formüle edilmiştir: Eğer özyinelemenin nasıl çalıştığını çok iyi anlamakta güçlük yaşıyorsanız, kullanmayınız!
Özyinelemeli metotları yazarken dikkatli olunuz. Özyineleme en çok kombinasyon problemlerini çözmek için elverişli güçlü bir programlama tekniğidir; fakat yanlışlara neden olunabilir
ve dikkatsiz kullanma nedeniyle herkese önerilmez. Programın sonlanamaması ve yığın taşmasına neden olması durumlarında, yinelemeli yaklaşımlarla probleme bir çözüm geliştirmeye, problemin tanımı analiz edildikten sonra karar verilmelidir.
Örneğin, labirentte en kısa çıkış yolunu bulmak ile tanımlı problemi analiz ederseniz
özyineleme kullanmadan, sadece (BFS) Breadth-First Search yardımıyla çözebilirsiniz.
Kuyruk (queue) veri yapısı ile uygulamaları bulunan “BFS” algoritması Wavefront
algoritması olarak da bilinir. http://en.wikipedia.org/wiki/Breadth-first_search makalesindeki “BFS” algoritması hakkında Wikipedia’da daha fazla bilgi bulabilirsiniz.
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
361
Alıştırmalar
1. n iç içe döngüyü simüle eden bir program geliştirin.
2. k sınıfından n eleman için tüm çeşitlemeleri tekrarlarıyla listeleyen bir özyinelemeli program geliştirin. Örnek girdi:
Çıktısı:
Aynı görev için yinelemeli algoritma uygulayın.
3. n elemanlı bir küme içinden k elemanlı tüm kombinasyonları tekrarlarıyla listeleyen bir özyinelemeli program geliştirin. Örnek girdi:
Çıktısı:
Aynı görev için yinelemeli algoritma uygulayın.
4. Verilen bir sözcük dizisi ve k değeri için, sözcük dizisinin k elemanlı tüm öz alt kümelerini listeleyen özyinelemeli program geliştirin. Örnek girdi:
Çıktısı:
Aynı görev için yinelemeli bir algoritma uygulayın.
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
362
5. Verilen bir sözcük dizisi için, sözcük dizisinin tüm alt kümelerini listeleyen özyinelemeli program
geliştirin. Örnek girdi:
Çıktısı:
Aynı görev için yinelemeli algoritma uygulayın.
6. Birleştirme-sıralama (merge-sort) algoritmasını özyinelemeli uygulayın. Bu algoritmaya girdi olarak verilen bir dizi önce iki eşit parçaya bölünür ve özyinelemeli olarak sıralanır. Sonrasında
sıralanan parçalar bütün diziyi oluşturmak üzere birleştirilir.
7. Verilen bir n tamsayısı için 1, 2, …, n sayılarının tüm permütasyonlarını listeleyen özyinelemeli
program geliştirin. Örnek girdi:
Çıktısı:
Aynı görev için yinelemeli algoritma uygulayın.
8. Verilen bir tamsayı dizisi ve N sayısı için, dizideki sayıların toplamı N olan tüm alt kümelerini
bulan özyinelemeli program geliştirin. Örneğin {2, 1 , 3 , -1} dizisi ve N=4 için, toplamı N=4 olan
sayılar şöyle elde edilir: 4=2+3-1; 4=3+1.
9. Verilen bir pozitif tamsayı dizisi için, elemanları toplamı S olan herhangi bir alt kümesinin olup
olmadığını hesaplayan bir program geliştirin. Büyük diziler için program verimli çalışabilir mi?
10.Verilen iki pozisyon için, geçerli ve geçersiz kapıları olan bir labirent üzerinde iki pozisyon arasında bulunan tüm yolları hesaplayan bir program geliştirin.
11.Labirentin en kısa yolunu bulmak için BFS (breadth-first search) algoritmasını uygulayan bir
program geliştirin.
www.nakov.com
C# İle Bilgisayar Programlama Temelleri
363
12.İki pozisyon arasındaki tüm yolları hesaplayan bir önceki alıştırmada istenen programı iki pozisyon arasında bir yol olup olmadığını kontrol edecek şekilde değiştirin.
13.Geçerli ve geçersiz kapıları olan verilen bir labirent için tek doğrultuda yol dönüş yapmaksızın,
başka tarafa sapmaksızın, komşu ve ardışık pozisyonlardan oluşan en büyük alanı hesaplayan
program geliştirin.
14.C:\ hard diski içindeki tüm klasör ve dosyaları ziyaret eden ve özyinelemeli listeleyen bir program geliştirin.
www.nakov.com
Download
Study collections