Làm cách nào để xác định thời gian chạy của hàm đệ quy kép?


15

Với bất kỳ hàm đệ quy kép tùy ý, làm thế nào để tính thời gian chạy của nó?

Ví dụ (bằng mã giả):

int a(int x){
  if (x < = 0)
    return 1010;
  else
    return b(x-1) + a(x-1);
}
int b(int y){
  if (y <= -5)
    return -2;
  else
    return b(a(y-1));
}

Hoặc một cái gì đó dọc theo các đường dây.

Những phương pháp nào có thể hoặc nên sử dụng để xác định một cái gì đó như thế này?


2
Đây có phải là bài tập về nhà không?
Bernard

5
Không, đó là thời gian mùa hè và tôi thích học hỏi. Tôi nghĩ về phía trước thay vì để cho bộ não của tôi chuyển sang ủ rũ.
if_zero_equals_one

11
Được rồi, đã rõ. Đối với những người bỏ phiếu để di chuyển cái này sang Stack Overflow: đây là chủ đề tại đây và ngoài chủ đề trên Stack Overflow. Lập trình viên.SE dành cho các câu hỏi về khái niệm, bảng trắng; Stack Overflow là để thực hiện, các câu hỏi trong khi tôi đang mã hóa.

3
Cảm ơn đó là lý do tôi đã làm nó ở đây ngay từ đầu. Bên cạnh đó, tốt hơn là biết cách câu cá hơn là nhận một con cá.
if_zero_equals_one

1
Trong trường hợp cụ thể này, nó vẫn thường là một đệ quy vô hạn vì b (a (0)) gọi vô hạn nhiều thuật ngữ b (a (0)) khác. Nó sẽ khác nếu nó là một công thức toán học. Nếu thiết lập của bạn khác đi, nó sẽ hoạt động khác đi. Cũng giống như trong toán học, trong cs, một số vấn đề có một giải pháp, một số thì không, một số thì có một dễ, một số thì không. Có nhiều trường hợp đệ quy lẫn nhau trong đó giải pháp không tồn tại. Đôi khi, để không thổi một chồng, người ta sẽ phải sử dụng mô hình tấm bạt lò xo.
Công việc

Câu trả lời:


11

Bạn tiếp tục thay đổi chức năng của bạn. Nhưng hãy tiếp tục chọn những thứ sẽ chạy mãi mãi mà không cần chuyển đổi ..

Đệ quy trở nên phức tạp, nhanh chóng. Bước đầu tiên để phân tích một hàm đệ quy gấp đôi được đề xuất là cố gắng theo dõi nó thông qua một số giá trị mẫu, để xem nó làm gì. Nếu tính toán của bạn vào một vòng lặp vô hạn, hàm không được xác định rõ. Nếu tính toán của bạn rơi vào vòng xoáy liên tục nhận được số lớn hơn (điều này xảy ra rất dễ dàng), thì có lẽ nó không được xác định rõ.

Nếu truy tìm nó thông qua một câu trả lời, thì bạn sẽ cố gắng đưa ra một số mô hình hoặc mối quan hệ lặp lại giữa các câu trả lời. Một khi bạn có điều đó, bạn có thể cố gắng tìm ra thời gian chạy của nó. Chỉ ra nó có thể rất, rất phức tạp, nhưng chúng ta có kết quả như định lý Master cho phép chúng ta tìm ra câu trả lời trong nhiều trường hợp.

Xin lưu ý rằng ngay cả với đệ quy đơn, rất dễ dàng đưa ra các hàm có thời gian chạy mà chúng ta không biết cách tính toán. Ví dụ, hãy xem xét những điều sau đây:

def recursive (n):
    if 0 == n%2:
        return 1 + recursive(n/2)
    elif 1 == n:
        return 0
    else:
        return recursive(3*n + 1)

Đó là hiện vẫn chưa biết liệu chức năng này luôn luôn được xác định rõ, chứ chưa nói gì thời gian chạy của nó là.


5

Thời gian chạy của cặp hàm cụ thể đó là vô hạn vì không trả về mà không gọi hàm kia. Giá trị trả về của aluôn luôn phụ thuộc vào giá trị trả về của một cuộc gọi đến bluôn gọi a... và đó là những gì được biết đến như đệ quy vô hạn .


Không tìm kiếm các chức năng cụ thể ngay tại đây. Tôi đang tìm một cách tổng quát để tìm thời gian chạy của các hàm đệ quy gọi nhau.
if_zero_equals_one

1
Tôi không chắc chắn có một giải pháp trong trường hợp chung. Để Big-O có ý nghĩa, bạn phải biết liệu thuật toán sẽ dừng lại. Có một số thuật toán đệ quy mà bạn phải chạy phép tính trước khi bạn biết sẽ mất bao lâu (ví dụ: xác định xem một điểm có thuộc tập Mandlebrot hay không).
jimreed

Không phải luôn luôn, achỉ gọi bnếu số được truyền vào là> = 0. Nhưng có, có một vòng lặp vô hạn.
btilly

1
@btilly ví dụ đã được thay đổi sau khi tôi đăng câu trả lời của mình.
jimreed

1
@jimreed: Và nó đã được thay đổi một lần nữa. Tôi sẽ xóa bình luận của tôi nếu tôi có thể.
btilly

4

Phương pháp rõ ràng là chạy hàm và đo thời gian cần thiết. Điều này chỉ cho bạn biết mất bao lâu cho một đầu vào cụ thể, mặc dù. Và nếu bạn không biết trước rằng chức năng chấm dứt, khó khăn: không có cách nào cơ học để tìm hiểu xem chức năng đó có chấm dứt hay không - đó là vấn đề tạm dừng , và đó là điều không thể giải quyết được.

Tìm thời gian chạy của hàm là tương tự không thể giải quyết được, theo định lý của Rice . Trên thực tế, định lý của Rice cho thấy rằng thậm chí quyết định liệu một hàm có chạy O(f(n))đúng lúc hay không là điều không thể giải quyết được.

Vì vậy, điều tốt nhất bạn có thể làm nói chung là sử dụng trí thông minh của con người (mà theo như chúng tôi biết, không bị ràng buộc bởi các giới hạn của máy Turing) và cố gắng nhận ra một mô hình hoặc phát minh ra một mô hình. Một cách điển hình để phân tích thời gian chạy của hàm là biến định nghĩa đệ quy của hàm thành phương trình đệ quy theo thời gian chạy của nó (hoặc một bộ phương trình cho các hàm đệ quy lẫn nhau):

T_a(x) = if x ≤ 0 then 1 else T_b(x-1) + T_a(x-1)
T_b(x) = if x ≤ -5 then 1 else T_b(T_a(x-1))

Tiếp theo là gì? Bây giờ bạn có một vấn đề toán học: bạn cần giải các phương trình chức năng này. Một cách tiếp cận thường hoạt động là biến các phương trình này trên các hàm số nguyên thành phương trình trên các hàm phân tích và sử dụng phép tính để giải các hàm này, diễn giải các hàm T_aT_bnhư là các hàm tạo .

Về việc tạo các hàm và các chủ đề toán học rời rạc khác, tôi giới thiệu cuốn sách Concrete Concrete , của Ronald Graham, Donald Knuth và Oren Patashnik.


1

Như những người khác đã chỉ ra, phân tích đệ quy có thể rất khó rất nhanh. Dưới đây là một ví dụ khác về điều đó: http://rosettacode.org/wiki/Mutual_recursion http://en.wikipedia.org/wiki/Hofstadter_fterence#Hofstadter_Fbang_and_Male_fterences thật khó để tính toán một câu trả lời và thời gian chạy cho những điều này. Điều này là do các hàm đệ quy lẫn nhau này có "dạng khó".

Dù sao đi nữa, hãy xem ví dụ dễ hiểu này:

http://pramode.net/clojure/2010/05/08/clojure-trampoline/

(declare funa funb)
(defn funa [n]
  (if (= n 0)
    0
    (funb (dec n))))
(defn funb [n]
  (if (= n 0)
    0
    (funa (dec n))))

Hãy bắt đầu bằng cách tính toán funa(m), m > 0:

funa(m) = funb(m - 1) = funa(m - 2) = ... funa(0) or funb(0) = 0 either way.

Thời gian chạy là:

R(funa(m)) = 1 + R(funb(m - 1)) = 2 + R(funa(m - 2)) = ... m + R(funa(0)) or m + R(funb(0)) = m + 1 steps either way

Bây giờ hãy chọn một ví dụ khác, phức tạp hơn một chút:

Lấy cảm hứng từ http://planetmath.org/encyclopedia/MutualRecursion.html , đây là một cách đọc tốt, chúng ta hãy xem: "" "Các số Fibonacci có thể được diễn giải thông qua đệ quy lẫn nhau: F (0) = 1 và G (0 ) = 1, với F (n + 1) = F (n) + G (n) và G (n + 1) = F (n). "" "

Vậy, thời gian chạy của F là gì? Chúng tôi sẽ đi theo con đường khác.
Vâng, R (F (0)) = 1 = F (0); R (G (0)) = 1 = G (0)
Bây giờ R (F (1)) = R (F (0)) + R (G (0)) = F (0) + G (0) = F (1)
...
Không khó để thấy rằng R (F (m)) = F (m) - ví dụ: số lượng các lệnh gọi hàm cần thiết để tính toán một số Fibonacci tại chỉ số i bằng với giá trị của một số Fibonacci tại chỉ số i. Điều này giả định rằng việc cộng hai số lại với nhau nhanh hơn nhiều so với lệnh gọi hàm. Nếu đây không phải là trường hợp, thì điều này sẽ đúng: R (F (1)) = R (F (0)) + 1 + R (G (0)), và việc phân tích điều này sẽ phức tạp hơn, có thể không có một giải pháp dạng đóng dễ dàng.

Biểu mẫu đóng cho chuỗi Fibonacci không nhất thiết phải dễ dàng phát minh lại, chưa kể một số ví dụ phức tạp hơn.


0

Điều đầu tiên cần làm là chỉ ra rằng các hàm bạn đã xác định chấm dứt và giá trị nào chính xác. Trong ví dụ bạn đã xác định

int a(int x){
  if (x < = 0)
    return 1010;
  else
    return b(x-1) + a(x-1);
}
int b(int y){
  if (y <= -5)
    return -2;
  else
    return b(a(y-1));
}

bchỉ chấm dứt y <= -5vì nếu bạn cắm vào bất kỳ giá trị nào khác thì bạn sẽ có một thuật ngữ của biểu mẫu b(a(y-1)). Nếu bạn mở rộng thêm một chút, bạn sẽ thấy rằng một thuật ngữ của biểu mẫu b(a(y-1))cuối cùng sẽ dẫn đến thuật ngữ b(1010)dẫn đến một thuật ngữ b(a(1009))một lần nữa dẫn đến thuật ngữ đó b(1010). Điều này có nghĩa là bạn không thể cắm bất kỳ giá trị nào vào ađiều đó không thỏa mãn x <= -4bởi vì nếu bạn kết thúc với một vòng lặp vô hạn trong đó giá trị được tính toán phụ thuộc vào giá trị được tính toán. Vì vậy, về cơ bản ví dụ này có thời gian chạy liên tục.

Vì vậy, câu trả lời đơn giản là không có phương pháp chung để xác định thời gian chạy của các hàm đệ quy vì không có quy trình chung xác định xem một hàm được định nghĩa đệ quy có chấm dứt hay không.


-5

Thời gian chạy như trong Big-O?

Điều đó thật dễ dàng: O (N) - giả sử rằng có một điều kiện chấm dứt.

Đệ quy chỉ là vòng lặp và một vòng lặp đơn giản là O (N) cho dù bạn có bao nhiêu việc trong vòng lặp đó (và gọi một phương thức khác chỉ là một bước khác trong vòng lặp).

Nơi nó sẽ trở nên thú vị là nếu bạn có một vòng lặp trong một hoặc nhiều phương thức đệ quy. Trong trường hợp đó, bạn sẽ kết thúc với một số loại hiệu suất theo cấp số nhân (nhân với O (N) trên mỗi lần chuyển qua phương thức).


2
Bạn xác định hiệu suất Big-O bằng cách lấy thứ tự cao nhất của bất kỳ phương thức được gọi nào và nhân nó với thứ tự của phương thức gọi. Tuy nhiên, một khi bạn bắt đầu nói về hiệu suất theo cấp số nhân và giai thừa, bạn có thể bỏ qua hiệu suất đa thức. Tôi tin rằng điều tương tự cũng xảy ra khi so sánh theo cấp số nhân và giai thừa: giai thừa. Tôi chưa bao giờ phải phân tích một hệ thống cả theo cấp số nhân và giai thừa.
Anon

5
Điều này là không chính xác. Các hình thức đệ quy để tính toán số Fibonacci thứ n và quicksort làO(2^n)O(n*log(n)) lần lượt và .
unpythonic

1
Không cần thực hiện một số bằng chứng thú vị nào, tôi muốn hướng dẫn bạn đến amazon.com/Intesistion-Alacticms-Second-Thomas-Cormen/dp/ , và thử xem trang web SE này cstheory.stackexchange.com .
Bryan Harrington

4
Tại sao mọi người bỏ phiếu cho câu trả lời sai lầm khủng khiếp này? Gọi một phương thức mất thời gian tỷ lệ thuận với thời gian mà phương thức đó mất. Trong trường hợp này, phương thức agọi bbcuộc gọia để bạn không thể giả sử rằng một trong hai phương thức đó cần có thời gian O(1).
btilly

2
@Anon - Người đăng đã yêu cầu một hàm đệ quy tùy ý, không chỉ là chức năng được hiển thị ở trên. Tôi đã đưa ra hai ví dụ về đệ quy đơn giản không phù hợp với lời giải thích của bạn. Việc chuyển đổi các tiêu chuẩn cũ thành một dạng "đệ quy kép" không quan trọng, một dạng theo cấp số nhân (phù hợp với sự cảnh báo của bạn) và một tiêu chuẩn không (không được bảo hiểm).
unpythonic
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.