Đệ quy không có giai thừa, số Fibonacci, v.v.


48

Hầu như mọi bài viết tôi có thể tìm thấy về đệ quy bao gồm các ví dụ về số nhân tử hoặc số Fibonacci, đó là:

  1. môn Toán
  2. Vô dụng trong cuộc sống thực

Có một số ví dụ phi toán học thú vị để dạy đệ quy?

Tôi đang nghĩ các thuật toán phân chia và chinh phục nhưng chúng thường liên quan đến các cấu trúc dữ liệu phức tạp.


26
Mặc dù câu hỏi của bạn là hoàn toàn hợp lệ, tôi ngần ngại gọi các số Fibonacci vô dụng trong cuộc sống thực . Cùng đi cho giai thừa .
Zach L

2
The Little Schemer là toàn bộ cuốn sách về đệ quy không bao giờ sử dụng Fact hoặc Fib. junix-linux-config.googlecode.com/files/ Kẻ
Eric Wilson

5
@Zach: Mặc dù vậy, đệ quy là một cách khủng khiếp để thực hiện các số Fibonacci, vì thời gian chạy theo cấp số nhân.
dan04

2
@ dan04: Đệ quy là một cách khủng khiếp để thực hiện hầu hết mọi thứ do khả năng tràn ngăn xếp trong hầu hết các làn đường.
R ..

5
@ dan04 trừ khi ngôn ngữ của bạn đủ thông minh để thực hiện tối ưu hóa cuộc gọi như hầu hết các ngôn ngữ chức năng trong trường hợp nó hoạt động tốt
Zachary K

Câu trả lời:


107

Các cấu trúc thư mục / tệp là ví dụ tốt nhất về việc sử dụng đệ quy, bởi vì mọi người đều hiểu chúng trước khi bạn bắt đầu, nhưng bất cứ điều gì liên quan đến các cấu trúc giống như cây sẽ làm.

void GetAllFilePaths(Directory dir, List<string> paths)
{
    foreach(File file in dir.Files)
    {
        paths.Add(file.Path);
    }

    foreach(Directory subdir in dir.Directories)
    {
        GetAllFilePaths(subdir, paths)
    }
}

List<string> GetAllFilePaths(Directory dir)
{
    List<string> paths = new List<string>();
    GetAllFilePaths(dir, paths);
    return paths;
}

1
Cảm ơn, tôi nghĩ rằng tôi sẽ đi với hệ thống tập tin. Đó là một cái gì đó cụ thể và có thể được sử dụng cho nhiều ví dụ trong thế giới thực.
khớp thần kinh

9
Lưu ý: lệnh unix thường loại trừ tùy chọn -r (cp hoặc rm cho ví dụ). -r đứng cho đệ quy.
deadalnix

7
bạn phải cẩn thận một chút ở đây vì trong các hệ thống tệp trong thế giới thực thực sự là một biểu đồ có hướng không nhất thiết là một cây, nếu được hỗ trợ bởi hệ thống tệp, các liên kết cứng, v.v. có thể tạo các phép nối và chu trình
jk.

1
@jk: Các thư mục không thể liên kết cứng, do đó, modulo một số lá có thể xuất hiện ở nhiều hơn một vị trí và giả sử bạn loại trừ các liên kết tượng trưng, ​​hệ thống tệp trong thế giới thực là cây.
R ..

1
có một số đặc thù khác trong một số hệ thống tệp cho các thư mục, ví dụ như các điểm lặp lại NTFS. quan điểm của tôi là mã không nhận thức được đặc biệt về điều này có thể có kết quả bất ngờ trên các hệ thống tệp trong thế giới thực
jk.

51

Tìm kiếm những thứ liên quan đến cấu trúc cây. Một cây tương đối dễ nắm bắt và vẻ đẹp của một giải pháp đệ quy trở nên rõ ràng sớm hơn nhiều so với các cấu trúc dữ liệu tuyến tính như danh sách.

Những điều cần suy nghĩ:

  • hệ thống tập tin - về cơ bản là cây; một nhiệm vụ lập trình tốt sẽ là tìm nạp tất cả các hình ảnh .jpg trong một thư mục nhất định và tất cả các thư mục con của nó
  • tổ tiên - được cho một cây gia đình, tìm số thế hệ bạn phải đi lên để tìm một tổ tiên chung; hoặc kiểm tra xem hai người trong cây có thuộc cùng một thế hệ hay không; hoặc kiểm tra xem hai người trên cây có thể kết hôn hợp pháp hay không (tùy thuộc vào quyền tài phán :)
  • Các tài liệu giống như HTML - chuyển đổi giữa biểu diễn nối tiếp (văn bản) của tài liệu và cây DOM; thực hiện các hoạt động trên các tập hợp con của DOM (thậm chí có thể triển khai một tập hợp con của xpath?); ...

Tất cả đều liên quan đến các kịch bản trong thế giới thực và tất cả chúng có thể được sử dụng trong các ứng dụng có ý nghĩa trong thế giới thực.


Tất nhiên cần lưu ý rằng bất cứ khi nào bạn có quyền tự do thiết kế cấu trúc cây của riêng mình, hầu như luôn luôn tốt hơn để giữ con trỏ / tham chiếu đến cha mẹ / anh chị em tiếp theo / v.v. trong các nút để bạn có thể lặp qua cây mà không cần đệ quy.
R ..

1
Con trỏ phải làm gì với nó? Ngay cả khi bạn có con trỏ đầu tiên, anh chị em và cha mẹ tiếp theo, bạn vẫn phải đi bộ qua cây của bạn bằng cách nào đó, và trong một số trường hợp, đệ quy là cách khả thi nhất.
tdammers

41

https://stackoverflow.com/questions/105838/real-world-examples-of-recursion

  • mô hình nhiễm trùng truyền nhiễm
  • tạo hình học
  • quản lý thư mục
  • phân loại

https://stackoverflow.com/questions/2085834/how-did-you-practally-use-recursion

  • có hi vọng
  • cờ vua
  • phân tích mã nguồn (ngữ pháp ngôn ngữ)

https://stackoverflow.com/questions/4945128/what-is-a-good-example-of-recursion-other-than-generating-a-fiborie- resultence

  • Tìm kiếm BST
  • Tháp Hà Nội
  • tìm kiếm palindrom

https://stackoverflow.com/questions/126756/examples-of-recursive-fifts

  • Cung cấp một câu chuyện bằng tiếng Anh hay, minh họa sự đệ quy bằng một câu chuyện trước khi đi ngủ.

10
Trong khi về mặt lý thuyết có thể trả lời câu hỏi, tốt hơn là nên bao gồm các phần thiết yếu của những câu hỏi và câu trả lời ở đây, và cung cấp các liên kết để tham khảo. Nếu các câu hỏi đã bị xóa khỏi SO, câu trả lời của bạn sẽ hoàn toàn vô dụng.
Adam Lear

@Anna Chà, người dùng không thể xóa câu hỏi của họ để có khả năng điều đó xảy ra?
vemv

4
@vemv Xóa phiếu bầu, người kiểm duyệt, quy tắc về những gì về chủ đề thay đổi ... nó có thể xảy ra. Dù bằng cách nào, có một câu trả lời đầy đủ hơn ở đây sẽ tốt hơn là gửi một khách truy cập đến bốn trang khác nhau ngay lập tức.
Adam Lear

@ Anna trùng lặp câu hỏi về lập trình viên?
SF.

1
@SF Nếu chúng tôi có thể đóng nó dưới dạng trùng lặp, chúng tôi sẽ, nhưng các bản sao chéo trang web không được hỗ trợ. Các lập trình viên là một trang web riêng biệt, vì vậy các câu trả lời lý tưởng ở đây sẽ sử dụng SO như bất kỳ tài liệu tham khảo nào khác, chứ không phải ủy thác hoàn toàn cho nó. Không khác gì chỉ nói "câu trả lời của bạn nằm trong cuốn sách này" - về mặt kỹ thuật, nhưng không thể được sử dụng ngay mà không tham khảo tài liệu tham khảo.
Adam Lear

23

Dưới đây là một số vấn đề thực tế hơn xuất hiện trong tâm trí của tôi:

  • Hợp nhất sắp xếp
  • Tìm kiếm nhị phân
  • Truyền tải, chèn và xóa trên cây (phần lớn được sử dụng trên các ứng dụng cơ sở dữ liệu)
  • Máy phát điện
  • Người giải Sudoku (có quay lại)
  • Kiểm tra chính tả (một lần nữa với quay lại)
  • Phân tích cú pháp (.eg, một chương trình chuyển đổi tiền tố thành ký hiệu hậu tố)

11

QuickSort sẽ là người đầu tiên nhảy vào tâm trí. Tìm kiếm nhị phân cũng là một vấn đề đệ quy. Ngoài ra, có toàn bộ các loại vấn đề mà các giải pháp rơi ra gần như miễn phí khi bạn bắt đầu làm việc với đệ quy.


3
Tìm kiếm nhị phân thường được coi là một vấn đề đệ quy nhưng nó không quan trọng (và thường được ưa thích hơn) để thực hiện theo cách bắt buộc.
lông mịn

Tùy thuộc vào ngôn ngữ bạn đang sử dụng mã có thể hoặc không thể đệ quy rõ ràng hoặc bắt buộc hoặc đệ quy. Nhưng nó vẫn là một thuật toán đệ quy ở chỗ bạn đang chia vấn đề thành các phần nhỏ hơn và nhỏ hơn để đi đến giải pháp.
Zachary K

2
@Zachary: Các thuật toán có thể được thực hiện với đệ quy đuôi (như tìm kiếm nhị phân) thuộc một lớp không gian khác về cơ bản so với các thuật toán yêu cầu đệ quy thực (hoặc các cấu trúc trạng thái của riêng bạn với các yêu cầu không gian đắt tiền như nhau). Tôi không nghĩ rằng việc họ được dạy cùng nhau có ích như thể chúng giống nhau.
R ..

8

Sắp xếp, định nghĩa đệ quy trong Python.

def sort( a ):
    if len(a) == 1: return a
    part1= sort( a[:len(a)//2] )
    part2= sort( a[len(a)//2:] )
    return merge( part1, part2 )

Hợp nhất, định nghĩa đệ quy.

def merge( a, b ):
    if len(b) == 0: return a
    if len(a) == 0: return b
    if a[0] < b[0]:
        return [ a[0] ] + merge(a[1:], b)
    else:
        return [ b[0] ] + merge(a, b[1:]) 

Tìm kiếm tuyến tính, được định nghĩa đệ quy.

def find( element, sequence ):
    if len(sequence) == 0: return False
    if element == sequence[0]: return True
    return find( element, sequence[1:] )

Tìm kiếm nhị phân, định nghĩa đệ quy.

def binsearch( element, sequence ):
    if len(sequence) == 0: return False
    mid = len(sequence)//2
    if element < mid: 
        return binsearch( element, sequence[:mid] )
    else:
        return binsearch( element, sequence[mid:] )

6

Theo một nghĩa nào đó, đệ quy là tất cả về phân chia và chinh phục các giải pháp, đó là đưa không gian vấn đề vào một vấn đề nhỏ hơn để giúp tìm ra giải pháp cho một vấn đề đơn giản, và sau đó thường quay lại xây dựng lại vấn đề ban đầu để đưa ra câu trả lời đúng.

Một số ví dụ không liên quan đến toán học để dạy đệ quy (ít nhất là những vấn đề tôi nhớ từ những năm đại học):

Đây là những ví dụ về việc sử dụng Backtracking để giải quyết vấn đề.

Các vấn đề khác là kinh điển của miền Trí tuệ nhân tạo: Sử dụng Depth First Search, tìm đường, lập kế hoạch.

Tất cả những vấn đề đó liên quan đến một số loại cấu trúc dữ liệu "phức tạp", nhưng nếu bạn không muốn dạy nó bằng toán học (số) thì sự lựa chọn của bạn có thể bị hạn chế hơn. Yoy có thể muốn bắt đầu giảng dạy với cấu trúc dữ liệu cơ bản, như Danh sách được liên kết. Ví dụ: biểu thị các số tự nhiên bằng cách sử dụng Danh sách:

0 = danh sách trống 1 = danh sách có một nút. 2 = danh sách có 2 nút. ...

sau đó xác định tổng của hai số theo cấu trúc dữ liệu này như sau: Empty + N = N Node (X) + N = Node (X + N)


5

Tháp Hà Nội là một trong những tốt để giúp học đệ quy.

Có nhiều giải pháp cho nó trên web bằng nhiều ngôn ngữ khác nhau.


3
Đây thực sự là theo ý kiến ​​của tôi một ví dụ xấu khác. Trước hết, nó là không thực tế; đó không phải là vấn đề mà mọi người thực sự gặp phải. Thứ hai, có những giải pháp không đệ quy dễ dàng. . )
Eric Lippert

5

Một máy dò Palindrom:

Bắt đầu bằng một chuỗi: "ABCDEEDCBA" Nếu các ký tự bắt đầu và kết thúc bằng nhau, sau đó lặp lại và kiểm tra "BCDEEDCB", v.v ...


6
Đó cũng là chuyện nhỏ để giải quyết mà không cần đệ quy và, IMHO, giải quyết tốt hơn mà không cần giải quyết.
Blrfl

3
Đồng ý, nhưng OP đặc biệt yêu cầu các ví dụ về Dạy học với việc sử dụng tối thiểu các cấu trúc dữ liệu.
NWS

5
Đó không phải là một ví dụ giảng dạy tốt nếu sinh viên của bạn có thể nghĩ ngay đến một giải pháp không đệ quy. Tại sao ai đó sẽ chú ý khi ví dụ của bạn là "Đây là một việc nhỏ để làm với một vòng lặp. Bây giờ tôi sẽ chỉ cho bạn một cách khó hơn mà không có lý do rõ ràng."
Phục hồi Monica

5

Một thuật toán tìm kiếm nhị phân nghe có vẻ như những gì bạn muốn.


4

Trong các ngôn ngữ lập trình chức năng, khi không có chức năng bậc cao hơn, đệ quy được sử dụng thay vì các vòng lặp bắt buộc để tránh trạng thái đột biến.

F # là một ngôn ngữ chức năng không tinh khiết cho phép cả hai phong cách vì vậy tôi sẽ so sánh cả hai ở đây. Tổng hợp sau đây tất cả các số trong một danh sách.

Vòng lặp bắt buộc với biến có thể thay đổi

let xlist = [1;2;3;4;5;6;7;8;9;10]
let mutable sum = 0
for x in xlist do
    sum <- sum + x

Vòng lặp đệ quy không có trạng thái có thể thay đổi

let xlist = [1;2;3;4;5;6;7;8;9;10]
let rec loop sum xlist = 
    match xlist with
    | [] -> sum
    | x::xlist -> loop (sum + x) xlist
let sum = loop 0 xlist

Lưu ý rằng loại tổng hợp này được ghi lại trong hàm bậc cao hơn List.foldvà có thể được viết dưới dạng List.fold (+) 0 xlisthoặc thậm chí đơn giản hơn với hàm tiện lợi List.sumnhư chỉ List.sum xlist.


bạn sẽ xứng đáng được nhiều điểm hơn là chỉ +1 từ tôi. F # không có đệ quy sẽ rất tẻ nhạt, điều này thậm chí còn đúng hơn đối với Haskell không có cấu trúc vòng lặp CHỈ đệ quy!
schoetbi

3

Tôi đã sử dụng đệ quy rất nhiều trong trò chơi AI. Viết bằng C ++, tôi đã sử dụng một chuỗi gồm khoảng 7 hàm gọi nhau theo thứ tự (với hàm đầu tiên có tùy chọn bỏ qua tất cả các hàm đó và gọi thay vào đó là một chuỗi gồm 2 hàm nữa). Hàm cuối cùng trong một trong hai chuỗi được gọi là hàm đầu tiên một lần nữa cho đến khi độ sâu còn lại tôi muốn tìm kiếm về 0, trong trường hợp đó, hàm cuối cùng sẽ gọi hàm đánh giá của tôi và trả về điểm của vị trí.

Nhiều chức năng cho phép tôi dễ dàng phân nhánh dựa trên quyết định của người chơi hoặc các sự kiện ngẫu nhiên trong trò chơi. Tôi đã sử dụng tham chiếu qua bất cứ khi nào tôi có thể, bởi vì tôi đang đi qua các cấu trúc dữ liệu rất lớn, nhưng vì cách cấu trúc trò chơi, nên rất khó để có một "hoàn tác di chuyển" trong tìm kiếm của tôi, vì vậy Tôi sẽ sử dụng mật khẩu trong một số chức năng để giữ cho dữ liệu gốc của mình không thay đổi. Bởi vì điều này, việc chuyển sang một cách tiếp cận dựa trên vòng lặp thay vì một cách tiếp cận đệ quy tỏ ra quá khó khăn.

Bạn có thể thấy một phác thảo rất cơ bản của loại chương trình này, xem https://secure.wikidia.org/wikipedia/en/wiki/Minimax#Pseudocode


3

Một ví dụ thực tế tốt trong kinh doanh là một thứ gọi là "Hóa đơn vật liệu". Đây là dữ liệu đại diện cho tất cả các thành phần tạo nên một sản phẩm hoàn chỉnh. Ví dụ: hãy sử dụng Xe đạp. Một chiếc xe đạp có tay lái, bánh xe, khung, v.v ... Và mỗi thành phần đó có thể có các thành phần phụ. ví dụ Bánh xe có thể có Phát ngôn, thân van, v.v ... Vì vậy, thông thường chúng được thể hiện trong cấu trúc cây.

Bây giờ để truy vấn bất kỳ thông tin tổng hợp nào về BOM hoặc để thay đổi các yếu tố trong BOM thường bạn sử dụng đệ quy.

    class BomPart
    {
        public string PartNumber { get; set; }
        public string Desription { get; set; }
        public int Quantity { get; set; }
        public bool Plastic { get; set; }
        public List<BomPart> Components = new List<BomPart>();
    }

Và một cuộc gọi đệ quy mẫu ...

    static int ComponentCount(BomPart part)
    {
        int subCount = 0;
        foreach(BomPart p in part.Components)
            subCount += ComponentCount(p);
        return part.Quantity * Math.Max(1,subCount);

    }

Rõ ràng Lớp BomPart sẽ có nhiều lĩnh vực hơn. Bạn có thể cần phải tính xem bạn có bao nhiêu thành phần nhựa, cần bao nhiêu công sức để xây dựng một bộ phận hoàn chỉnh, v.v ... Tất cả điều này trở lại với tính hữu ích của Recursion trên cấu trúc cây.


Một hóa đơn Vật liệu có thể là một grath được định hướng chứ không phải là một cái cây, ví dụ như cùng một loại vít có thể được sử dụng bởi nhiều hơn một thành phần.
Ian

-1

Quan hệ gia đình là ví dụ điển hình vì mọi người đều hiểu họ bằng trực giác:

ancestor(joe, me) = (joe == me) 
                    OR ancestor(joe, me.father) 
                    OR ancestor(joe, me.mother);

mã này được viết bằng ngôn ngữ nào?
törzsmókus

@ törzsmókus Không có ngôn ngữ cụ thể. Cú pháp khá gần với C, Obj-C, C ++, Java và nhiều ngôn ngữ khác, nhưng nếu bạn muốn mã thực, bạn có thể cần phải thay thế một toán tử thích hợp như ||cho OR.
Caleb

-1

Một khá vô dụng nhưng cho thấy đệ quy bên trong làm việc tốt là đệ quy strlen():

size_t strlen( const char* str )
{
    if( *str == 0 ) {
       return 0;
    }
    return 1 + strlen( str + 1 );
}

Không có toán học - một chức năng rất đơn giản. Tất nhiên bạn không thực hiện nó một cách đệ quy trong cuộc sống thực, nhưng đó là một bản demo tốt về đệ quy.


-2

Một vấn đề đệ quy trong thế giới thực khác mà sinh viên có thể liên quan đến là xây dựng trình thu thập dữ liệu web của riêng họ để lấy thông tin từ một trang web và theo dõi tất cả các liên kết trong trang web đó (và tất cả các liên kết từ các liên kết đó, v.v.).


2
Điều đó thường được phục vụ tốt hơn bởi một hàng đợi quá trình trái ngược với đệ quy theo nghĩa truyền thống.
lông mịn

-2

Tôi đã giải quyết một vấn đề với mô hình hiệp sĩ (trên bàn cờ) bằng chương trình đệ quy. Bạn phải di chuyển hiệp sĩ xung quanh để nó chạm vào mọi ô vuông trừ một vài ô vuông được đánh dấu.

Bạn đơn giản:

mark your "Current" square
gather a list of free squares that are valid moves
are there no valid moves?
    are all squares marked?
        you win!
for each free square
    recurse!
clear the mark on your current square.
return.    

Nhiều loại kịch bản "nghĩ trước" có thể được thực hiện bằng cách kiểm tra các khả năng trong tương lai trong một cây như thế này.

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.