Python’da Özyinelemeli Fonksiyonlar: Recursive Functions
Bu yazımızda, Python’da özyinelemeli fonksiyonlar olarak Türkçeleştirebileceğimiz recursive functions konu anlatımı yapacağız. Bu fonksiyonların nasıl oluşturulduğunu ve ne zaman kullanılması gerektiğini ele alacağız.
Python’da Özyinelemeli Fonksiyonlar
“Özyineleme”, en genel anlamıyla bir yapının “kendini tekrar etme” durumudur. Özyinelemeli fonksiyonların isimlendirmesinin altında yatan mantık da zaten bu fonksiyonların kendi içlerinde doğrudan ya da dolaylı olarak kendi kendilerini çağırmasıdır. Yani, bir fonksiyon tanımının içerisinde aynı fonksiyonun tekrar çağrılmasına dayanır. Bu “kendi kendini çağırma” eylemi, fonksiyonun adının “özyineleme” olarak belirlenmesine neden olmuştur.
Bu tür bir yaklaşım, özellikle bir problemin daha küçük alt problemlere bölünebildiği durumlarda kullanışlıdır. Bu küçük alt problemler, aynı fonksiyon ile çözülebilir ve sonuçlar birleştirilerek ana problemin çözümüne ulaşılır. Özyinelemeli fonksiyonların bu “kendi kendini tekrar etme” özelliği, onları bazı matematiksel problemler ve algoritmalarda son derece etkili kılar.
Tabiri caizse bir problemin çözümünü; “böl, parçala, fethet” şeklinde yapar. Bu parçalanma, problemin parçalanamayan en basit parçası olarak adlandırılan taban parçasına kadar devam eder. Yani en üstten başlayarak tabana kadar iner ve sonrasında da tabanda bulduğu değeri geriye doğru en üste kadar taşır. Bu yönüyle döngülerden ayrılır.
Bu fonksiyonlar, bazı problemlerin çözümünde oldukça etkili olabilir. Ancak dikkatli kullanılmazlarsa, sonu gelmeyen döngülere neden olabilirler. “Peki, hangi durumlarda özyinelemeli fonksiyonları kullanmalıyız?”
Eğer bir problemi daha küçük parçalara bölebilirseniz ve bu parçaların her biri aynı yöntemle çözülebilirse, özyinelemeli fonksiyonlar bu işlemler için idealdir.
Faktöriyel hesaplamaları, özyinelemeli fonksiyonların kullanımını anlatan tipik bir örnek olarak karşımıza çıkar. Zira bir sayının faktöriyeli, o sayıdan 1’e kadar olan tüm sayıların çarpımıdır. Özyinelemeli bir yaklaşım kullanarak şu şekilde hesaplayabiliriz:
1 2 3 4 5 6 7 8 9 |
def faktoriyel(n): # Taban durumu if n == 0: return 1 # Özyinelemeli çağrı else: return n * faktoriyel(n-1) print(faktoriyel(5)) # 120 olarak çıktı verir |
Yukarıdaki örnekte, faktoriyel
fonksiyonu kendi içinde tekrar çağrılıyor (faktoriyel(n-1)
). Ancak n==0
olduğunda doğrudan 1
döndürülüyor. Burası, özyinelemenin sonlanması için kritik bir adımdır. Eğer bu taban durumu olmasaydı, fonksiyon kendisini sürekli çağırmaya devam eder ve sonsuz bir döngüye girerdi.
Benzer şekilde, özyineleme fonksiyonu kullanarak fibonacci dizisinin n. terimini bulma örneğine bakalım:
1 2 3 4 5 6 7 8 9 10 11 |
def fibonacci (n): if n ==1: return 1 elif n== 2: return 1 else: return (fibonacci (n-1)+fibonacci (n-2)) n= int (input ("Bir rakam giriniz!:")) fib = fibonacci (n) print ("n'in fibonaccisi=", fib) |
Aşağıdaki resimde, özyinelemeli olarak çözülen fibonacci fonksiyonumuz için kullanıcının girdiği 5 rakamıyla ortaya çıkan çalışma şeması gösterilmiştir:
Yukarıdaki resimde de görüleceği üzere kodumuz önce kullanıcının girdiği 5 değerinden başlıyor. O değeri bulabilmek için programın parçalanabilecek en küçük parçası olan 1’e kadar iniyor ve sonra hesapladığı değerden sonraki en yüksek değeri benzer şekilde hesaplamaya gidiyor.
Ancak belirtmek isterim ki, Python’da recursive fonksiyonlar 1000 derinlik limitine sahiptir. Yani kullanıcı 999 değil de 1000 sayısını girdiği anda hesaplama yapamayacak ve aşağıdaki hatayı verecektir:
1 |
RecursionError: maximum recursion depth exceeded in comparison |
Zaten özyinelemeli fonksiyonlar hesapladığı değerleri tekrar tekrar hesapladığı için bilgisayarı aşırı şekilde yorar. Haliyle, kullanıcının girdiği değer yüksek oldukça yapılan işlem sayısı artmakta ve bu da sistemin iyice yavaşlamasına yol açmaktadır.
Görüldüğü üzere, özyineleme (recursion) yaklaşımında, bir problem daha yönetilebilir alt problemlere bölünür. Bu alt problemler, asıl problemle aynı tiptedir ve yine aynı çözüm yolu ile çözülür. En basit haliyle (taban durumu) ulaşıldığında, bu durum genellikle direkt bir sonuçla sonuçlanır. Daha sonra bu basit sonuçlar, yukarıda daha kompleks problemleri çözmek için birleştirilir.
Özyinelemenin bu “böl ve fethet” yaklaşımı, bazı problemlerin çözümü için oldukça etkili olabilir. Ancak, yanlış veya dikkatsizce kullanıldığında sonsuz döngülere veya istenmeyen sonuçlara neden olabilir. Bu nedenle, bir özyinelemeli fonksiyonun doğru çalışmasını sağlamak için taban durumunun doğru tanımlandığından emin olunmalıdır.