Trong tiếng Anh đơn giản, đệ quy là gì?


74

Ý tưởng về đệ quy không phổ biến lắm trong thế giới thực. Vì vậy, nó có vẻ hơi khó hiểu với các lập trình viên mới làm quen. Mặc dù, tôi đoán, họ dần dần quen với khái niệm này. Vì vậy, những gì có thể là một lời giải thích tốt đẹp để họ nắm bắt ý tưởng dễ dàng?


Thông tin thêm đang được chia sẻ về chủ đề này tại Tài nguyên để cải thiện sự hiểu biết của bạn về đệ quy?
Kenneth


1
Đệ quy là khi một hàm có thể gọi chính nó. "Nếu bạn hoàn toàn hiểu không gian tên và phạm vi và cách các tham số được truyền cho một hàm, thì bạn đã biết đệ quy rồi. Tôi có thể hiển thị các ví dụ, nhưng bạn sẽ có thể tự mình tìm ra cách chúng hoạt động." Các sinh viên thường đấu tranh với đệ quy không quá nhiều vì nó khó hiểu, nhưng vì họ không nắm vững phạm vi / không gian tên biến. Trước khi đi sâu vào đệ quy, hãy đảm bảo rằng các sinh viên có thể theo dõi chính xác thông qua một chương trình mà bạn đã cố tình đưa ra các biến ở các phạm vi khác nhau cùng tên để gây nhầm lẫn cho họ.
dspyz


1
Để hiểu đệ quy, trước tiên bạn phải hiểu đệ quy
Goerman

Câu trả lời:


110

Để giải thích đệ quy , tôi sử dụng kết hợp nhiều cách giải thích khác nhau, thường là cả hai cố gắng:

  • giải thích khái niệm
  • giải thích tại sao nó quan trọng
  • giải thích làm thế nào để có được nó.

Để bắt đầu, Wolfram | Alpha định nghĩa nó theo thuật ngữ đơn giản hơn Wikipedia :

Một biểu thức sao cho mỗi thuật ngữ được tạo bằng cách lặp lại một phép toán cụ thể.


Toán học

Nếu học sinh của bạn (hoặc người bạn giải thích quá, từ bây giờ tôi sẽ nói học sinh) có ít nhất một số nền tảng toán học, rõ ràng là chúng đã gặp phải đệ quy bằng cách nghiên cứu loạt và khái niệm về đệ quyquan hệ lặp lại của chúng .

Một cách rất tốt để bắt đầu sau đó là thể hiện bằng một loạt và nói rằng nó khá đơn giản là những gì đệ quy nói về:

  • một hàm toán học ...
  • ... Nó tự gọi mình để tính toán một giá trị tương ứng với phần tử thứ n ...
  • ... Và xác định một số ranh giới.

Thông thường, bạn có thể nhận được "huh huh, whatev '" vì họ vẫn không sử dụng nó, hoặc nhiều khả năng chỉ là một tiếng ngáy rất sâu.


Ví dụ mã hóa

Đối với phần còn lại, nó thực sự là một phiên bản chi tiết về những gì tôi thể hiện trong Phụ lục của câu trả lời của tôi cho câu hỏi mà bạn chỉ ra liên quan đến con trỏ (chơi chữ xấu).

Ở giai đoạn này, học sinh của tôi thường biết cách in một cái gì đó lên màn hình. Giả sử chúng ta đang sử dụng C, họ biết cách in một char bằng cách sử dụng writehoặc printf. Họ cũng biết về các vòng điều khiển.

Tôi thường sử dụng một vài vấn đề lập trình đơn giản và lặp đi lặp lại cho đến khi họ hiểu được:

  • các hoạt động giai thừa ,
  • một máy in bảng chữ cái,
  • một máy in bảng chữ cái đảo ngược,
  • các phép toán lũy thừa .

yếu tố

Yếu tố là một khái niệm toán học rất đơn giản để hiểu, và việc thực hiện rất gần với biểu diễn toán học của nó. Tuy nhiên, họ có thể không nhận được nó lúc đầu.

Định nghĩa đệ quy của hoạt động nhân tố

Bảng chữ cái

Phiên bản bảng chữ cái rất thú vị để dạy họ suy nghĩ về thứ tự của các câu lệnh đệ quy. Giống như với con trỏ, họ sẽ chỉ ném ngẫu nhiên vào bạn. Vấn đề là đưa chúng đến nhận ra rằng một vòng lặp có thể được đảo ngược bằng cách sửa đổi các điều kiện HOẶC chỉ bằng cách đảo ngược thứ tự của các câu lệnh trong hàm của bạn. Đó là nơi in bảng chữ cái giúp, vì nó là một cái gì đó trực quan cho họ. Đơn giản chỉ cần họ viết một hàm sẽ in một ký tự cho mỗi cuộc gọi và gọi chính nó một cách đệ quy để viết tiếp (hoặc trước đó).

Các fan hâm mộ của FP, bỏ qua thực tế rằng việc in các thứ vào luồng đầu ra là một tác dụng phụ bây giờ ... Chúng ta đừng quá khó chịu trên mặt trận FP. (Nhưng nếu bạn sử dụng một ngôn ngữ có hỗ trợ danh sách, hãy thoải mái nối với một danh sách ở mỗi lần lặp và chỉ in kết quả cuối cùng. .

Lũy thừa

Vấn đề lũy thừa hơi khó khăn hơn ( ở giai đoạn học tập này). Rõ ràng khái niệm này hoàn toàn giống với một giai thừa và không có sự phức tạp thêm vào ... ngoại trừ việc bạn có nhiều tham số. Và điều đó thường đủ để gây nhầm lẫn cho mọi người và ném chúng ngay từ đầu.

Hình thức đơn giản của nó:

Hình thức đơn giản của phép toán lũy thừa

có thể được thể hiện như thế này bằng cách tái phát:

Mối quan hệ lặp lại cho hoạt động lũy ​​thừa

Khó hơn

Khi các vấn đề đơn giản này đã được hiển thị VÀ được triển khai lại trong hướng dẫn, bạn có thể đưa ra các bài tập khó hơn (nhưng rất cổ điển):

  • Các số Fibonacci ,
  • Các ước chung lớn nhất ,
  • Các 8-Queens vấn đề,
  • Trò chơi The Towers of Hà Nội ,
  • Và nếu bạn có một môi trường đồ họa (hoặc có thể cung cấp cuống mã cho nó hoặc cho đầu ra thiết bị đầu cuối hoặc họ có thể quản lý điều đó rồi), những thứ như:
  • Và đối với các ví dụ thực tế, hãy xem xét viết:
    • một thuật toán truyền tải cây,
    • một trình phân tích cú pháp biểu thức toán học đơn giản,
    • một trò chơi quét mìn.

Lưu ý: Một lần nữa, một số trong số này thực sự không khó hơn ... Họ chỉ tiếp cận vấn đề từ cùng một góc độ, hoặc một góc hơi khác. Nhưng thực hành làm cho hoàn hảo.


Người giúp việc

Một tài liệu tham khảo

Một số đọc không bao giờ đau. Chà, lúc đầu nó sẽ như vậy, và họ sẽ còn cảm thấy lạc lõng hơn nữa. Đó là thứ phát triển trong bạn và nằm ở phía sau đầu bạn cho đến một ngày bạn nhận ra rằng cuối cùng bạn đã nhận được nó. Và sau đó bạn nghĩ lại những thứ bạn đọc. Việc đệ quy , đệ quy trong Khoa học máy tính và các trang quan hệ lặp lại trên Wikipedia sẽ làm ngay bây giờ.

Cấp độ / độ sâu

Giả sử sinh viên của bạn không có nhiều kinh nghiệm mã hóa, hãy cung cấp cuống mã. Sau những lần thử đầu tiên, hãy cung cấp cho họ chức năng in có thể hiển thị mức đệ quy. In giá trị số của cấp độ giúp.

Sơ đồ ngăn xếp

Việc thụt vào một kết quả được in (hoặc đầu ra của cấp độ) cũng giúp ích, vì nó cung cấp một biểu diễn trực quan khác về những gì chương trình của bạn đang làm, mở và đóng các bối cảnh ngăn xếp như ngăn kéo hoặc thư mục trong trình thám hiểm hệ thống tệp.

Từ viết tắt đệ quy

Nếu sinh viên của bạn đã thành thạo một chút về văn hóa máy tính, họ có thể đã sử dụng một số dự án / phần mềm có tên bằng các từ viết tắt đệ quy . Đó là một truyền thống xuất hiện trong một thời gian, đặc biệt là trong các dự án GNU. Một số ví dụ bao gồm:

Đệ quy:

  • GNU - "GNU không phải Unix"
  • Nagios - "Nagios Ain't Gonna khăng khăng về vị thánh"
  • PHP - "Bộ xử lý siêu văn bản PHP" (và nguồn gốc là "Trang chủ cá nhân")
  • Rượu vang - "Rượu không phải là trình giả lập"
  • Zile - "Zile là mất mát Emacs"

Đệ quy lẫn nhau:

  • HURD - "HIRD của Unix thay thế Daemon" (trong đó HIRD là "HURD của các giao diện đại diện cho độ sâu")

Có họ cố gắng để đưa ra với riêng của họ.

Tương tự, có nhiều sự xuất hiện của sự hài hước đệ quy, như sửa lỗi tìm kiếm đệ quy của Google . Để biết thêm thông tin về đệ quy, đọc câu trả lời này .


Cạm bẫy và học hỏi thêm

Một số vấn đề mà mọi người thường đấu tranh và bạn cần biết câu trả lời.

Tại sao, trời ơi tại sao ???

Tại sao bạn lại làm vậy? Một lý do tốt nhưng không rõ ràng là thường đơn giản hơn để diễn đạt một vấn đề theo cách đó. Một lý do không tốt nhưng rõ ràng là nó thường mất ít thao tác gõ hơn (đừng khiến họ cảm thấy loot l33t vì chỉ sử dụng đệ quy mặc dù ...).

Một số vấn đề chắc chắn dễ giải quyết hơn khi sử dụng phương pháp đệ quy. Thông thường, bất kỳ vấn đề nào bạn có thể giải quyết bằng mô hình Phân chia và Chinh phục sẽ phù hợp với thuật toán đệ quy đa nhánh.

Lại là gì nữa ??

Tại sao mỗi lần tôi nhoặc (bất cứ tên biến của bạn) khác nhau? Người mới bắt đầu thường có một vấn đề hiểu một biến và tham số là gì và làm thế nào để những thứ có tên ntrong chương trình của bạn có thể có các giá trị khác nhau. Vì vậy, bây giờ nếu giá trị này nằm trong vòng điều khiển hoặc đệ quy, điều đó thậm chí còn tồi tệ hơn! Hãy tử tế và không sử dụng cùng một tên biến ở mọi nơi và làm rõ rằng các tham số chỉ là biến .

Điều kiện kết thúc

Làm thế nào để tôi xác định tình trạng cuối của tôi? Điều đó thật dễ dàng, chỉ cần họ nói to các bước. Chẳng hạn, giai thừa bắt đầu từ 5, rồi 4, rồi ... cho đến 0.

Ma quỷ là trong các chi tiết

Đừng nói chuyện với những thứ sớm như tối ưu hóa cuộc gọi đuôi . Tôi biết, tôi biết, TCO rất hay, nhưng lúc đầu họ không quan tâm. Cung cấp cho họ một chút thời gian để quấn đầu xung quanh quá trình theo cách phù hợp với họ. Hãy thoải mái phá vỡ thế giới của họ một lần nữa sau đó, nhưng hãy cho họ nghỉ ngơi.

Tương tự, đừng nói thẳng từ bài giảng đầu tiên về ngăn xếp cuộc gọi và mức tiêu thụ bộ nhớ của nó và ... à ... tràn ngăn xếp . Tôi thường dạy kèm cho các sinh viên một cách riêng tư, những người chỉ cho tôi những bài giảng nơi họ có 50 slide về mọi thứ cần biết về đệ quy khi họ hầu như không thể viết một vòng lặp chính xác trong giai đoạn này. Đó là một ví dụ tốt về cách một tài liệu tham khảo sẽ giúp sau này nhưng ngay bây giờ chỉ khiến bạn bối rối .

Nhưng xin vui lòng, trong thời gian thích hợp, làm rõ rằng có những lý do để đi theo con đường lặp hoặc đệ quy .

Đệ quy lẫn nhau

Chúng ta đã thấy rằng các hàm có thể được đệ quy và thậm chí chúng có thể có nhiều điểm gọi (8-Queen, Hà Nội, Fibonacci hoặc thậm chí là một thuật toán thăm dò cho một người quét mìn). Nhưng những gì về các cuộc gọi đệ quy lẫn nhau ? Bắt đầu với toán học ở đây là tốt. f(x) = g(x) + h(x)nơi g(x) = f(x) + l(x)hlchỉ làm công cụ.

Bắt đầu chỉ với loạt toán học giúp viết và thực hiện dễ dàng hơn vì hợp đồng được xác định rõ ràng bằng các biểu thức. Chẳng hạn, các chuỗi Nam và Nữ của Hofstadter :

Trình tự nam và nữ của Hofstadter

Tuy nhiên, về mặt mã, cần lưu ý rằng việc triển khai giải pháp đệ quy lẫn nhau thường dẫn đến sao chép mã và nên được sắp xếp hợp lý thành một dạng đệ quy duy nhất (Xem Giải quyết mọi câu đố Sudoku của Peter Norvig .


5
Tôi đang đọc câu trả lời của bạn sau khi nhìn thấy nó sau gần 5 hoặc 6 lần. Tôi nghĩ nó thật tuyệt nhưng quá dài để thu hút những người dùng khác ở đây. Tôi đã học được nhiều điều về giảng dạy đệ quy ở đây. Là một giáo viên, bạn vui lòng đánh giá ý tưởng của tôi cho dạy recursion- programmers.stackexchange.com/questions/25052/...
Gulshan

9
@Gulshan, tôi nghĩ rằng câu trả lời này là toàn diện như bất kỳ điều gì sẽ xảy ra và dễ dàng bị 'đọc lướt' bởi một người đọc bình thường. Do đó, nó nhận được static unsigned int vote = 1;từ tôi. Tha thứ cho sự hài hước tĩnh, nếu bạn muốn :) Đây là câu trả lời tốt nhất cho đến nay.
Tim Post

1
@Gulsan: chỉ có người muốn học là sẵn sàng dành thời gian để làm là đúng :) Tôi không thực sự bận tâm. Đôi khi, một câu trả lời ngắn gọn là thanh lịch và truyền tải nhiều thông tin hữu ích và cần thiết để bắt đầu hoặc giải thích một khái niệm chung. Tôi chỉ muốn một câu trả lời dài hơn cho câu hỏi đó và xem xét OP đề cập đến một câu hỏi mà tôi đã được trao câu trả lời "đúng" và hỏi một câu tương tự, tôi nghĩ rằng nó phù hợp để đưa ra cùng một câu trả lời. Vui mừng bạn đã học được một cái gì đó.
haylem

@Gulshan: Bây giờ liên quan đến câu trả lời của bạn: điểm đầu tiên làm tôi bối rối rất nhiều, tôi phải nói. Tôi thích rằng bạn mô tả khái niệm về các hàm đệ quy như một cái gì đó thay đổi trạng thái tăng dần theo thời gian, nhưng tôi nghĩ rằng cách trình bày của bạn là một chút lạ. Sau khi đọc 3 điểm của bạn, tôi sẽ không mong đợi nhiều sinh viên đột nhiên có thể giải được Hà Nội. Nhưng nó có thể chỉ là một vấn đề từ ngữ.
haylem

Tôi đã bắt gặp một cách hay để chỉ ra rằng cùng một biến có thể có các giá trị khác nhau ở độ sâu đệ quy khác nhau: xem xét việc học sinh viết ra các bước họ đang thực hiện và các giá trị của biến, theo một số mã đệ quy. Khi họ đạt đến một đệ quy, yêu cầu họ bắt đầu lại trên một mảnh mới. Khi chúng đạt đến một điều kiện thoát, hãy để chúng di chuyển trở lại mảnh trước đó. Đây thực chất là mô phỏng một ngăn xếp cuộc gọi, nhưng đó là một cách tốt để thể hiện những khác biệt này.
Andy Hunt

58

Việc gọi một hàm từ bên trong cùng hàm đó.


2
Đây là lời giải thích tốt nhất để bắt đầu từ thực sự. Ngắn gọn và đúng trọng tâm; một khi bạn đã thiết lập bản tóm tắt này, sau đó đi vào tất cả các chi tiết chuyến đi.
jhocking

27

Đệ quy là một hàm gọi chính nó.

Làm thế nào để sử dụng nó, khi nào sử dụng nó và làm thế nào để tránh thiết kế xấu là điều quan trọng cần biết, điều này đòi hỏi bạn phải tự mình thử nó và hiểu những gì xảy ra.

Điều quan trọng nhất bạn cần biết là phải rất cẩn thận để không có một vòng lặp không bao giờ kết thúc. Câu trả lời từ pramodc84 cho câu hỏi của bạn có lỗi này: Nó không bao giờ kết thúc ...
Một hàm đệ quy phải luôn kiểm tra một điều kiện để xác định xem nó có nên tự gọi lại hay không.

Ví dụ kinh điển nhất để sử dụng đệ quy, là làm việc với một cây không có giới hạn tĩnh về chiều sâu. Đây là một nhiệm vụ mà bạn phải sử dụng đệ quy.


Bạn vẫn có thể thực hiện đoạn cuối của mình theo cách lặp, mặc dù đệ quy rõ ràng sẽ tốt hơn. "Làm thế nào để sử dụng nó, khi nào nên sử dụng nó và làm thế nào để tránh thiết kế xấu là điều quan trọng cần biết, điều này đòi hỏi bạn phải tự mình thử và hiểu điều gì xảy ra." Tôi nghĩ rằng vấn đề là phải giải thích cho Những thứ đó.
haylem

@haylem: Bạn đã đúng về câu trả lời đó cho câu "Cách sử dụng nó, khi nào sử dụng nó và làm thế nào để tránh thiết kế xấu .." sẽ phù hợp hơn với những gì OP yêu cầu (không chỉ là "hãy tự mình thử "Như tôi đã nói), nhưng điều đó sẽ đòi hỏi một bài giảng sâu rộng về loại hình giảng dạy nhiều hơn, thay vì trả lời nhanh cho một câu hỏi ở đây. Bạn đã làm một công việc rất tốt với câu trả lời của bạn mặc dù. +1 cho điều đó ... Những người thực sự cần hiểu rõ hơn về khái niệm này sẽ có lợi khi đọc câu trả lời của bạn.
kinh ngạc

những gì về một cặp chức năng gọi nhau. A gọi B mà gọi lại A cho đến khi đạt được một số điều kiện. điều này vẫn sẽ được coi là đệ quy?
santiagozky

Có, hàm avẫn tự gọi, chỉ gián tiếp (bằng cách gọi b).
kindall

1
@santiagozky: Như kindall đã nói, nó vẫn là đệ quy. Tuy nhiên, tôi khuyên bạn không nên làm điều đó. Khi sử dụng đệ quy, cần phải rất rõ ràng trong mã rằng đệ quy được đặt đúng chỗ. Nếu tự gọi nó một cách gián tiếp thông qua chức năng khác, điều đó khiến cho việc nhìn thấy những gì đang diễn ra khó khăn hơn nhiều. Nếu bạn không biết rằng một hàm gọi chính nó một cách hiệu quả, bạn có thể gặp phải trường hợp bạn (hoặc người khác không tạo ra hàm này) phá vỡ một số điều kiện cho đệ quy (trong khi thay đổi chức năng trong mã) và bạn kết thúc trong một khóa chết với một vòng lặp không bao giờ kết thúc.
kinh ngạc

21

Lập trình đệ quy là quá trình giảm dần một vấn đề để dễ dàng giải quyết các phiên bản của chính nó.

Mọi hàm đệ quy có xu hướng:

  1. lấy một danh sách để xử lý, hoặc một số cấu trúc khác, hoặc miền vấn đề
  2. đối phó với điểm / bước hiện tại
  3. gọi chính nó trên (các) phần còn lại / tên miền phụ
  4. kết hợp hoặc sử dụng kết quả của công việc tên miền phụ

Khi bước 2 ở trước 3 và khi bước 4 là tầm thường (nối, tổng hoặc không có gì), điều này cho phép đệ quy đuôi . Bước 2 thường phải đến sau bước 3, vì các kết quả từ (các) tên miền phụ của vấn đề có thể cần thiết để hoàn thành bước hiện tại.

Đi qua một cây nhị phân thẳng về phía trước. Việc truyền tải có thể được thực hiện theo thứ tự trước, theo thứ tự hoặc sau khi đặt hàng, tùy thuộc vào những gì được yêu cầu.

   B
A     C

Đặt hàng trước: BAC

traverse(tree):
    visit the node
    traverse(left)
    traverse(right)

Theo thứ tự: ABC

traverse(tree):
    traverse(left)
    visit the node
    traverse(right)

Đặt hàng sau: ACB

traverse(tree):
    traverse(left)
    traverse(right)
    visit the node

Rất nhiều vấn đề đệ quy là các trường hợp cụ thể của một thao tác bản đồ hoặc một nếp gấp - chỉ hiểu hai thao tác này có thể dẫn đến sự hiểu biết đáng kể về các trường hợp sử dụng tốt cho đệ quy.


Thành phần quan trọng để đệ quy thực tế là ý tưởng sử dụng giải pháp cho một vấn đề nhỏ hơn một chút để giải quyết vấn đề lớn hơn. Nếu không, bạn chỉ có đệ quy vô hạn.
Barry Brown

@Barry Brown: Hoàn toàn đúng. Do đó, tuyên bố của tôi "... giảm một vấn đề để dễ dàng giải quyết các phiên bản của chính nó"
Orble

Tôi không nhất thiết phải nói như vậy ... Nó thường là như vậy, đặc biệt là trong các vấn đề phân chia và chinh phục hoặc trong các tình huống mà bạn thực sự xác định một mối quan hệ tái phát xảy ra trong một trường hợp đơn giản. Nhưng tôi muốn nói thêm về việc chứng minh rằng với mỗi lần lặp N của vấn đề của bạn, có một trường hợp tính toán N + 1.
haylem

1
@Sean McMillan: Recursion là một công cụ mạnh mẽ khi được sử dụng trong các miền phù hợp với nó. Tôi thường thấy nó được sử dụng như một cách thông minh để xử lý một số vấn đề tương đối tầm thường, làm ồ ạt che giấu bản chất thực sự của nhiệm vụ trong tay.
Orble

20

OP nói rằng đệ quy không tồn tại trong thế giới thực, nhưng tôi xin khác.

Chúng ta hãy thực hiện "hoạt động" trong thế giới thực của việc cắt một chiếc bánh pizza. Bạn đã lấy pizza ra khỏi lò và để phục vụ nó, bạn phải cắt nó làm đôi, sau đó cắt một nửa, sau đó cắt đôi một nửa.

Hoạt động cắt bánh pizza bạn thực hiện lặp đi lặp lại cho đến khi bạn có kết quả bạn muốn (số lát). Và để tranh luận, hãy nói rằng một chiếc bánh pizza không cắt là một lát.

Đây là một ví dụ trong Ruby:

def cut_pizza (current_slices, wish_slices)
  if current_slices! = wish_slices
    # chúng tôi chưa có đủ lát để nuôi tất cả mọi người, vì vậy
    # chúng tôi đang cắt lát pizza, do đó nhân đôi số lượng của họ
    new_slices = current_slices * 2 
    # và đây là cuộc gọi đệ quy
    cut_pizza (new_slices, wish_slices)
  khác
    # chúng tôi có số lát mong muốn, vì vậy chúng tôi quay lại
    # ở đây thay vì tiếp tục tái diễn
    trả về current_slices
  kết thúc
kết thúc

pizza = 1 # toàn bộ pizza, 'một lát'
cut_pizza (pizza, 8) # => chúng tôi sẽ nhận được 8

Vì vậy, hoạt động trong thế giới thực đang cắt một chiếc bánh pizza, và đệ quy đang làm điều tương tự lặp đi lặp lại cho đến khi bạn có những gì bạn muốn.

Các hoạt động bạn sẽ thấy rằng cắt xén mà bạn có thể thực hiện với các hàm đệ quy là:

  • Tính lãi kép trong một số tháng.
  • Tìm kiếm một tệp trên một hệ thống tệp (vì hệ thống tệp là cây vì các thư mục).
  • Bất cứ điều gì liên quan đến làm việc với cây nói chung, tôi đoán.

Tôi khuyên bạn nên viết chương trình để tìm tệp dựa trên tên tệp của nó và cố gắng viết một hàm tự gọi cho đến khi tìm thấy, chữ ký sẽ trông như thế này:

find_file_by_name(file_name_we_are_looking_for, path_to_look_in)

Vì vậy, bạn có thể gọi nó như thế này:

find_file_by_name('httpd.conf', '/etc') # damn it i can never find apache's conf

Theo tôi, đó chỉ đơn giản là cơ chế lập trình, một cách khéo léo loại bỏ sự trùng lặp. Bạn có thể viết lại điều này bằng cách sử dụng các biến, nhưng đây là một giải pháp 'đẹp hơn'. Không có gì bí ẩn hoặc khó khăn về nó. Bạn sẽ viết một vài chức năng đệ quy, nó sẽ bấm và huzzah khác lừa cơ khí trong hộp công cụ lập trình của bạn.

Điểm thêm Các cut_pizzaví dụ trên sẽ cho bạn một mức đống lỗi quá sâu nếu bạn hỏi nó cho một số lát đó không phải là một sức mạnh của 2 (tức là 2 hoặc 4 hoặc 8 hoặc 16). Bạn có thể sửa đổi nó để nếu ai đó yêu cầu 10 lát thì nó sẽ không chạy được bao giờ không?


16

Được rồi tôi sẽ cố gắng để giữ cho đơn giản và ngắn gọn này.

Hàm đệ quy là các hàm tự gọi. Hàm đệ quy bao gồm ba điều:

  1. Logic VCL
  2. Một cuộc gọi đến chính nó
  3. Khi nào chấm dứt.

Cách tốt nhất để viết các phương thức đệ quy, là nghĩ về phương thức mà bạn đang cố gắng viết như một ví dụ đơn giản chỉ xử lý một vòng lặp của quy trình bạn muốn lặp lại, sau đó thêm lệnh gọi vào chính phương thức đó và thêm khi bạn muốn chấm dứt. Cách tốt nhất để học là thực hành như tất cả mọi thứ.

Vì đây là trang web lập trình viên nên tôi sẽ không viết mã nhưng đây là một liên kết tốt

nếu bạn có trò đùa đó, bạn hiểu ý nghĩa của đệ quy.



4
Nghiêm túc, đệ quy không cần một điều kiện chấm dứt; đảm bảo rằng đệ quy chấm dứt hạn chế các loại vấn đề mà hàm đệ quy có thể giải quyết, và có một số loại trình bày ngữ nghĩa hoàn toàn không yêu cầu chấm dứt.
Donal Fellows

6

Đệ quy là một công cụ mà lập trình viên có thể sử dụng để gọi một hàm gọi chính nó. Chuỗi Fibonacci là ví dụ trong sách giáo khoa về cách sử dụng đệ quy.

Hầu hết các mã đệ quy nếu không phải tất cả đều có thể được biểu diễn dưới dạng hàm lặp, nhưng nó thường lộn xộn. Các ví dụ điển hình của các chương trình đệ quy khác là Cấu trúc dữ liệu như cây, cây tìm kiếm nhị phân và thậm chí quicksort.

Đệ quy được sử dụng để làm cho mã ít cẩu thả hơn, hãy nhớ rằng nó thường chậm hơn và đòi hỏi nhiều bộ nhớ hơn.


Cho dù nó chậm hơn hay đòi hỏi nhiều bộ nhớ hơn rất nhiều phụ thuộc vào việc sử dụng.
Orble

5
Tính toán chuỗi Fibonacci là một điều khủng khiếp để làm đệ quy. Cây ngang là cách sử dụng đệ quy tự nhiên hơn nhiều. Thông thường, khi đệ quy được sử dụng tốt, nó không chậm hơn và không cần nhiều bộ nhớ hơn, vì bạn phải duy trì một ngăn xếp của riêng mình thay vì ngăn xếp cuộc gọi.
David Thornley

1
@Dave: Tôi sẽ không tranh cãi về điều đó, nhưng tôi nghĩ rằng Fibonacci là một ví dụ tốt để bắt đầu.
Bryan Harrington

5

Tôi thích sử dụng cái này:

Làm thế nào để bạn đi bộ đến cửa hàng?

Nếu bạn đang ở lối vào cửa hàng, chỉ cần đi qua nó. Nếu không, hãy thực hiện một bước, sau đó đi bộ phần còn lại của cửa hàng đến cửa hàng.

Điều quan trọng là bao gồm ba khía cạnh:

  • Một trường hợp cơ sở tầm thường
  • Giải quyết một phần nhỏ của vấn đề
  • Giải quyết phần còn lại của bài toán một cách đệ quy

Chúng tôi thực sự sử dụng đệ quy rất nhiều trong cuộc sống hàng ngày; chúng ta không nghĩ về nó theo cách đó


Đó không phải là đệ quy. Sẽ là nếu bạn chia nó làm hai: Đi bộ một nửa đến cửa hàng, đi bộ nửa còn lại. Tái diễn.

2
Đây là đệ quy. Đây không phải là phân chia và chinh phục, nhưng nó chỉ là một loại đệ quy. Các thuật toán đồ thị (như tìm đường dẫn) có đầy đủ các khái niệm đệ quy.
deadalnix

2
Đây là đệ quy, nhưng tôi coi đó là một ví dụ kém, vì quá dễ để dịch "một bước, sau đó đi phần còn lại của cửa hàng" thành một thuật toán lặp. Tôi cảm thấy như thế này tương đương với việc chuyển đổi một forvòng lặp được viết độc đáo thành một hàm đệ quy vô nghĩa.
Brian

3

Ví dụ tốt nhất mà tôi muốn chỉ cho bạn là Ngôn ngữ lập trình C của K & R. Trong cuốn sách đó (và tôi đang trích dẫn từ bộ nhớ), mục trong trang chỉ mục để đệ quy (một mình) liệt kê trang thực tế nơi họ nói về đệ quy và trang chỉ mục là tốt.


2

Josh K đã đề cập đến búp bê Matroshka . Giả sử rằng bạn muốn học một cái gì đó mà chỉ con búp bê ngắn nhất mới biết. Vấn đề là bạn không thể thực sự nói chuyện trực tiếp với cô ấy, vì ban đầu cô ấy sống bên trong con búp bê cao hơn mà trên bức ảnh đầu tiên được đặt bên trái cô ấy. Cấu trúc này diễn ra như vậy (một con búp bê sống bên trong con búp bê cao hơn) cho đến khi chỉ kết thúc với con cao nhất.

Vì vậy, điều duy nhất bạn có thể làm là đặt câu hỏi của bạn cho con búp bê cao nhất. Con búp bê cao nhất (người không biết câu trả lời) sẽ cần chuyển câu hỏi của bạn cho con búp bê ngắn hơn (mà trên bức ảnh đầu tiên nằm bên phải cô ấy). Vì cô ấy cũng không có câu trả lời, cô ấy cần hỏi con búp bê ngắn hơn tiếp theo. Điều này sẽ diễn ra như vậy cho đến khi tin nhắn đến được con búp bê ngắn nhất. Con búp bê ngắn nhất (là người duy nhất biết câu trả lời bí mật) sẽ chuyển câu trả lời cho con búp bê cao hơn tiếp theo (được tìm thấy ở bên trái của cô), nó sẽ chuyển nó cho con búp bê cao hơn tiếp theo ... và điều này sẽ tiếp tục cho đến khi câu trả lời đạt đến đích cuối cùng, đó là con búp bê cao nhất và cuối cùng là ... bạn :)

Đây là những gì đệ quy thực sự làm. Một hàm / phương thức gọi chính nó cho đến khi nhận được câu trả lời mong đợi. Đó là lý do tại sao khi bạn viết mã đệ quy, điều rất quan trọng là quyết định khi nào đệ quy nên chấm dứt.

Không phải là lời giải thích tốt nhất nhưng nó hy vọng sẽ giúp.


2

Đệ quy n. - Một mẫu thiết kế thuật toán trong đó một hoạt động được xác định theo nghĩa của chính nó.

Ví dụ kinh điển là tìm giai thừa của một số, n!. 0! = 1 và với bất kỳ số tự nhiên N nào khác, giai thừa của N là tích của tất cả các số tự nhiên nhỏ hơn hoặc bằng N. Vì vậy, 6! = 6 * 5 * 4 * 3 * 2 * 1 = 720. Định nghĩa cơ bản này sẽ cho phép bạn tạo một giải pháp lặp đơn giản:

int Fact(int degree)
{
    int result = 1;
    for(int i=degree; i>1; i--)
       result *= i;

    return result;
}

Tuy nhiên, kiểm tra hoạt động một lần nữa. 6! = 6 * 5 * 4 * 3 * 2 * 1. Theo định nghĩa tương tự, 5! = 5 * 4 * 3 * 2 * 1, nghĩa là chúng ta có thể nói 6! = 6 * (5!). Lần lượt, 5! = 5 * (4!) Và cứ thế. Bằng cách này, chúng tôi giảm vấn đề thành một hoạt động được thực hiện trên kết quả của tất cả các hoạt động trước đó. Điều này cuối cùng giảm đến một điểm, được gọi là trường hợp cơ sở, trong đó kết quả được biết theo định nghĩa. Trong trường hợp của chúng tôi, 0! = 1 (trong hầu hết các trường hợp, chúng tôi cũng có thể nói rằng 1! = 1). Trong điện toán, chúng ta thường được phép định nghĩa các thuật toán theo cách rất giống nhau, bằng cách tự gọi phương thức và chuyển một đầu vào nhỏ hơn, do đó giảm vấn đề thông qua nhiều lần truy cập vào trường hợp cơ sở:

int Fact(int degree)
{
    if(degree==0) return 1; //the base case; 0! = 1 by definition
    else return degree * Fact(degree -1); //the recursive case; N! = N*(N-1)!
}

Điều này có thể, trong nhiều ngôn ngữ, có thể được đơn giản hóa hơn nữa bằng cách sử dụng toán tử ternary (đôi khi được xem là hàm Iif trong các ngôn ngữ không cung cấp toán tử như vậy):

int Fact(int degree)
{
    //reads equivalently to the above, but is concise and often optimizable
    return degree==0 ? 1: degree * Fact(degree -1);
}

Ưu điểm:

  • Biểu thức tự nhiên - đối với nhiều loại thuật toán, đây là một cách rất tự nhiên để diễn đạt hàm.
  • Giảm LỘC - Nó thường ngắn gọn hơn nhiều để xác định một hàm đệ quy.
  • Tốc độ - Trong một số trường hợp nhất định, tùy thuộc vào ngôn ngữ và kiến ​​trúc máy tính, đệ quy thuật toán nhanh hơn giải pháp lặp tương đương, thường là do thực hiện cuộc gọi hàm là thao tác nhanh hơn ở cấp phần cứng so với các thao tác và truy cập bộ nhớ cần thiết để lặp lại.
  • Tính phân chia - Nhiều thuật toán đệ quy có tâm lý "phân chia và chinh phục"; kết quả của hoạt động là một chức năng của kết quả của cùng một hoạt động được thực hiện trên mỗi hai nửa của đầu vào. Điều này cho phép bạn chia công việc thành hai ở mỗi cấp độ và nếu có sẵn, bạn có thể giao cho nửa kia cho một "đơn vị thực hiện" khác để xử lý. Điều này thường khó hơn hoặc không thể với thuật toán lặp.

Nhược điểm:

  • Yêu cầu hiểu - Đơn giản là bạn phải "nắm bắt" khái niệm đệ quy để hiểu những gì đang diễn ra, và do đó viết và duy trì các thuật toán đệ quy hiệu quả. Nếu không, nó trông giống như ma thuật đen.
  • Phụ thuộc vào bối cảnh - Việc đệ quy có phải là một ý tưởng tốt hay không phụ thuộc vào mức độ trang nhã của thuật toán có thể được định nghĩa theo chính nó. Trong khi có thể xây dựng, ví dụ, một SelectionSort đệ quy, thuật toán lặp thường là dễ hiểu hơn.
  • Giao dịch truy cập RAM cho ngăn xếp cuộc gọi - Thông thường, các cuộc gọi chức năng rẻ hơn truy cập bộ đệm, có thể thực hiện đệ quy nhanh hơn lặp lại. Nhưng, thường có giới hạn về độ sâu của ngăn xếp cuộc gọi có thể gây ra đệ quy bị lỗi trong đó thuật toán lặp sẽ hoạt động.
  • Đệ quy vô hạn - Bạn phải biết khi nào nên dừng lại. Lặp lại vô hạn cũng có thể nhưng các cấu trúc lặp có liên quan thường dễ hiểu hơn và do đó để gỡ lỗi.

1

Ví dụ tôi sử dụng là một vấn đề tôi gặp phải trong cuộc sống thực. Bạn có một hộp đựng (chẳng hạn như một chiếc ba lô lớn mà bạn dự định thực hiện trong một chuyến đi) và bạn muốn biết tổng trọng lượng. Bạn có trong hai hoặc ba vật chứa lỏng lẻo và một số vật chứa khác (ví dụ, bao tải.) Trọng lượng của tổng container rõ ràng là trọng lượng của container rỗng cộng với trọng lượng của mọi thứ trong đó. Đối với các mặt hàng lỏng lẻo, bạn chỉ có thể cân chúng, và đối với các bao tải bạn chỉ có thể cân chúng hoặc bạn có thể nói "trọng lượng của mỗi bao là trọng lượng của thùng rỗng cộng với trọng lượng của mọi thứ trong đó". Và sau đó bạn tiếp tục đi vào các container vào các thùng chứa và cứ thế cho đến khi bạn đến một điểm mà chỉ có các vật phẩm lỏng lẻo trong một container. Đó là đệ quy.

Bạn có thể nghĩ rằng điều đó không bao giờ xảy ra trong cuộc sống thực, nhưng hãy tưởng tượng bạn đang cố gắng đếm, hoặc tăng lương cho những người trong một công ty hoặc bộ phận cụ thể, có sự pha trộn của những người chỉ làm việc cho công ty, những người trong bộ phận, sau đó các bộ phận có các phòng ban và như vậy. Hoặc bán hàng ở một quốc gia có các khu vực, một số trong đó có các tiểu vùng, v.v ... Những loại vấn đề này xảy ra mọi lúc trong kinh doanh.


0

Đệ quy có thể được sử dụng để giải quyết rất nhiều vấn đề đếm. Ví dụ: giả sử bạn có một nhóm n người trong một bữa tiệc (n> 1) và mọi người đều bắt tay người khác chính xác một lần. Có bao nhiêu cái bắt tay diễn ra? Bạn có thể biết rằng giải pháp là C (n, 2) = n (n-1) / 2, nhưng bạn có thể giải đệ quy như sau:

Giả sử chỉ có hai người. Sau đó (bằng cách kiểm tra) câu trả lời rõ ràng là 1.

Giả sử bạn có ba người. Độc thân một người, và lưu ý rằng anh ấy / cô ấy bắt tay với hai người khác. Sau đó, bạn phải đếm chỉ những cái bắt tay giữa hai người kia. Chúng tôi đã làm điều đó ngay bây giờ, và nó là 1. Vì vậy, câu trả lời là 2 + 1 = 3.

Giả sử bạn có n người. Theo logic tương tự như trước, đó là (n-1) + (số lần bắt tay giữa n-1 người). Mở rộng, chúng tôi nhận được (n-1) + (n-2) + ... + 1.

Được biểu thị như một hàm đệ quy,

f (2) = 1
f (n) = n-1 + f (n-1), n> 2


0

Trong cuộc sống (trái ngược với trong một chương trình máy tính), sự đệ quy hiếm khi xảy ra dưới sự kiểm soát trực tiếp của chúng tôi, bởi vì nó có thể gây nhầm lẫn khi thực hiện. Ngoài ra, nhận thức có xu hướng về các tác dụng phụ, thay vì thuần túy về mặt chức năng, vì vậy nếu sự đệ quy đang xảy ra, bạn có thể không nhận thấy điều đó.

Đệ quy không xảy ra ở đây trên thế giới. Rất nhiều.

Một ví dụ điển hình là (một phiên bản đơn giản hóa) của chu trình nước:

  • Mặt trời làm nóng hồ
  • Nước lên trời và tạo thành mây
  • Những đám mây trôi qua một ngọn núi
  • Ở trên núi không khí trở nên quá lạnh khiến độ ẩm của chúng bị giữ lại
  • Mưa rơi
  • Một dòng sông hình thành
  • Nước trong sông chảy vào hồ

Đây là một chu kỳ khiến cho bản thân của nó xảy ra một lần nữa. Đó là đệ quy.

Một nơi khác bạn có thể nhận được đệ quy là bằng tiếng Anh (và ngôn ngữ của con người nói chung). Ban đầu bạn có thể không nhận ra nó, nhưng cách chúng ta có thể tạo một câu là đệ quy, bởi vì các quy tắc cho phép chúng ta nhúng một thể hiện của một biểu tượng vào một thể hiện khác của cùng một biểu tượng.

Từ Bản năng ngôn ngữ của Steven Pinker's:

Nếu cô gái ăn kem hoặc cô gái ăn kẹo thì chàng trai ăn xúc xích

Đó là toàn bộ câu có chứa toàn bộ câu khác:

cô gái ăn kem

cô gái ăn kẹo

cậu bé ăn xúc xích

Hành động hiểu toàn bộ câu liên quan đến việc hiểu các câu nhỏ hơn, sử dụng cùng một bộ mánh khóe tinh thần để được hiểu là toàn bộ câu.

Để hiểu đệ quy từ góc độ lập trình, dễ nhất là xem xét một vấn đề có thể giải quyết bằng đệ quy và hiểu tại sao nó nên và điều đó có nghĩa là bạn cần phải làm gì.

Ví dụ, tôi sẽ sử dụng hàm ước số chung lớn nhất hoặc viết tắt là gcd.

Bạn có hai số của bạn ab. Để tìm gcd của chúng (giả sử không phải là 0), bạn cần kiểm tra xem acó chia hết cho không b. Nếu đó blà gcd, nếu không, bạn cần kiểm tra gcd của bvà phần còn lại của a/b.

Bạn đã có thể thấy rằng đây là một hàm đệ quy, vì bạn có hàm gcd gọi hàm gcd. Chỉ cần đưa nó về nhà, đây là c # (một lần nữa, giả sử 0 không bao giờ được truyền vào dưới dạng tham số):

int gcd(int a, int b)
{   
    if (a % b == 0) //this is a stopping condition
    {
        return b;
    }

    return (gcd(b, a % b)); //the call to gcd here makes this function recursive
}

Trong một chương trình, điều quan trọng là phải có một điều kiện dừng, nếu không chức năng của bạn sẽ tái diễn mãi mãi, điều này cuối cùng sẽ gây ra tràn ngăn xếp!

Lý do để sử dụng đệ quy ở đây, thay vì vòng lặp while hoặc một số cấu trúc lặp khác, là khi bạn đọc mã, nó sẽ cho bạn biết nó đang làm gì và điều gì sẽ xảy ra tiếp theo, vì vậy sẽ dễ dàng hơn nếu nó hoạt động chính xác .


1
Tôi tìm thấy ví dụ chu trình nước lặp đi lặp lại. Ví dụ ngôn ngữ thứ hai có vẻ phân chia và chinh phục hơn là đệ quy.
Gul Sơn

@Gulshan: Tôi muốn nói rằng chu trình nước là đệ quy vì nó khiến bản thân nó lặp lại, giống như một hàm đệ quy. Nó không giống như vẽ một căn phòng, nơi bạn thực hiện cùng một bộ các bước trên một số đối tượng (tường, trần, v.v.), giống như trong một vòng lặp for. Ví dụ ngôn ngữ không sử dụng phép chia và chinh phục, nhưng cũng là "hàm" được gọi là tự gọi nó để làm việc trên các câu lồng nhau, do đó, được đệ quy theo cách đó.
Matt Ellen

Trong chu trình nước, chu trình được bắt đầu bởi mặt trời và không có yếu tố nào khác của chu trình làm cho mặt trời bắt đầu lại. Vậy, cuộc gọi đệ quy ở đâu?
Gul Sơn

Không có cuộc gọi đệ quy! Không phải là một chức năng. : D Nó là đệ quy vì nó làm cho bản thân nó tái diễn. Nước từ hồ quay trở lại hồ, và chu kỳ lại bắt đầu. Nếu một số hệ thống khác đưa nước vào hồ, thì nó sẽ lặp đi lặp lại.
Matt Ellen

1
Chu trình nước là một vòng lặp while. Chắc chắn, một vòng lặp while có thể được thể hiện bằng cách sử dụng đệ quy, nhưng làm như vậy sẽ thổi bay ngăn xếp. Xin đừng.
Brian

0

Đây là một ví dụ thực tế cho đệ quy.

Hãy để họ tưởng tượng rằng họ có một bộ sưu tập truyện tranh và bạn sẽ trộn tất cả lại thành một đống lớn. Cẩn thận - nếu họ thực sự có một bộ sưu tập, họ có thể giết bạn ngay lập tức khi bạn chỉ đề cập đến ý tưởng để làm như vậy.

Bây giờ hãy để họ sắp xếp đống truyện tranh lớn chưa được sắp xếp này với sự giúp đỡ của hướng dẫn này:

Manual: How to sort a pile of comics

Check the pile if it is already sorted. If it is, then done.

As long as there are comics in the pile, put each one on another pile, 
ordered from left to right in ascending order:

    If your current pile contains different comics, pile them by comic.
    If not and your current pile contains different years, pile them by year.
    If not and your current pile contains different tenth digits, pile them 
    by this digit: Issue 1 to 9, 10 to 19, and so on.
    If not then "pile" them by issue number.

Refer to the "Manual: How to sort a pile of comics" to separately sort each
of the new piles.

Collect the piles back to a big pile from left to right.

Done.

Điều tốt đẹp ở đây là: Khi họ gặp vấn đề đơn lẻ, họ có đầy đủ "khung ngăn xếp" với các cọc cục bộ có thể nhìn thấy trước khi họ ở trên mặt đất. Cung cấp cho họ nhiều bản in của sổ tay và đặt một cấp cho mỗi cấp độ cọc với một dấu mà bạn hiện đang ở cấp độ này (nghĩa là trạng thái của các biến cục bộ), để bạn có thể tiếp tục ở đó trên mỗi Xong.

Đó là những gì đệ quy về cơ bản là: Thực hiện cùng một quy trình, chỉ ở mức độ chi tiết tốt hơn, bạn càng đi sâu vào nó.


-1
  • Chấm dứt nếu đạt được điều kiện thoát
  • làm gì đó để thay đổi trạng thái của sự vật
  • làm công việc tất cả bắt đầu với tình trạng hiện tại của sự vật

Đệ quy là một cách rất súc tích để diễn đạt một cái gì đó phải được lặp lại cho đến khi đạt được một cái gì đó.



-2

Một sự khám phá tốt đẹp về đệ quy theo nghĩa đen là "một hành động tái hiện từ bên trong chính nó".

Hãy xem xét một họa sĩ vẽ một bức tường, nó được đệ quy bởi vì hành động là "vẽ một dải từ trần nhà xuống sàn nhà hơn một chút về phía bên phải và (vẽ một dải từ trần nhà xuống sàn nhà hơn là lướt qua một chút bên phải và (vẽ một dải từ trần đến sàn hơn so với trượt qua một chút bên phải và (vv))) ".

Hàm paint () của anh ta gọi đi gọi lại nhiều lần để tạo nên hàm paint_wall () lớn hơn của anh ta.

Hy vọng họa sĩ nghèo này có một số loại điều kiện dừng lại :)


6
Đối với tôi, ví dụ có vẻ giống như một quy trình lặp, không phải đệ quy.
Gul Sơn

@Gulshan: Đệ quy và lặp lại làm những việc tương tự, và thường mọi thứ sẽ hoạt động tốt với một trong hai. Các ngôn ngữ hàm thường sử dụng đệ quy thay vì lặp. Có những ví dụ tốt hơn về đệ quy trong đó sẽ rất khó xử khi viết cùng một điều lặp đi lặp lại.
David Thornley
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.