Xác định độ phức tạp cho các hàm đệ quy (ký hiệu Big O)


267

Tôi có một Khoa học máy tính giữa kỳ vào ngày mai và tôi cần trợ giúp để xác định mức độ phức tạp của các hàm đệ quy này. Tôi biết cách giải quyết những trường hợp đơn giản, nhưng tôi vẫn đang cố gắng học cách giải quyết những trường hợp khó hơn này. Đây chỉ là một vài trong số các vấn đề mẫu mà tôi không thể tìm ra. Bất kỳ trợ giúp sẽ được nhiều đánh giá cao và sẽ giúp rất nhiều trong nghiên cứu của tôi, Cảm ơn bạn!

int recursiveFun1(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun1(n-1);
}

int recursiveFun2(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun2(n-5);
}

int recursiveFun3(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun3(n/5);
}

void recursiveFun4(int n, int m, int o)
{
    if (n <= 0)
    {
        printf("%d, %d\n",m, o);
    }
    else
    {
        recursiveFun4(n-1, m+1, o);
        recursiveFun4(n-1, m, o+1);
    }
}

int recursiveFun5(int n)
{
    for (i = 0; i < n; i += 2) {
        // do something
    }

    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun5(n-5);
}

4
Nếu bạn không muốn đi qua phân tích mỗi lần, có một kỹ thuật hộp đen được gọi là phương pháp Master. Nhưng với giả định rằng tất cả các phân chia đệ quy của đầu vào có kích thước bằng nhau trong mỗi trường hợp.
Vivek Krishna


Câu trả lời:


345

Độ phức tạp thời gian, theo ký hiệu Big O, cho mỗi hàm, theo thứ tự số:

  1. Hàm đầu tiên đang được gọi đệ quy n lần trước khi đến trường hợp cơ sở, do đó O(n), thường được gọi là tuyến tính .
  2. Hàm thứ hai được gọi là n-5 cho mỗi lần, vì vậy chúng tôi trừ năm từ n trước khi gọi hàm, nhưng n-5 cũng vậy O(n). (Trên thực tế được gọi là thứ tự của n / 5 lần. Và, O (n / 5) = O (n)).
  3. Hàm này là log (n) cơ sở 5, vì mỗi lần chúng ta chia cho 5 trước khi gọi hàm nên O(log(n))(cơ sở 5), thường được gọi là logarit và phân tích độ phức tạp và ký hiệu Big O thường sử dụng cơ sở 2.
  4. Trong lần thứ tư, nó O(2^n), hoặc theo cấp số nhân , vì mỗi hàm gọi chính nó hai lần trừ khi nó được đệ quy n lần.
  5. Đối với hàm cuối cùng, vòng lặp for mất n / 2 vì chúng ta tăng thêm 2 và đệ quy mất n-5 và vì vòng lặp for được gọi đệ quy do đó độ phức tạp thời gian nằm trong (n-5) * (n / 2) = (2n-10) * n = 2n ^ 2- 10n, do hành vi tiệm cận và cân nhắc trường hợp xấu nhất hoặc giới hạn trên mà O lớn đang cố gắng, chúng tôi chỉ quan tâm đến thuật ngữ lớn nhất như vậy O(n^2).

    Chúc may mắn trên midterms của bạn;)


Quyền của bạn về thứ năm, n sẽ giảm cho vòng lặp for nhưng đối với thứ tư tôi không nghĩ n ^ 2 của nó giống như một cái cây mỗi khi bạn gọi đệ quy hai lần nên nó sẽ là 2 ^ n cộng với đó là của bạn Trả lời trong bình luận trước đó.
viên

2
@MJGwater Hãy để thời gian chạy của vòng lặp là m. Khi đệ quy chạy 1 lần, phải mất m để thực hiện vòng lặp. Khi đệ quy chạy 2 lần, vòng lặp cũng chạy 2 lần, vì vậy phải mất 2m ... và cứ thế. Vì vậy, đó là '*', không phải '^'.
bjc

3
@coder Lời giải thích cho 5 có vẻ kỳ quặc. Nếu tăng thêm 2 kết quả trong các n/2lần lặp của forvòng lặp, tại sao việc giảm 5 sẽ không dẫn đến n/5các cuộc gọi đệ quy? Điều này vẫn sẽ dẫn đến O(n^2)nhưng có vẻ như một lời giải thích trực quan hơn. Tại sao trộn lẫn phép trừ và phép chia khi chúng cần thiết làm cùng một việc?
Jack

1
@coder vậy đối với # 4, nếu có 3 lệnh gọi đệ quy trong định nghĩa hàm, nó sẽ có độ phức tạp thời gian là O (3 ^ n)? Và trong 5 cuộc gọi đệ quy, nó sẽ là O (5 ^ n), đúng không?
rmutalik

1
@Jack Vâng, tôi cũng đã tự hỏi tương tự. Nó phải là n/5không n-5. Và cuối cùng, toàn bộ sẽ sôi sục xuống O(N^2).
Anuj

128

Đối với trường hợp n <= 0, T(n) = O(1). Do đó, độ phức tạp thời gian sẽ phụ thuộc vào khi nào n >= 0.

Chúng tôi sẽ xem xét trường hợp n >= 0trong phần dưới đây.

1.

T(n) = a + T(n - 1)

trong đó a là hằng số.

Theo cảm ứng:

T(n) = n * a + T(0) = n * a + b = O(n)

trong đó a, b là một số hằng số.

2.

T(n) = a + T(n - 5)

trong đó a là hằng số

Theo cảm ứng:

T(n) = ceil(n / 5) * a + T(k) = ceil(n / 5) * a + b = O(n)

trong đó a, b là một số hằng và k <= 0

3.

T(n) = a + T(n / 5)

trong đó a là hằng số

Theo cảm ứng:

T(n) = a * log5(n) + T(0) = a * log5(n) + b = O(log n)

trong đó a, b là một số hằng

4.

T(n) = a + 2 * T(n - 1)

trong đó a là hằng số

Theo cảm ứng:

T(n) = a + 2a + 4a + ... + 2^(n-1) * a + T(0) * 2^n 
     = a * 2^n - a + b * 2^n
     = (a + b) * 2^n - a
     = O(2 ^ n)

trong đó a, b là một số hằng số.

5.

T(n) = n / 2 + T(n - 5)

trong đó n là hằng số

Viết lại n = 5q + rtrong đó q và r là số nguyên và r = 0, 1, 2, 3, 4

T(5q + r) = (5q + r) / 2 + T(5 * (q - 1) + r)

Chúng ta có q = (n - r) / 5, và vì r <5, chúng ta có thể coi nó là một hằng số, vì vậyq = O(n)

Theo cảm ứng:

T(n) = T(5q + r)
     = (5q + r) / 2 + (5 * (q - 1) + r) / 2 + ... + r / 2 +  T(r)
     = 5 / 2 * (q + (q - 1) + ... + 1) +  1 / 2 * (q + 1) * r + T(r)
     = 5 / 4 * (q + 1) * q + 1 / 2 * (q + 1) * r + T(r)
     = 5 / 4 * q^2 + 5 / 4 * q + 1 / 2 * q * r + 1 / 2 * r + T(r)

Vì r <4, chúng ta có thể tìm thấy một số b không đổi b >= T(r)

T(n) = T(5q + r)
     = 5 / 2 * q^2 + (5 / 4 + 1 / 2 * r) * q + 1 / 2 * r + b
     = 5 / 2 * O(n ^ 2) + (5 / 4 + 1 / 2 * r) * O(n) + 1 / 2 * r + b
     = O(n ^ 2)

1
Gần đây tôi đã thất bại một câu hỏi phỏng vấn (và bằng cách mở rộng cuộc phỏng vấn) có liên quan đến việc phân tích sự phức tạp về thời gian và không gian của một hàm đệ quy. Câu trả lời này thật tuyệt vời và nó đã giúp ích rất nhiều, tôi yêu nó, tôi ước tôi có thể bầu bạn hai lần. Tôi biết nó đã cũ nhưng bạn có bất cứ điều gì tương tự để tính toán không gian - có thể là một liên kết, bất cứ điều gì không?
Dimitar Dimitrov

Đối với số 4, mặc dù kết quả là như nhau, không nên cảm ứng như sau? T (n) = a + 2T (n-1) = a + 2a + 4T (n-1) = 3a + 4a + 8T (n-1) = a * (2 ^ n - 1) + 2 ^ n * T (0) = a * (2 ^ n - 1) + b * 2 ^ n = (a + b) * 2 ^ n - a = O (2 ^ n)
Cá tuyết

27

Một trong những cách tốt nhất tôi tìm thấy để tính gần đúng độ phức tạp của thuật toán đệ quy là vẽ cây đệ quy. Một khi bạn có cây đệ quy:

Complexity = length of tree from root node to leaf node * number of leaf nodes
  1. Hàm đầu tiên sẽ có độ dài nvà số lượng nút lá 1nên độ phức tạp sẽ làn*1 = n
  2. Hàm thứ hai sẽ có độ dài n/5và số lượng nút lá một lần nữa 1nên độ phức tạp sẽ là n/5 * 1 = n/5. Nó nên được xấp xỉ đển

  3. Đối với hàm thứ ba, do nđược chia cho 5 cho mỗi lệnh gọi đệ quy, nên độ dài của cây đệ quy sẽ là log(n)(base 5)và số nút lá lại là 1 nên độ phức tạp sẽ làlog(n)(base 5) * 1 = log(n)(base 5)

  4. Đối với hàm thứ tư vì mỗi nút sẽ có hai nút con, số lượng nút lá sẽ bằng (2^n)và chiều dài của cây đệ quy sẽ nrất phức tạp (2^n) * n. Nhưng vì nkhông đáng kể ở phía trước (2^n), nó có thể bị bỏ qua và sự phức tạp chỉ có thể được nói là (2^n).

  5. Đối với chức năng thứ năm, có hai yếu tố giới thiệu sự phức tạp. Độ phức tạp được giới thiệu bởi tính chất đệ quy của hàm và độ phức tạp được giới thiệu bởi forvòng lặp trong mỗi hàm. Thực hiện tính toán trên, độ phức tạp được giới thiệu bởi tính chất đệ quy của hàm sẽ là ~ nvà độ phức tạp do vòng lặp n. Tổng số phức tạp sẽ được n*n.

Lưu ý: Đây là một cách tính toán phức tạp nhanh chóng và bẩn thỉu (không có gì chính thức!). Rất thích nghe phản hồi về điều này. Cảm ơn.


Câu trả lời tuyệt vời! Tôi có một câu hỏi về chức năng thứ tư. Nếu nó có ba cuộc gọi đệ quy, thì câu trả lời sẽ là (3 ^ n). Hay bạn vẫn chỉ nói (2 ^ n)?
Ben Forsrup

@Shubham: # 4 dường như không đúng với tôi. Nếu số lượng lá là 2^nchiều cao của cây phải n, không log n. Chiều cao sẽ chỉ là log nnếu nđại diện cho tổng số nút trong cây. Nhưng nó không.
Julian A.

@BenForsrup: Sẽ là 3 ^ n vì mỗi nút sẽ có ba nút con. Cách tốt nhất để chắc chắn về điều này là tự vẽ cây đệ quy bằng các giá trị giả.
Shubham

# 2 nên là n-5 chứ không phải n / 5
Fintasys

7

Chúng ta có thể chứng minh nó một cách toán học, đó là điều mà tôi đã thiếu trong các câu trả lời ở trên.

Nó có thể nhanh chóng giúp bạn hiểu làm thế nào để tính toán bất kỳ phương pháp. Tôi khuyên bạn nên đọc nó từ trên xuống dưới để hiểu đầy đủ cách thực hiện:

  1. T(n) = T(n-1) + 1Điều đó có nghĩa là thời gian để phương thức kết thúc bằng với cùng một phương thức nhưng với n-1 T(n-1)và bây giờ chúng ta thêm vào + 1vì đó là thời gian cần thiết để hoàn thành các thao tác chung (ngoại trừ T(n-1)). Bây giờ, chúng ta sẽ tìm thấy T(n-1)như sau : T(n-1) = T(n-1-1) + 1. Có vẻ như bây giờ chúng ta có thể tạo thành một chức năng có thể cung cấp cho chúng ta một số loại lặp lại để chúng ta có thể hiểu đầy đủ. Chúng tôi sẽ đặt phía bên phải T(n-1) = ...thay vì T(n-1)bên trong phương thức T(n) = ...sẽ cung cấp cho chúng tôi: T(n) = T(n-1-1) + 1 + 1đó là T(n) = T(n-2) + 2hoặc nói cách khác chúng tôi cần tìm thấy sự thiếu sót của chúng tôi k: T(n) = T(n-k) + k. Bước tiếp theo là thực hiệnn-k và tuyên bố rằng n-k = 1vì khi kết thúc đệ quy, nó sẽ mất chính xác O (1) khin<=0. Từ phương trình đơn giản này, bây giờ chúng ta biết rằng k = n - 1. Chúng ta hãy đặt kvào phương thức cuối cùng của chúng ta: T(n) = T(n-k) + ksẽ cho chúng ta: T(n) = 1 + n - 1chính xác là nhay O(n).
  2. Cũng giống như 1. Bạn có thể tự kiểm tra và thấy rằng mình nhận được O(n).
  3. T(n) = T(n/5) + 1như trước đây, thời gian để phương thức này kết thúc bằng với thời gian của cùng một phương thức nhưng với n/5đó là lý do tại sao nó bị ràng buộc T(n/5). Hãy tìm T(n/5)như trong 1:T(n/5) = T(n/5/5) + 1 đó là T(n/5) = T(n/5^2) + 1. Hãy đặt T(n/5)bên trong T(n)để tính toán cuối cùng : T(n) = T(n/5^k) + k. Một lần nữa như trước đây, n/5^k = 1đó là n = 5^kchính xác như hỏi những gì trong sức mạnh của 5, sẽ cho chúng ta n, câu trả lời là log5n = k(nhật ký của cơ sở 5). Hãy đặt những phát hiện của chúng tôi T(n) = T(n/5^k) + knhư sau: T(n) = 1 + lognđó làO(logn)
  4. T(n) = 2T(n-1) + 1 những gì chúng ta có ở đây về cơ bản vẫn giống như trước đây nhưng lần này chúng ta đang gọi phương thức đệ quy 2 lần, do đó chúng ta nhân nó lên 2. Hãy tìm T(n-1) = 2T(n-1-1) + 1 xem đó là gì T(n-1) = 2T(n-2) + 1. Địa điểm tiếp theo của chúng tôi như trước đây, hãy đặt phát hiện của chúng tôi: T(n) = 2(2T(n-2)) + 1 + 1đó là T(n) = 2^2T(n-2) + 2nơi mang lại cho chúng tôi T(n) = 2^kT(n-k) + k. Hãy tìm kbằng cách tuyên bố rằng n-k = 1đó là k = n - 1. Hãy đặt knhư sau: T(n) = 2^(n-1) + n - 1đó là khoảngO(2^n)
  5. T(n) = T(n-5) + n + 1Nó gần giống như 4 nhưng bây giờ chúng tôi thêm nvì chúng tôi có một forvòng lặp. Chúng ta hãy tìm T(n-5) = T(n-5-5) + n + 1cái nàoT(n-5) = T(n - 2*5) + n + 1 . Chúng ta hãy đặt nó: T(n) = T(n-2*5) + n + n + 1 + 1)đó là T(n) = T(n-2*5) + 2n + 2)và cho k: T(n) = T(n-k*5) + kn + k)một lần nữa: n-5k = 1đó n = 5k + 1là khoảng n = k. Điều này sẽ cho chúng ta: T(n) = T(0) + n^2 + nđó là khoảng O(n^2).

Bây giờ tôi khuyên bạn nên đọc phần còn lại của câu trả lời mà bây giờ, sẽ cho bạn một viễn cảnh tốt hơn. Chúc may mắn chiến thắng những O lớn đó :)


1

Chìa khóa ở đây là trực quan hóa cây gọi. Sau khi thực hiện điều đó, sự phức tạp là:

nodes of the call tree * complexity of other code in the function

thuật ngữ sau có thể được tính giống như cách chúng ta làm cho một hàm lặp bình thường.

Thay vào đó, tổng số nút của một cây hoàn chỉnh được tính là

                  C^L - 1
                  -------  , when C>1
               /   C - 1
              /
 # of nodes =
              \    
               \ 
                  L        , when C=1

Trong đó C là số con của mỗi nút và L là số cấp của cây (bao gồm cả gốc).

Thật dễ dàng để hình dung cây. Bắt đầu từ cuộc gọi đầu tiên (nút gốc) sau đó vẽ một số con giống như số cuộc gọi đệ quy trong hàm. Nó cũng hữu ích để viết tham số được truyền cho lệnh gọi phụ là "giá trị của nút".

Vì vậy, trong các ví dụ trên:

  1. cây gọi ở đây là C = 1, L = n + 1. Độ phức tạp của phần còn lại của hàm là O (1). Do đó tổng độ phức tạp là L * O (1) = (n + 1) * O (1) = O (n)
n     level 1
n-1   level 2
n-2   level 3
n-3   level 4
... ~ n levels -> L = n
  1. gọi cây ở đây là C = 1, L = n / 5. Độ phức tạp của phần còn lại của hàm là O (1). Do đó tổng độ phức tạp là L * O (1) = (n / 5) * O (1) = O (n)
n
n-5
n-10
n-15
... ~ n/5 levels -> L = n/5
  1. gọi cây ở đây là C = 1, L = log (n). Độ phức tạp của phần còn lại của hàm là O (1). Do đó tổng độ phức tạp là L * O (1) = log5 (n) * O (1) = O (log (n))
n
n/5
n/5^2
n/5^3
... ~ log5(n) levels -> L = log5(n)
  1. gọi cây ở đây là C = 2, L = n. Độ phức tạp của phần còn lại của hàm là O (1). Lần này chúng tôi sử dụng công thức đầy đủ cho số nút trong cây cuộc gọi vì C> 1. Do đó tổng độ phức tạp là (C ^ L-1) / (C-1) * O (1) = (2 ^ n - 1 ) * O (1) = O (2 ^ n) .
               n                   level 1
      n-1             n-1          level 2
  n-2     n-2     n-2     n-2      ...
n-3 n-3 n-3 n-3 n-3 n-3 n-3 n-3    ...     
              ...                ~ n levels -> L = n
  1. gọi cây ở đây là C = 1, L = n / 5. Độ phức tạp của phần còn lại của hàm là O (n). Do đó tổng độ phức tạp là L * O (1) = (n / 5) * O (n) = O (n ^ 2)
n
n-5
n-10
n-15
... ~ n/5 levels -> L = n/5
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.