Análise de Algoritmos

Ronaldo Cristiano Prati

Bloco A, sala 513-2

ronaldo.prati@ufabc.edu.br

Método mestre (simplificado)

(exemplo 1)

T(n)=9T(n3)+nT(n) = 9T\left(\frac{n}{3}\right) + n

  • a=9,b=3,d=1,a>bd(9>31)a=9, b=3, d=1, a > b^d (9 > 3^1)
  • Caso 3
  • Pelo teorema mestre, T(n)=Θ(nlog39)=Θ(n2)T(n) = \Theta(n^{\log_{3} 9} ) = \Theta(n^2)

Método mestre (simplificado)

(exemplo 2)

T(n)=T(2n3)+1T(n) = T\left(\frac{2n}{3}\right) + 1

  • a=1,b=3/2,d=0,a=bd(1=(3/2)0)a=1, b=3/2, d=0, a = b^d (1 = (3/2)^0)
  • Caso 1
  • Pelo teorema mestre, T(n)=Θ(ndlog(n))=Θ(n0logn)=Θ(logn)T(n) = \Theta(n^d\log (n) ) = \Theta(n^0\log n) = \Theta(\log n)

Método mestre (simplificado)

(exemplo 3)

T(n)=2T(n2)+cn2T(n) = 2T\left(\frac{n}{2}\right) + c n^2

  • a=2,b=2,d=2,a<bd(2>22)a=2, b=2, d=2, a < b^d (2 > 2^2)
  • Caso 2
  • Pelo teorema mestre, T(n)=Θ(nd)=Θ(n2)T(n) = \Theta(n^d ) = \Theta(n^2)

Método mestre (simplificado)

(exemplo 4)

T(n)=8T(n2)+cn2T(n) = 8T\left(\frac{n}{2}\right) + c n^2

  • a=8,b=2,d=2,a>bd(8>22)a=8, b=2, d=2, a > b^d (8 > 2^2)
  • Caso 3
  • Pelo teorema mestre, T(n)=Θ(nlog26)=Θ(n3)T(n) = \Theta(n^{\log_{2} 6} ) = \Theta(n^3)

Método mestre (geral)

(exemplo 5)

T(n)=3T(n4)+nlognT(n) = 3T\left(\frac{n}{4}\right) + n \log n

  • a=3,b=4,f(n)=nlogna=3, b=4, f(n) = n \log n
  • nlogba=nlog43n^{\log_b a} = n^{\log_4 3}
  • Além disso f(n)=Ω(nlog43+ϵ)f(n) = \Omega(n^{\log_4 3+\epsilon}) e af(nb)=3n4log(n4)(34)nlogna f\left(\frac{n}{b}\right) = 3\frac{n}{4}\log \left(\frac{n}{4}\right) \leq \left(\frac{3}{4}\right)n \log n
  • Caso 3
  • Pelo teorema mestre, T(n)=Θ(nlog(n))T(n) = \Theta(n\log (n) )

Método mestre (geral)

(exemplo 6)

T(n)=2T(n2)+nlognT(n) = 2T\left(\frac{n}{2}\right) + n \log n

  • a=2,b=2,f(n)=nlogna=2, b=2, f(n) = n \log n
  • nlogba=nlog22=nn^{\log_b a} = n^{\log_2 2} = n
  • Caso 3?
  • f(n)Ω(n1+ϵ)f(n) \ne \Omega(n^{1+\epsilon}), já que
    nlognn1+ϵnlognnnϵlognnϵ\begin{aligned} n \log n & \ge n^{1 + \epsilon} \\ n \log n & \ge n\cdot n^{\epsilon} \\ \log n & \ge n^{\epsilon} \end{aligned}
  • Que como visto na aula passada é impossível (contradição). Nesse caso o teorema mestre não é aplicável e temos que resolver por meio de outro método

Quicksort

  • Um dos algoritmos de ordenação mais usados na prática
  • Não precisa de memória extra
  • Pior caso O(n2)O(n^2), mas executa em O(nlogn)O(n \log n) no caso médio

Quicksort

  • Ideia principal: particiona o vetor em torno de um elemento pivô.

  • Usa um elemento do vetor como pivô

  • Reorganiza o vetor de ta maneira que:

    • elementos à esquerda: menores que o pivô
    • elementos a direita: maiores que o pivô
  • O pivô fica em sua posição correta

Quicksort

Quicksort

Propriedades Subrotina Particiona

  • Cada etapa de partição pode ser executada em tempo linear O(n)O(n)
  • Não precisa de memória extra
  • Reduz o tamanho de problema

Subrotina Particiona

  • Assuma que o pivô é o último elemento do (sub)vetor
    • Se não for, troque o pivô com o último elmento
  • Ideia (alto nível):
    • Faz uma passada pelo vetor
    • invariante: os elementos vistos até o momento já estão particionados

Subrotina Particiona

  • Usa duas variáveis auxiliares
    • jj: elementos vistos até o momento
    • ii: elementos em A[inicio..j]A[inicio..j] que são menores que o pivô

Subrotina Particiona

Subrotina Particiona

Subrotina Particiona

  • Tempo de execução = O(n)O(n), em que nn é o tamanho do (sub)vetor
  • Custo O(1)O(1) para cada elemento do (sub)vetor
  • Claramente não usa memória extra (inplace), poi faz trocas repetidas no mesmo (sub)vetor

Corretude Particiona

  • O laço mantém as invariantes:
    1. Todos os elementos de A[inicio..i1]A[inicio..i-1] são menores que o pivô pp
    2. Todos os elementos de A[i..j]A[i..j] são maiores que pp
  • Ao final do laço temos A[inicio..i1]A[inicio..i-1] menor que pp e A[i..fim1]A[i..fim-1] maior que pp
  • Trocar A[fim]A[fim] com A[i]A[i] coloca o último elemento na posição correta, e não altera o invariante já que A[i]A[i] é maior que pp.
  • Ao final da execução, o (sub)vetor está pariticionado ao redor de pp.

Corretude Quicksort

  • Observe que A escolha do pivô não altera o funcionamento do algoritmo
  • É possível usar o fato da corretude do usando indução
    • Caso base: quando ni=1n_i=1 o vetor está naturalmente ordenado
    • Hipótese: Supunha que QuickSort funciona para um vetor 1<m<ni1< m<n_i elementos, em que nin_i é o número de elmentos em A[inicio..fim]A[inicio..fim]
    • Passo de indução: seja xix_i a posição do pivô ao final de Praticiona. O elemento em A[xi]A[x_i] estará na posição correta, e temos dois subvetores A[inicio..xi1]A[inicio..x_i-1] e A[xi+1..fim]A[x_i+1..fim], que são (recursivamente) ordenados por QuickSort (que pela Hipótese de indução, estão corretamente ordenados).
    • Portanto A[inicio..xi1,xi,xi+1..fim]A[inicio..x_i-1,x_i,x_i+1..fim] estará ordenado.
    • A[1..x01,x0,x01..n]A[1..x_0-1,x_0,x_0-1..n] em que x0x_0 é o primeiro pivô e inicioinicio e fimfim corresponde ao vetor completo

Complexidade Quicksort

  • A análise do QuickSort depende da escolha do pivô.
  • No pior caso, EscolhePivo sempre retorna o maior (ou menor) elemento, de maneira que temos dois subvetores de tamanho 00 e n1n-1. Nesse caso, a equação de recorrência é:
    T(n)=T(n1)+n=T(n2)+n+(n1)=T(1)+i=2n1i=1+(n+1)(n2)2=Θ(n2)\begin{aligned} T(n) &=T(n-1)+n \\ &=T(n-2)+n+(n-1) \\ & \vdots \\ &=T(1)+\sum_{i=2}^{n-1} i \\ &=1+\frac{(n+1)(n-2)}{2} \\ &=\Theta\left(n^{2}\right) \end{aligned}

Complexidade Quicksort

  • No melhor caso, EscolhePivo sempre retorna a mediana , de maneira que temos dois subvetores de tamanho n/2n/2. Nesse caso, a equação de recorrência é:
    T(n)=2T(n2)+n=Θ(nlogn)\begin{aligned} T(n) &= 2T\left(\frac{n}{2}\right)+n \\ &=\Theta\left(n \log n\right) \end{aligned}

Complexidade Quicksort

  • Em um caso intermediário, EscolhePivo divide o vetor original em duas partes de tamanho pnpn e (1p)n(1-p) n, em que 0<p<10 < p < 1 é a proporção de elementos em uma das partições.

  • Nesse caso, a equação de recorrência é:
    T(n)=T(pn)+T((1p)n)+nT(n) = T\left(pn\right)+T\left((1-p)n\right)+n

  • Seja k=p1k=p^{-1}, podemos reescrever a recorrência como
    T(n)=T(nk)+T(k1kn)+nT(n) = T\left(\frac{n}{k}\right)+T\left(\frac{k-1}{k}n\right)+n

Complexidade Quicksort

  • Nesse caso, podemos provar que T(n)=O(nlogn)T(n) = O(n \log n)

T(n)=T(n/k)+T((k1)n/k)+nc(nklog(nk))+nk+c((k1)nklog((k1)nk))+(k1)nk+n=c(nklog(nk))+c((k1)nk(log(k1)+log(nk)))+2n=cnlogn+ncnlogk+(c(k1)nklog(k1)+n)cnlogn+n\begin{aligned} T(n) &=T(n / k)+T((k-1) n / k)+n \\ & \leq c\left(\frac{n}{k} \log \left(\frac{n}{k}\right)\right)+\frac{n}{k}+c\left(\frac{(k-1) n}{k} \log \left(\frac{(k-1) n}{k}\right)\right)+\frac{(k-1) n}{k}+n \\ &=c\left(\frac{n}{k} \log \left(\frac{n}{k}\right)\right)+c\left(\frac{(k-1) n}{k}\left(\log (k-1)+\log \left(\frac{n}{k}\right)\right)\right)+2 n \\ &=c n \log n+n-c n \log k+\left(\frac{c(k-1) n}{k} \log (k-1)+n\right) \\ & \leq c n \log n+n \end{aligned}

que é válida se c>k/(logk)c > k/(\log k) pois nesse caso temos:

cnlogk(c(k1)nklog(k1)+n)c n \log k \geq\left(\frac{c(k-1) n}{k} \log (k-1)+n\right)

Complexidade Quicksort

  • Portanto, temos que QuickSort é O(nlogn)O(n \log n) se garantirmos que Particiona divide o vetor AA de tamanhos aproximadamente n/kn/k e (k1)n/k(k-1)n/k
  • Apesar de contraintuivo, podemos observar que o tamanho da árvore de recursão é logk/(k1)nn=Θ(logn)\log_{k/(k-1) n} n = \Theta(\log n), e em cada passo executamos algo proporcional a nn
  • Qualquer divisão que não deixe um subvetor vazio já seria boa o suficiente (assintoticamente falando)

Complexidade Quicksort

  • O problema dessa análise ́e que ́e improvável que a partição
    seja sempre feita da mesma maneira em todas as chamadas recursivas.
  • Vamos fazer uma análise probabilistica do algoritmo
  • Para isso, vamos analisar o que acontece no caso medio, em que cada uma das n!n! possíveis permutações dos elementos de AA tem a mesma chance de ser a ordenação do vetor de entrada AA.

Complexidade Quicksort

  • Seja Xa,bX_{a,b} uma variável aleatória que que indica se o elemento xax_a é comparado com o elemento xbx_b
    Xa,b={1se xa eˊ comparado com xj0se xa na˜eˊ comparado com xjdX_{a,b}=\begin{cases} 1 &\text{se $x_a$ é comparado com $x_j$} \\ 0 &\text{se $x_a$ não é comparado com $x_j$} d \end{cases}
  • O número total de comparações é:

a=1n1b=a+1nXa,b\sum_{a=1}^{n-1}\sum_{b=a+1}^{n} X_{a,b}

Complexidade Quicksort

  • O valor médio esperado E[Xa,b]E[X_{a,b}] dá uma estimativa do número médio esperado de comparações de Xa,bX_{a,b}

E[Xa,b]=E[a=1n1b=a+1nXa,b]=a=1n1b=a+1nE[Xa,b]=a=1n1b=a+1n(Pa,b=1)×1+(Pa,b=0)×0=a=1n1b=a+1n(Pa,b=1)\begin{aligned}E[X_{a,b}] & = E\left[\sum_{a=1}^{n-1}\sum_{b=a+1}^{n} X_{a,b}\right]\\ & = \sum_{a=1}^{n-1}\sum_{b=a+1}^{n} E\left[ X_{a,b}\right]\\ & = \sum_{a=1}^{n-1}\sum_{b=a+1}^{n} (P_{a,b} = 1)\times 1 + (P_{a,b} = 0)\times 0 \\ & = \sum_{a=1}^{n-1}\sum_{b=a+1}^{n} (P_{a,b} = 1) \end{aligned}

Complexidade Quicksort

  • Recorde que no Quicksort, os elmentos são comparados apenas com o pivô. Após uma rodada de Particiona, o pivô migra para a posição final e não é mais comparado com ninguém.

  • Portanto, podemos analisar a probabilidade de comparar dois elementos considerando as suas posições finais, relativamente à probabilidade de um elmento virar pivô em um determinado instante.

Complexidade Quicksort

  • Seja o1,o2,,ono_1, o_2, \cdots, o_n os elementos de AA em ordem (depois de ordenados), ou seja, o1o2ono_1 \leq o_2 \leq \cdots \leq o_n

  • Dados dois elementos quaisquer oao_a e obo_b, a<ba<b eles são comparados (no máximo) uma única vez pois quando (qualquer) um deles for pivô.

  • Ele é colocado na posição correta, e o outro estará a sua direita ou a sua esquerda, e o pivô não é comparado com mais ninguém.

Complexidade Quicksort

  • Seja Pa,b=1P_{a,b}=1 a probabilidade de comparar os elementos oao_a e obo_b.
  • Dado o subvetor O[a..b]=[oa,oa+1,,ob]O[a..b] = [o_a,o_{a+1},\cdots,o_b], Pa,bP_{a,b} é igual a probabilidade de oao_a ou obo_b sejam escolhidos como pivô.
    • Se algum outro elemento oio_i, a<k<ba<k<b for escolhido como pivô, então oao_a e obo_b vão para partes diferentes no final de Particiona com oio_i pivô, e nunca serão comparados
  • Se qualquer elemento de O[a..b]O[a..b] tiver igual probabilidade de ser selecionado como pivô, como temos ba+1b-a+1 elementos em O[a..b]O[a..b], temos que

(Pa,b=1)=2ba+1(P_{a,b}=1) = \frac{2}{b-a+1}

Complexidade Quicksort

  • Substituindo na equação do valor experado temos que:

E[Xa,b]=a=1n1b=a+1n(Pa,b=1)=a=1n1b=a+1n2ba+1\begin{aligned}E[X_{a,b}] & = \sum_{a=1}^{n-1}\sum_{b=a+1}^{n} (P_{a,b} = 1) \\ & = \sum_{a=1}^{n-1}\sum_{b=a+1}^{n} \frac{2}{b-a+1} \end{aligned}

Complexidade Quicksort

  • O valor médio esperado E[Xa,b]E[X_{a,b}] dá uma estimativa do número médio esperado de comparações Xa,bX_{a,b} (se todas as permutações são igualmente prováveis)

  • Portanto, no caso médio:

    T(n)=O(E(Xa,b))ca=1n1b=a+1n2ba+1<2cnk=in1k\begin{aligned}T(n) & = O(E(X_{a,b})) \\ & \leq c \sum_{a=1}^{n-1}\sum_{b=a+1}^{n} \frac{2}{b-a+1} \\ & < 2c n \sum_{k=i}^{n} \frac{1}{k} \\ \end{aligned}

  • É possivel provar que k=in1k\sum_{k=i}^{n} \frac{1}{k} é O(logn)O(\log n), poranto T(n)=O(nlogn)T(n) = O(n \log n)

Complexidade Quicksort

  • Portanto, concluímos que o tempo medio de execução do Quicksort ́e O(nlogn)O(n \log n).

  • Se, em vez de escolhermos um elemento fixo para ser o pivô, escolhermos um dos elementos do (sub)vetor com probabilidade uniforme, então uma análise análoga a que fizemos aqui mostra que o tempo esperado de execução dessa versão aleatória de Quicksort ́e O(nlogn)O(n \log n).

  • Assim, sem supor nada sobre a entrada do algoritmo, garantimos um tempo de execução esperado de O(nlogn)O(n \log n).