Kekambuhan Di Jawa

1. Pengenalan

Dalam artikel ini, kita akan memfokuskan pada konsep teras dalam bahasa pengaturcaraan - pengulangan.

Kami akan menerangkan ciri-ciri fungsi rekursif dan menunjukkan cara menggunakan rekursi untuk menyelesaikan pelbagai masalah di Jawa.

2. Memahami Pengulangan

2.1. Definisi

Di Jawa, mekanisme fungsi-panggilan menyokong kemungkinan adanya kaedah memanggilnya sendiri . Fungsi ini dikenali sebagai rekursi .

Sebagai contoh, andaikan kita mahu menjumlahkan bilangan bulat dari 0 hingga beberapa nilai n :

public int sum(int n) { if (n >= 1) { return sum(n - 1) + n; } return n; }

Terdapat dua syarat utama fungsi rekursif:

  • Keadaan Berhenti - fungsi mengembalikan nilai apabila keadaan tertentu dipenuhi, tanpa panggilan berulang
  • The Recursive Call - fungsi memanggil dirinya dengan input yang selangkah lebih dekat ke keadaan berhenti

Setiap panggilan berulang akan menambah bingkai baru ke memori timbunan JVM. Jadi, jika kita tidak memperhatikan seberapa dalam panggilan rekursif kita dapat menyelam, pengecualian memori mungkin berlaku.

Masalah berpotensi ini dapat diatasi dengan memanfaatkan pengoptimuman rekursi ekor.

2.2. Tail Recursion Versus Head Recurion

Kami merujuk fungsi recursive sebagai tail-recursion apabila panggilan recursive adalah perkara terakhir yang berfungsi. Jika tidak, ia dikenali sebagai recurion kepala .

Pelaksanaan kami di atas fungsi sum () adalah contoh rekursi kepala dan dapat diubah menjadi rekursi ekor:

public int tailSum(int currentSum, int n) { if (n <= 1) { return currentSum + n; } return tailSum(currentSum + n, n - 1); }

Dengan recursion ekor, panggilan recursive adalah perkara terakhir yang dilakukan kaedah ini, jadi tidak ada yang tersisa untuk dilaksanakan dalam fungsi semasa.

Oleh itu, secara logiknya tidak perlu menyimpan kerangka susunan fungsi semasa.

Walaupun pengkompil dapat menggunakan titik ini untuk mengoptimumkan memori, harus diingat bahawa penyusun Java tidak mengoptimumkan pengulangan ekor untuk saat ini .

2.3. Recursion Versus Iteration

Pengulangan dapat membantu mempermudah pelaksanaan beberapa masalah yang rumit dengan menjadikan kodnya lebih jelas dan lebih mudah dibaca.

Tetapi seperti yang telah kita lihat pendekatan rekursif sering memerlukan lebih banyak memori kerana memori timbunan yang diperlukan meningkat dengan setiap panggilan rekursif.

Sebagai alternatif, jika kita dapat menyelesaikan masalah dengan berulang, kita juga dapat menyelesaikannya dengan lelaran.

Sebagai contoh, kaedah penjumlahan kami dapat dilaksanakan menggunakan lelaran:

public int iterativeSum(int n) { int sum = 0; if(n < 0) { return -1; } for(int i=0; i<=n; i++) { sum += i; } return sum; }

Sebagai perbandingan dengan rekursi, pendekatan berulang dapat berpotensi memberikan prestasi yang lebih baik. Oleh itu, lelaran akan lebih rumit dan sukar difahami berbanding dengan berulang, contohnya: melintasi pokok binari.

Membuat pilihan yang tepat antara rekursi kepala, rekursi ekor dan pendekatan berulang semuanya bergantung pada masalah dan situasi tertentu.

3. Contohnya

Sekarang, mari kita cuba menyelesaikan beberapa masalah secara berulang.

3.1. Mencari Kuasa N-Th Sepuluh

Katakan kita perlu mengira daya n -ke-10. Di sini input kita adalah n. Berfikir secara rekursif, kita dapat mengira (n-1) -th kekuatan 10 pertama, dan mengalikan hasilnya dengan 10.

Kemudian, untuk mengira daya (n-1) -th 10 adalah kekuatan (n-2) -th 10 dan darabkan hasilnya dengan 10, dan seterusnya. Kita akan terus seperti ini sehingga kita sampai ke titik di mana kita perlu mengira kekuatan (nn) -th dari 10, iaitu 1.

Sekiranya kami ingin menerapkannya di Java, kami akan menulis:

public int powerOf10(int n) { if (n == 0) { return 1; } return powerOf10(n-1) * 10; }

3.2. Mencari Unsur N-Th Jujukan Fibonacci

Bermula dengan 0 dan 1 , yang Fibonacci Sequence adalah satu urutan nombor di mana setiap nombor ditakrifkan sebagai jumlah dua nombor meneruskan ia : 0 1 1 2 3 5 8 13 21 34 55 ...

Oleh itu, memandangkan angka n , masalah kita ialah mencari unsur n - urutan Fibonacci Sequence . Untuk melaksanakan penyelesaian rekursif, kita perlu mengetahui Keadaan Berhenti dan Panggilan Rekursif .

Nasib baik, ia benar-benar mudah.

Mari kita sebut f (n) nilai n -urutan. Maka kita akan mempunyai f (n) = f (n-1) + f (n-2) ( Panggilan Rekursif ) .

Sementara itu, f (0) = 0 dan f (1) = 1 ( Keadaan Berhenti) .

Maka, sangat jelas bagi kita untuk menentukan kaedah rekursif untuk menyelesaikan masalah:

public int fibonacci(int n) { if (n <= 1) { return n; } return fibonacci(n-1) + fibonacci(n-2); }

3.3. Menukar dari Perpuluhan ke Perduaan

Sekarang, mari kita pertimbangkan masalah menukar nombor perpuluhan menjadi perduaan. Syaratnya adalah untuk melaksanakan kaedah yang menerima nilai integer positif n dan mengembalikan representasi String binari .

Salah satu pendekatan untuk menukar nombor perpuluhan menjadi binari adalah dengan membahagi nilainya dengan 2, mencatat selebihnya dan terus membahagikan hasil bagi dengan 2.

Kami terus membelah seperti itu sehingga kami mendapat hasil 0 . Kemudian, dengan menuliskan semua baki dalam urutan rizab, kami memperoleh rentetan binari.

Oleh itu, masalah kami adalah menulis kaedah yang mengembalikan baki ini dalam susunan rizab:

public String toBinary(int n) { if (n <= 1 ) { return String.valueOf(n); } return toBinary(n / 2) + String.valueOf(n % 2); }

3.4. Tinggi Pokok Perduaan

Ketinggian pokok binari ditakrifkan sebagai bilangan tepi dari akar ke daun paling dalam. Masalah kita sekarang ialah mengira nilai ini untuk pokok binari yang diberikan.

Satu kaedah mudah ialah mencari daun paling dalam kemudian mengira tepi antara akar dan daun itu.

Tetapi cuba memikirkan penyelesaian rekursif, kita dapat menyatakan semula definisi untuk ketinggian pokok binari sebagai ketinggian maksimum cabang kiri akar dan cabang kanan akar, ditambah 1 .

Sekiranya akar tidak mempunyai cabang kiri dan cabang kanan, tingginya adalah sifar .

Inilah pelaksanaan kami:

public int calculateTreeHeight(BinaryNode root){ if (root!= null) { if (root.getLeft() != null || root.getRight() != null) { return 1 + max(calculateTreeHeight(root.left), calculateTreeHeight(root.right)); } } return 0; }

Oleh itu, kita melihat bahawa beberapa masalah dapat diselesaikan dengan pengulangan dengan cara yang sangat sederhana.

4. Kesimpulan

Dalam tutorial ini, kami telah memperkenalkan konsep rekursi di Java dan menunjukkannya dengan beberapa contoh mudah.

Pelaksanaan artikel ini boleh didapati di Github.