Đệ quy là một chủ đề khó hiểu và tôi không nghĩ rằng mình có thể làm điều đó một cách công bằng ở đây. Thay vào đó, tôi sẽ cố gắng tập trung vào đoạn mã cụ thể mà bạn có ở đây và cố gắng mô tả cả trực giác về lý do tại sao giải pháp hoạt động và cơ chế của cách mã tính toán kết quả của nó.
Đoạn mã bạn đưa ra ở đây giải quyết vấn đề sau: bạn muốn biết tổng của tất cả các số nguyên từ a đến b, bao gồm cả. Ví dụ: bạn muốn tổng các số từ 2 đến 5, bao gồm
2 + 3 + 4 + 5
Khi cố gắng giải quyết một vấn đề một cách đệ quy, một trong những bước đầu tiên nên là tìm cách chia vấn đề thành một bài toán nhỏ hơn với cùng cấu trúc. Vì vậy, giả sử rằng bạn muốn tổng hợp các số từ 2 đến 5, bao gồm cả. Một cách để đơn giản hóa điều này là lưu ý rằng tổng trên có thể được viết lại thành
2 + (3 + 4 + 5)
Ở đây, (3 + 4 + 5) là tổng của tất cả các số nguyên từ 3 đến 5, bao gồm cả. Nói cách khác, nếu bạn muốn biết tổng của tất cả các số nguyên từ 2 đến 5, hãy bắt đầu bằng cách tính tổng của tất cả các số nguyên từ 3 đến 5, sau đó cộng 2.
Vậy làm cách nào để tính tổng của tất cả các số nguyên từ 3 đến 5, bao gồm cả? Chà, tổng đó là
3 + 4 + 5
thay vào đó có thể được coi là
3 + (4 + 5)
Ở đây, (4 + 5) là tổng của tất cả các số nguyên từ 4 đến 5, bao gồm cả. Vì vậy, nếu bạn muốn tính tổng của tất cả các số từ 3 đến 5, bao gồm cả, bạn sẽ tính tổng của tất cả các số nguyên từ 4 đến 5, sau đó cộng 3.
Có một mẫu ở đây! Nếu bạn muốn tính tổng các số nguyên giữa a và b, bao gồm cả, bạn có thể thực hiện như sau. Đầu tiên, tính tổng các số nguyên giữa a + 1 và b, bao gồm cả. Tiếp theo, thêm a vào tổng số đó. Bạn sẽ nhận thấy rằng "tính tổng các số nguyên giữa a + 1 và b, inclusive" xảy ra khá giống loại vấn đề mà chúng tôi đang cố gắng giải quyết, nhưng với các tham số hơi khác. Thay vì tính toán từ a đến b, bao gồm, chúng tôi tính toán từ a + 1 đến b, bao gồm cả. Đó là bước đệ quy - để giải quyết vấn đề lớn hơn ("tổng từ a đến b, bao gồm"), chúng tôi giảm vấn đề thành một phiên bản nhỏ hơn của chính nó ("tổng từ a + 1 đến b, bao gồm.").
Nếu bạn xem mã bạn có ở trên, bạn sẽ nhận thấy rằng có bước này trong đó:
return a + sumInts(a + 1, b: b)
Mã này chỉ đơn giản là một bản dịch của logic ở trên - nếu bạn muốn tính tổng từ a đến b, bao gồm, hãy bắt đầu bằng cách tổng từ a + 1 đến b, bao gồm (đó là lệnh gọi đệ quy cho sumInt
s), sau đó thêm a
.
Tất nhiên, bản thân cách tiếp cận này sẽ không thực sự hiệu quả. Ví dụ, bạn sẽ tính tổng của tất cả các số nguyên từ 5 đến 5 bằng cách nào? Vâng, sử dụng logic hiện tại của chúng tôi, bạn sẽ tính tổng của tất cả các số nguyên từ 6 đến 5, bao gồm, sau đó cộng 5. Vậy làm cách nào để tính tổng của tất cả các số nguyên từ 6 đến 5, bao gồm cả? Chà, sử dụng logic hiện tại của chúng tôi, bạn sẽ tính tổng của tất cả các số nguyên từ 7 đến 5, bao gồm, sau đó thêm 6. Bạn sẽ nhận thấy một vấn đề ở đây - điều này cứ tiếp tục và liên tục!
Trong giải quyết vấn đề đệ quy, cần phải có một số cách để ngừng đơn giản hóa vấn đề và thay vào đó chỉ đi giải quyết nó một cách trực tiếp. Thông thường, bạn sẽ tìm một trường hợp đơn giản mà câu trả lời có thể được xác định ngay lập tức, sau đó cấu trúc giải pháp của bạn để giải quyết các trường hợp đơn giản trực tiếp khi chúng phát sinh. Đây thường được gọi là trường hợp cơ sở hoặc cơ sở đệ quy .
Vậy trường hợp cơ bản trong vấn đề cụ thể này là gì? Khi bạn tính tổng các số nguyên từ a đến b, nếu a lớn hơn b, thì câu trả lời là 0 - không có bất kỳ số nào trong phạm vi! Do đó, chúng tôi sẽ cấu trúc giải pháp của mình như sau:
- Nếu a> b, thì câu trả lời là 0.
- Nếu không (a ≤ b), nhận được câu trả lời như sau:
- Tính tổng các số nguyên giữa a + 1 và b.
- Thêm a để có câu trả lời.
Bây giờ, hãy so sánh mã giả này với mã thực của bạn:
func sumInts(a: Int, b: Int) -> Int {
if (a > b) {
return 0
} else {
return a + sumInts(a + 1, b: b)
}
}
Lưu ý rằng hầu như có một bản đồ 1-1 giữa giải pháp được nêu trong mã giả và mã thực tế này. Bước đầu tiên là trường hợp cơ sở - trong trường hợp bạn yêu cầu tổng của một dãy số trống, bạn sẽ nhận được 0. Nếu không, hãy tính tổng giữa a + 1 và b, sau đó cộng a.
Cho đến nay, tôi chỉ đưa ra một ý tưởng cấp cao đằng sau mã. Nhưng bạn có hai câu hỏi khác, rất hay. Đầu tiên, tại sao điều này không luôn trả về 0, vì hàm nói rằng trả về 0 nếu a> b? Thứ hai, số 14 thực sự đến từ đâu? Chúng ta hãy lần lượt xem xét những điều này.
Hãy thử một trường hợp rất, rất đơn giản. Điều gì xảy ra nếu bạn gọi sumInts(6, 5)
? Trong trường hợp này, truy tìm mã, bạn thấy rằng hàm chỉ trả về 0. Đó là điều đúng đắn cần làm, vì - không có bất kỳ số nào trong phạm vi. Bây giờ, hãy thử điều gì đó khó hơn. Điều gì xảy ra khi bạn gọi sumInts(5, 5)
? Chà, đây là những gì sẽ xảy ra:
- Bạn gọi
sumInts(5, 5)
. Chúng tôi rơi vàoelse
nhánh trả về giá trị của `a + sumInts (6, 5).
- Để
sumInts(5, 5)
xác định điều gì sumInts(6, 5)
đang xảy ra, chúng ta cần tạm dừng công việc đang làm và thực hiện cuộc gọi đến sumInts(6, 5)
.
sumInts(6, 5)
được gọi. Nó đi vào if
chi nhánh và quay trở lại 0
. Tuy nhiên, trường hợp này của sumInts
được gọi bởi sumInts(5, 5)
, do đó, giá trị trả về được truyền trở lại sumInts(5, 5)
, không phải cho người gọi cấp cao nhất.
sumInts(5, 5)
bây giờ có thể tính toán 5 + sumInts(6, 5)
để lấy lại 5
. Sau đó, nó sẽ trả lại cho người gọi cấp cao nhất.
Lưu ý cách giá trị 5 được hình thành ở đây. Chúng tôi đã bắt đầu với một cuộc gọi hiện hoạt tới sumInts
. Điều đó đã kích hoạt một cuộc gọi đệ quy khác và giá trị được trả về bởi cuộc gọi đó đã truyền thông tin trở lại sumInts(5, 5)
. Cuộc gọi đếnsumInts(5, 5)
Đến lượt nó, thực hiện một số tính toán và trả về một giá trị cho người gọi.
Nếu bạn thử điều này với sumInts(4, 5)
, đây là những gì sẽ xảy ra:
sumInts(4, 5)
cố gắng trở lại 4 + sumInts(5, 5)
. Để làm điều đó, nó kêu gọi sumInts(5, 5)
.
sumInts(5, 5)
cố gắng trở lại 5 + sumInts(6, 5)
. Để làm điều đó, nó kêu gọi sumInts(6, 5)
.
sumInts(6, 5)
trả về 0 trở lại sumInts(5, 5).</li>
<li>
sumInts (5, 5) now has a value for
sumInts (6, 5) , namely 0. It then returns
5 + 0 = 5`.
sumInts(4, 5)
bây giờ có một giá trị cho sumInts(5, 5)
, cụ thể là 5. Sau đó nó trả về 4 + 5 = 9
.
Nói cách khác, giá trị được trả về được hình thành bằng cách tính tổng các giá trị tại một thời điểm, mỗi lần lấy một giá trị được trả về bởi một lệnh gọi đệ quy cụ thể sumInts
và thêm vào giá trị hiện tại của a
. Khi đệ quy chạm đáy, lệnh gọi sâu nhất trả về 0. Tuy nhiên, giá trị đó không thoát ngay khỏi chuỗi lệnh gọi đệ quy; thay vào đó, nó chỉ đưa giá trị trở lại lệnh gọi đệ quy một lớp phía trên nó. Theo cách đó, mỗi lệnh gọi đệ quy chỉ thêm một số nữa và trả về số đó cao hơn trong chuỗi, lên đến đỉnh điểm là tổng thể. Như một bài tập, hãy thử theo dõi điều này đểsumInts(2, 5)
, đó là điều bạn muốn bắt đầu.
Hi vọng điêu nay co ich!