How to prove the asymptotic order of an algorithm?

Asked

Viewed 2,716 times

24

Considering an algorithm like the one below:

function somaMatriz(matA, size) {
  let soma = 0;                                        // 1
  for(let i = 0; i < size; i++){                       // n + 1
    for(let j = 0; j < size; j++){                     // n(n + 1)
      soma += a[i][j];                                 // (n.n)
    }
  }  
  return soma;                                         // 1 
}

In a somewhat detailed analysis - considering unit cost and uniform - the expression referring to cost would be something like:

T(n) = 3 + 2n + 2n²

However, it can easily be noted that T(n) belongs to O(n²).

I know the asymptotic order doesn’t mean literally that this function (in this case, ) dominates the original function (in this case, 3 + 2n + 2n²), but there is a constant - which I will call c - and an "initial" value of n — which I will call m - such that:

Considering as f(n) and 3 + 2n + 2n² as g(n).

cf(n) dominate asymptotically g(n) from a given m.

In the case of the example, one can see it quite easily. Taking c as 3 and m as 4, one has:

3 + 2 * 4 + 2 * 4² = 43

and

3 * 4² = 48

And from this on (n 4), for any possible value of n the function cf(n) domina g(n).

But how can this be proved mathematically? In the above case, for example, I did it in trial and error (and then checked the examples of a book).

For example, even to try to standardize the possible answers, how to prove that:

5n² + 3n O(n²)

  • 1

    The big notation O represents sets. We use sign of equality by vice. The correct would be to say n^2 + n pertence a O(n^2)

  • @Jeffersonquesado I ended up finding the "belong" sign a little ugly for when I put there, then I switched to the xD equality sign

  • 2

    are the language addictions. And honestly gets much prettier with = and everyone understands, so it’s a forgivable addiction

  • 1

    lim(n->inf, 5n2+3n)=lim(n->inf, n(5n+3)) 3 is despicable against 5n therefore ...=lim(n->inf, 5n2)=5*lim(n->inf, n2) ==> ...O(N2)

  • 5n^2+3n <= 5n^2+3n^2 => 5n^2 + 3n <= 8n^2 that would give a k of 8, soon an^2 + bn <= an^2 + bn^2 => an^2 + bn <= (a+b)n^2, which implies that a k of a+b always works. That’s what I was trying to get?

  • @Isac I got confused; => is really an arrow, correct?

  • 1

    @Jeffersonquesado yes an implication arrow(implies that), to follow up the equation. Normally it would be the <=> of equivalent, but came me so

  • @Isac right, it’s just that this same trail has confused me

  • @Isac That’s it. Right on this path.

  • @Isac, I showed membership to a large set O by function division. Particularly it’s the formalization of that you wrote. Not to mention that you feel that o(n) = o(n^2) but that o(n^2) =/= o(n)

  • 1

    @Jeffersonquesado yes, your answer is very good and covers what I said formally, I was just giving a possible and short calculation for the k (which in the question is indicated as m) in an equation of the type an^2+bn

  • Jefferson gave a very complete answer. Actually, it’s very simple. To prove it, just calculate the limit. Why was there no selection of the answer? I should also try to explain to see if he understands?

  • @RHERWOLF Try to explain to see if who understands?

  • You. It says here that you didn’t get any answers, so it hasn’t been cleared up yet, has it? I’m seeing a more direct explanation to post, may be more appropriate.

  • @RHERWOLF actually means that I haven’t decided which answer is going to be chosen yet and I’m accepting new answers. Feel free.

  • @LINQ ok, the answers from there are sufficient, well complete.

Show 11 more comments

3 answers

20


I’m going to give a short answer regarding only the proof, although Jefferson has already spoken (and very well) of complexities, domains, associated notations, etc.

Prove that a function c.g(n) domina f(n) from a given n and c.

Being f(n) a quadratic function as the given example, 5n^2 + 3n, we can formally define it as:

inserir a descrição da imagem aqui

Then we know she’ll always be equal to or less than:

inserir a descrição da imagem aqui

Soon we can transform into an unambiguous and manipulate:

inserir a descrição da imagem aqui

inserir a descrição da imagem aqui

So we are sure that from a c of A+B+C the function c.g(n) domina f(n) for the whole of n >= 1.

Taking the 5n² + 3n by example and applying logic we are sure that a c of 8, coming from (5+3+0), is sufficient to master the function, which does not imply that there is no c lower than also dominate it!

Prove that f(n) which belongs to the set of O(g(n))

In this case we have to use the formula:

inserir a descrição da imagem aqui

Where it will belong to the set if c is a finite real number greater than or equal to 0.

Applying to the same example, and taking f(n) as 5n² + 3n and g(n) as we have:

inserir a descrição da imagem aqui

Like c is 5, proof that 5n² + 3n belongs to O(n²)

We can also generalize this proof to any quadratic function, in the form of inserir a descrição da imagem aqui :

inserir a descrição da imagem aqui

Like a is a coefficient, thus a constant, automatically proves that all quadratic functions belong to O(n²) unless this is negative, which for a complexity analysis of an algorithm may not be.

Both proofs can naturally be extrapolated to functions other than quadratic, generalizing a solution to these types of functions.

  • Elegant method for making polynomials unambiguous with x->infinity

20

The complexity of algorithms is given by a function. When we are dealing with classic nonrecursive problems, we usually come across polynomials.

TL;DR, the @Isac response shows in a very elegant way the necessary results

TL;DR2, optimal response with optimal mathematical rigor on the relevance of function to its order of complexity

As stated in the commentary, the large notation O sets. So when we talk about n^2 + n = O(n^2), we are saying that the function n^2 + n belongs to the set O(n^2). When we say that O(n^2) = O(n^3) we are saying that the set O(n^2) is contained in O(n^3); note that this operation is not commutative as it is not traditional equality, so despite O(n^2) = O(n^3) be true, O(n^3) = O(n^2) is false.

Okay, now how do we define the relevance of a function to a set described by large notation O? Doing a function division leading to the infinity limit (literally, I’ll come back to that point later).

You may have noticed that the big notation O is always accompanied by a function, right? How O(n log n), O(n), O(n^2), O(e^n), O(n^3 + n). The function within the parentheses represents its behavior; I haven’t found a formal name, but we can call this function set signature. the name for this function is limiting function (information courtesy of @LINQ).

To determine whether a function f(x) belongs to the set O(g(n)), we need to know if f(x) dominates the signature function limiting function of the whole g(x); if f(x) dominate g(x), then f(x) does not belong to O(g(x)), belongs to its complementary set omega(g(x)).

Notation omega: in large notation O, we define which functions have maximum asymptotic behavior equal to signing set limiter, in the rating omega, we define which functions have minimum asymptotic behavior equal to signing set; for example, Insertion Sort works in omega(n) (previously ordered set or slightly messy) but merge Sort works in omega(n log n)

Notation theta: if f(x) = O(g(x)) and f(x) = omega(g(x)), then f(x) = theta(g(x)); the notation theta establishes minimal and maximum asymptotic behaviors at the same time; for example, unbranded Bubble Sort works on theta(n^2).

So now we need to define the operation of domination between functions. I will call this function domina(f, g), it takes two functions and returns one of three possible results:

  • dominada if f is dominated by g;
  • co-dominantes if it is not possible to define who dominates whom;
  • dominante if f dominate g.

The function domina(f,g) is described as such:

domina(f,g) usando divisão de funções


Okay, now we know how to define when a function f(x) belongs to a group O(g(x)), but this is still not enough to prove whether a given algorithm runs in that order of complexity. To know this, it is necessary to know more or less how many operations will be necessary for the algorithm to work.

Defining amounts of operations depends on the machine running the algorithm. For example, an x86 has as a unit operation the sum of two 16-bit numbers n and m returning another 16-bit number making a sum in O(1); a simple Turing machine using unary notation cannot sum into a constant term of operations, but would need to do O(n + m) operations to complete processing.

So, yeah, soma(n, m) = O(1) on x86 processors with n and m with a fixed size of 16 bits, but soma(n, m) = O(n + m) on a Turing machine. (Note: on the Turing machine, we are not limiting the values of n nor of m).

Normally, basic arithmetic operations (such as summation, multiplication and division) with palpable numbers are done in fixed habitation and we consider them with weight O(1).

Small informal demonstration: b is therefore constant O(1); n is limited by b digits, having size O(b); the sum using binary numbers is done by analyzing each bit of n and of m a single time, together with an auxiliary variable, so the Turing machine will pass through all the bits only once, so O(tamanho(n) + tamanho(m)); as the size is limited to O(b), O(tamanho(n) + tamanho(m)) = O(O(b) + O(b)) = O(b + b) = O(2b) = O(b) = O(1).

In nonrecursive cases (either directly or indirectly), simply check the loops to see how many operations are done. Let’s look at your case:

function somaMatriz(matA, size)
  let soma = 0;
  for(let i = 0; i < size; i++) {
    for(let j = 0; j < size; j++) {
      soma += a[i][j];
    }
  }
  return soma;
}

We can unlock that function in two and have the same asymptotic behavior:

function somaLinha(matA, size, i) {
  let soma = 0;
  for(let j = 0; j < size; j++) {
    soma += a[i][j];
  }
  return soma;
}

function somaMatriz(matA, size) {
  let soma = 0;
  for(let i = 0; i < size; i++) {
    soma += somaLinha(matA, size, i);
  }
  return soma;
}

Does that mean that somaMatriz(matA,size) will do O(size*O(somaLinha(matA,size,i)) + 2) operations; size * O(somaLinha(matA,size,i)) because the outer loop repeats size times (i of [0,size) is O(size)) doing the operation somaLinha(matA,size,i) each time. The 2 which appears is due to return and initialization, but we know that any growing function dominates a constant value, so this 2 ends up leaving the signature.

Very well, to set now the cost of somaLinha(matA,size,i) we can divide it into two parts (similarly to what we have just done with somaMatriz(matA, size)):

function operador_soma(a, b) {
  return a + b;
}

function somaLinha(matA, size, i) {
  let soma = 0;
  for(let j = 0; j < size; j++) {
    soma = operador_soma(soma, a[i][j]);
  }
  return soma;
}

Similarly, somaLinha(matA, size, i) will do O(size) operations of operador_soma. operador_soma is only a simple sum of reasonable numbers and fixed dwelling, then executes in O(1). Therefore, somaLinha(matA, size, i) = O(size * operador_soma) = O(size * O(1)) = O(size).

Replacing this result in the asymptotic behavior of somaMatriz, we have to O(simaMatriz,matA,size) = O(size*O(somaLinha(matA,size,i)) + 2) = O(size*O(O(size)) + 2) = O(size * size) = O(size ^2).


When the loop does not have a very 100% size defined in an obvious way, the analysis is a little more complicated.

Take as an example the function that makes the union between two ordered sets (based on the function of that answer:

entrada:
  A, conjunto ordenado de elementos do tipo E
  B, conjunto ordenado de elementos do tipo E
  cmp, função sinal que compara dois elementos de E
retorno:
  C, conjunto de elementos do tipo E oriundo da união de A e B

começo
  i <- 0 # índice para iterar em A
  j <- 0 # índice para iterar em B
  C <- []
  ultimo_elemento_adicionado <- null

  enquanto i < A.tamanho && j < B.tamanho:
    s = cmp(A[i], B[j])
    se s == '0':
      # elementos são iguais, um deles como elemento candidato
      candidato <- A[i]
      i <- i + 1
      j <- j + 1
    senão, se s == '-':
      # A[i] < B[j], então próxima comparação será com A[i + 1] e B[j]; A[i] agora é candidato
      candidato <- A[i]
      i <- i + 1
    senão # caso trivial onde s == '+':
      # A[i] > B[j], então próxima comparação será com A[i] e B[j + 1]; B[j] agora é candidato
      candidato <- B[j]
      j <- j + 1
    # agora vamos ver se o candidato deve ser inserido em C: precisa ser distinto do último elemento adicionado, ou ser o primeiro elemento adicionado
    se ultimo_elemento_adicionado != null && cmp(candidato, ultimo_elemento_adicionado) != '0':
        ultimo_elemento_adicionado = candidato
        C.push(candidato)
  # caso i ou j extrapolem o tamanho de A ou B, respectivamente, não há mais comparações a se fazer
  retorna C
fim

Whereas the operations of cmp are performed in constant time O(1) and that adding an element at the end of an indexed set is also done in O(1).

The stopping condition is when i reaches the size of A or when j reaches the size of B. For loop iteration, we obtain that i or j (or inclusive) are increased. This means that, to stop by the condition of i >= A.tamanho, will be necessary A.tamanho increment operations in i; meanwhile, to stop by the condition of j >= B.tamanho, will be necessary B.tamanho increment operations in j. As each iteration I ensure there is at least one increment, the maximum iterations made is (A.tamanho - 1) + (B.tamanho - 1) + 1, so this bond needs O(A.tamanho + B.tamanho) operations to be executed.


In the case of merge Sort, we have a recursive function. Your analysis is even more complicated, so I won’t worry about her formalism, but about passing on the general idea.

Reviewing the merge Sort:

mergeSort(E[] entradaDesordenada, E[] areaTrabalho, int ini, int end) {
    if (end - ini <= 1) {
        return;
    }

    int middle = (ini + end) / 2;
    mergeSort(areaTrabalho, entradaDesordenada, ini, middle);
    mergeSort(areaTrabalho, entradaDesordenada, middle, end);
    merge(entradaDesordenada, areaTrabalho, ini, middle, end);
}

The idea of merge is to join two ordered sets into a third ordered set. The bulk of the algorithm is very similar to the union algorithm I presented earlier, including the same behavior. Therefore, merge(A) = O(tamanho(A)); if we call tamanho(A) = n, we have to the complexity of merge = O(n).

So all that remains is to define how many times merge will be called. A recursion mergeSort is made so that, in recursive call, only half of the elements are passed. So the depth of recursive calls is log2(n); as log2(n) = log(n) * log2(10), we can say that the depth is O(log n). For each recursion level, mergeSort will be called twice, each call with n/2 elements; then each level will call merge for n/2 elements 2 times.

The general idea can be seen in this image:

merge sort pelo link http://rosalind.info/problems/ms/

Note that in each level we run several times the merge (an execution for each node of the tree). In all, the amount of merging elements, at the same level of recursion depth, is n. So on the whole we have to be done O(profundidade da recursão * n) operations. As the depth is O(log n), the quantity of operations is O(n log n).


What is the relevance of this in practical life? Well, I did an experiment to answer a question about sorting algorithm performance. You can check the results in response. The difference between a time complexity algorithm O(n log n) and O(n^2) up to two orders of magnitude in total time for an input with n=100.000

  • The @LINQ showed me that the formal name for what I called set signature is a limiting function; later I update the text with this correction

11

The notation Θ

The question is always to verify what is the term of the equation that dominates. For this, we use the notation Theta (reads "Téta").

A function f(n) belongs to a class Theta(k(n)) if, and only if:

equação

That is, the function f(n) belongs to a class Theta(k(n)) if, and only if, from a given value n_0, the result of f(n) is always greater than or equal to the value of k(n) mutilated by some constant a greater than zero and less than or equal to the value of k(n) mutilated by some other constant b greater than a.

For example, be f(n)=5n²+3n. We have to f(n) in Theta(n²) because for all the value of n >= 3, we find that 5n²<=5n²+3n<=6n². In this case, we have to n0 = 3, a = 5, b = 6. To prove it, we can look at the table:

╔═══╦═════╦══════════╦═════╗
║ n ║ 5n² ║ 5n² + 3n ║ 6n² ║
╠═══╬═════╬══════════╬═════╣
║ 1 ║   5 ║        8 ║   6 ║
║ 2 ║  20 ║       26 ║  24 ║
║ 3 ║  45 ║       54 ║  54 ║
║ 4 ║  80 ║       92 ║  96 ║
║ 5 ║ 125 ║      140 ║ 150 ║
║ 6 ║ 180 ║      198 ║ 216 ║
╚═══╩═════╩══════════╩═════╝

In this table we see that for all n >= 3, the value of 5n² + 3n is between the value of 5n² and of 6n². This shows that f in Theta(n²).

How can we find the values of n0, a and b? Any numbers a and b such that 0 < a <= 5 < b would serve. The 5 is the coefficient of the term of higher degree. We could use to a and b any values satisfying that inequality, such as 5 and 5,0001, 3 and 10, 0,0001 and 10000. But what I did was choose the coefficient of the term of higher degree (5) for the a and add 1 to it to get the b. With this we can calculate the n_0 as follows:

6n0^2 = 5n0^2 + 3n0
n0^2 = 3n0
n0 = 3

The general formula is this:

b.k(n0) = f(n0)

Obviously this depends on the choice of the value of b.

But so far, we have assumed that we already knew the class was Theta(n²)). Suppose we tried with other classes:

╔═════╦══════════╦══════╦════════╗
║   n ║ 5n² + 3n ║ 6n   ║  1000n ║
╠═════╬══════════╬══════╬════════╣
║   1 ║        8 ║    6 ║   1000 ║
║   2 ║       26 ║   12 ║   2000 ║
║   3 ║       54 ║   18 ║   3000 ║
║   4 ║       92 ║   24 ║   4000 ║
║   5 ║      140 ║   30 ║   5000 ║
║   6 ║      198 ║   36 ║   6000 ║
║ ... ║      ... ║  ... ║    ... ║
║ 199 ║   198602 ║ 1194 ║ 199000 ║
║ 200 ║   200600 ║ 1200 ║ 200000 ║
║ 201 ║   202608 ║ 1206 ║ 201000 ║
╚═════╩══════════╩══════╩════════╝

Note that the value of 5n²+3n always goes beyond the 6n. Even if we use a huge coefficient and do 1000n, we have to for any values of n >= 200, the value of 5n²+3n is bigger. This means that 5n²+3n exceeds the value of any linear function (even if the coefficient used is very large), given a value of n large enough.

On the other hand, if we try with a higher degree function:

╔══════╦═══════════╦═══════════════╗
║    n ║  5n² + 3n ║       0.001n³ ║
╠══════╬═══════════╬═══════════════╣
║    1 ║         8 ║         0.001 ║
║    2 ║        26 ║         0.008 ║
║    3 ║        54 ║         0.027 ║
║    4 ║        92 ║         0.064 ║
║    5 ║       140 ║         0.125 ║
║    6 ║       198 ║         0.216 ║
║  ... ║       ... ║           ... ║
║ 5000 ║ 125015000 ║ 125000000.000 ║
║ 5001 ║ 125065008 ║ 125075015.001 ║
╚══════╩═══════════╩═══════════════╝

We have the opposite of the previous case, for any values of n >= 5001, the value of 5n²+3n is smaller. This means that 5n²+3n has its value exceeded by any cubic function (even if the coefficient used is very small), given a value of n large enough.

As you may have noticed, lower grade terms and coefficients matter little in the end. If the class k has been chosen for any function f such that f(n) in Theta(k(n)), then any coefficients a and b appropriate terms used will serve and the lesser terms will become irrelevant. If the class chosen is inadequate, then there will be no terms a and b which satisfy the equation of Theta of the beginning of this answer.

With this, we have that all quadratic functions are in the class Theta(n²). All linears are in the class Theta(n²). All cubics are in class Theta(n³). All logarithms are in the class Theta(log n). All exponentials are in the class Theta(2^n).

In general classes follow a hierarchy similar to this:

classes de complexidade mais comuns

These are the most common complexity classes.

What about the "asymptotic"?

Asymptotic behavior means the behavior of a function tending to infinity. That is, if we consider n with a very large value. As already demonstrated above, when this occurs, the terms of lesser degree become negligible.

What about O and Ω?

Let’s go over the equation of Theta:

equação

Let’s focus on that part:

inequação

This could be divided into two inequalities:

inequação
inequação

And then we’d have this:

equação
equação

And as a consequence:

equação

The notation O (reads "oh great") is very popular, more popular than the Theta inclusive. The O represents the upper limit of a function, but this limit does not need to be exact. That is, it is something that the function never exceeds given a value of n big enough. So, if you have any function d such that d in O(n²), then you could say that d is quadratic or lower.

Already the Omega (reads "big omega") is the lower limit (that is, something that is never exceeded down), which also does not need to be exact. So if you have any function d such that d in Omega(n²), then you could say that d is quadratic or higher.

When the upper and lower limit coincide (even with different coefficients), then we have the Theta. So if you have any function d such that d in Theta(n²), then you could say that d is quadratic, no more and no less. You could also say that d in O(n²) and d in Omega(n²) at the same time.

That is, if s(n) = n², we have to:

s in Omega(n)  |  s in Theta(n)  |  s in O(n)
s in Omega(n²)  |  s in Theta(n²)  |  s in O(n²)
s in Omega(n³)  |  s in Theta(n³)  |  s in O(n³)

What about o and Ω?

Whereas the O indicates an upper limit for the function, the o (read "O little one") represents what is beyond that limit. Likewise, the Omega represents the lower limit, while omega (read "small omega") represents what is below that limit. Soon:

s in omega(n)  |  s in o(n)
s in omega(n²)  |  s in o(n²)
s in omega(n³)  |  s in o(n³)

This is because s, which is quadratic, is beyond linear, so s in omega(n). But it is below the cubic, and therefore s in o(n³).

Notation with =

Often you see people using h = Theta(n²) instead of h in Theta(n²). Strictly speaking this is an abuse of the notation of "=", but it is a common abuse that everyone does, including in highly regarded academic articles. The right thing would be to use the in since the Theta(n²) represents the set of functions that are quadratic and what is being observed is not equality between sets but the belonging of a function to a given set of functions.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.