Determining the complexities given codes

前端 未结 6 440
天涯浪人
天涯浪人 2021-01-30 11:19

Given a snipplet of code, how will you determine the complexities in general. I find myself getting very confused with Big O questions. For example, a very simple question:

6条回答
  •  醉话见心
    2021-01-30 11:45

    In general, deciding algorithm complexity is theoretically impossible.

    However, one cool and code-centric method for doing it is to actually just think in terms of programs directly. Take your example:

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            System.out.println("*");
        }
    }
    

    Now we want to analyze its complexity, so let's add a simple counter that counts the number of executions of the inner line:

    int counter = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            System.out.println("*");
            counter++;
        }
    }
    

    Because the System.out.println line doesn't really matter, let's remove it:

    int counter = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            counter++;
        }
    }
    

    Now that we have only the counter left, we can obviously simplify the inner loop out:

    int counter = 0;
    for (int i = 0; i < n; i++) {
        counter += n;
    }
    

    ... because we know that the increment is run exactly n times. And now we see that counter is incremented by n exactly n times, so we simplify this to:

    int counter = 0;
    counter += n * n;
    

    And we emerged with the (correct) O(n2) complexity :) It's there in the code :)

    Let's look how this works for a recursive Fibonacci calculator:

    int fib(int n) {
      if (n < 2) return 1;
      return fib(n - 1) + fib(n - 2);
    }
    

    Change the routine so that it returns the number of iterations spent inside it instead of the actual Fibonacci numbers:

    int fib_count(int n) {
      if (n < 2) return 1;
      return fib_count(n - 1) + fib_count(n - 2);
    }
    

    It's still Fibonacci! :) So we know now that the recursive Fibonacci calculator is of complexity O(F(n)) where F is the Fibonacci number itself.

    Ok, let's look at something more interesting, say simple (and inefficient) mergesort:

    void mergesort(Array a, int from, int to) {
      if (from >= to - 1) return;
      int m = (from + to) / 2;
      /* Recursively sort halves */
      mergesort(a, from, m);
      mergesort(m, m,    to);
      /* Then merge */
      Array b = new Array(to - from);
      int i = from;
      int j = m;
      int ptr = 0;
      while (i < m || j < to) {
        if (i == m || a[j] < a[i]) {
          b[ptr] = a[j++];
        } else {
          b[ptr] = a[i++];
        }
        ptr++;
      }
      for (i = from; i < to; i++)
        a[i] = b[i - from];
    }
    

    Because we are not interested in the actual result but the complexity, we change the routine so that it actually returns the number of units of work carried out:

    int mergesort(Array a, int from, int to) {
      if (from >= to - 1) return 1;
      int m = (from + to) / 2;
      /* Recursively sort halves */
      int count = 0;
      count += mergesort(a, from, m);
      count += mergesort(m, m,    to);
      /* Then merge */
      Array b = new Array(to - from);
      int i = from;
      int j = m;
      int ptr = 0;
      while (i < m || j < to) {
        if (i == m || a[j] < a[i]) {
          b[ptr] = a[j++];
        } else {
          b[ptr] = a[i++];
        }
        ptr++;
        count++;
      }
      for (i = from; i < to; i++) {
        count++;
        a[i] = b[i - from];
      }
      return count;
    }
    

    Then we remove those lines that do not actually impact the counts and simplify:

    int mergesort(Array a, int from, int to) {
      if (from >= to - 1) return 1;
      int m = (from + to) / 2;
      /* Recursively sort halves */
      int count = 0;
      count += mergesort(a, from, m);
      count += mergesort(m, m,    to);
      /* Then merge */
      count += to - from;
      /* Copy the array */
      count += to - from;
      return count;
    }
    

    Still simplifying a bit:

    int mergesort(Array a, int from, int to) {
      if (from >= to - 1) return 1;
      int m = (from + to) / 2;
      int count = 0;
      count += mergesort(a, from, m);
      count += mergesort(m, m,    to);
      count += (to - from) * 2;
      return count;
    }
    

    We can now actually dispense with the array:

    int mergesort(int from, int to) {
      if (from >= to - 1) return 1;
      int m = (from + to) / 2;
      int count = 0;
      count += mergesort(from, m);
      count += mergesort(m,    to);
      count += (to - from) * 2;
      return count;
    }
    

    We can now see that actually the absolute values of from and to do not matter any more, but only their distance, so we modify this to:

    int mergesort(int d) {
      if (d <= 1) return 1;
      int count = 0;
      count += mergesort(d / 2);
      count += mergesort(d / 2);
      count += d * 2;
      return count;
    }
    

    And then we get to:

    int mergesort(int d) {
      if (d <= 1) return 1;
      return 2 * mergesort(d / 2) + d * 2;
    }
    

    Here obviously d on the first call is the size of the array to be sorted, so you have the recurrence for the complexity M(x) (this is in plain sight on the second line :)

    M(x) = 2(M(x/2) + x)
    

    and this you need to solve in order to get to a closed form solution. This you do easiest by guessing the solution M(x) = x log x, and verify for the right side:

    2 (x/2 log x/2 + x)
            = x log x/2 + 2x
            = x (log x - log 2 + 2)
            = x (log x - C)
    

    and verify it is asymptotically equivalent to the left side:

    x log x - Cx
    ------------ = 1 - [Cx / (x log x)] = 1 - [C / log x] --> 1 - 0 = 1.
    x log x
    

提交回复
热议问题