(exemplo 1)
T(n)=9T(3n)+n
- a=9,b=3,d=1,a>bd(9>31)
- Caso 3
- Pelo teorema mestre, T(n)=Θ(nlog39)=Θ(n2)
(exemplo 2)
T(n)=T(32n)+1
- a=1,b=3/2,d=0,a=bd(1=(3/2)0)
- Caso 1
- Pelo teorema mestre, T(n)=Θ(ndlog(n))=Θ(n0logn)=Θ(logn)
(exemplo 3)
T(n)=2T(2n)+cn2
- a=2,b=2,d=2,a<bd(2>22)
- Caso 2
- Pelo teorema mestre, T(n)=Θ(nd)=Θ(n2)
(exemplo 4)
T(n)=8T(2n)+cn2
- a=8,b=2,d=2,a>bd(8>22)
- Caso 3
- Pelo teorema mestre, T(n)=Θ(nlog26)=Θ(n3)
(exemplo 5)
T(n)=3T(4n)+nlogn
- a=3,b=4,f(n)=nlogn
- nlogba=nlog43
- Além disso f(n)=Ω(nlog43+ϵ) e af(bn)=34nlog(4n)≤(43)nlogn
- Caso 3
- Pelo teorema mestre, T(n)=Θ(nlog(n))
(exemplo 6)
T(n)=2T(2n)+nlogn
- a=2,b=2,f(n)=nlogn
- nlogba=nlog22=n
- Caso 3?
- f(n)=Ω(n1+ϵ), já que
nlognnlognlogn≥n1+ϵ≥n⋅nϵ≥nϵ
- 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
- Um dos algoritmos de ordenação mais usados na prática
- Não precisa de memória extra
- Pior caso O(n2), mas executa em O(nlogn) no caso médio
-
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
- Cada etapa de partição pode ser executada em tempo linear O(n)
- Não precisa de memória extra
- Reduz o tamanho de problema
- 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
- Usa duas variáveis auxiliares
- j: elementos vistos até o momento
- i: elementos em A[inicio..j] que são menores que o pivô
- Tempo de execução = O(n), em que n é o tamanho do (sub)vetor
- Custo O(1) para cada elemento do (sub)vetor
- Claramente não usa memória extra (inplace), poi faz trocas repetidas no mesmo (sub)vetor
- O laço mantém as invariantes:
- Todos os elementos de A[inicio..i−1] são menores que o pivô p
- Todos os elementos de A[i..j] são maiores que p
- Ao final do laço temos A[inicio..i−1] menor que p e A[i..fim−1] maior que p
- Trocar A[fim] com A[i] coloca o último elemento na posição correta, e não altera o invariante já que A[i] é maior que p.
- Ao final da execução, o (sub)vetor está pariticionado ao redor de p.
- 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=1 o vetor está naturalmente ordenado
- Hipótese: Supunha que QuickSort funciona para um vetor 1<m<ni elementos, em que ni é o número de elmentos em A[inicio..fim]
- Passo de indução: seja xi a posição do pivô ao final de Praticiona. O elemento em A[xi] estará na posição correta, e temos dois subvetores A[inicio..xi−1] e A[xi+1..fim], que são (recursivamente) ordenados por QuickSort (que pela Hipótese de indução, estão corretamente ordenados).
- Portanto A[inicio..xi−1,xi,xi+1..fim] estará ordenado.
- A[1..x0−1,x0,x0−1..n] em que x0 é o primeiro pivô e inicio e fim corresponde ao vetor completo
- 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 0 e n−1. Nesse caso, a equação de recorrência é:
T(n)=T(n−1)+n=T(n−2)+n+(n−1)⋮=T(1)+i=2∑n−1i=1+2(n+1)(n−2)=Θ(n2)
- No melhor caso, EscolhePivo sempre retorna a mediana , de maneira que temos dois subvetores de tamanho n/2. Nesse caso, a equação de recorrência é:
T(n)=2T(2n)+n=Θ(nlogn)
-
Em um caso intermediário, EscolhePivo divide o vetor original em duas partes de tamanho pn e (1−p)n, em que 0<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((1−p)n)+n
-
Seja k=p−1, podemos reescrever a recorrência como
T(n)=T(kn)+T(kk−1n)+n
- Nesse caso, podemos provar que T(n)=O(nlogn)
T(n)=T(n/k)+T((k−1)n/k)+n≤c(knlog(kn))+kn+c(k(k−1)nlog(k(k−1)n))+k(k−1)n+n=c(knlog(kn))+c(k(k−1)n(log(k−1)+log(kn)))+2n=cnlogn+n−cnlogk+(kc(k−1)nlog(k−1)+n)≤cnlogn+n
que é válida se c>k/(logk) pois nesse caso temos:
cnlogk≥(kc(k−1)nlog(k−1)+n)
- Portanto, temos que QuickSort é O(nlogn) se garantirmos que Particiona divide o vetor A de tamanhos aproximadamente n/k e (k−1)n/k
- Apesar de contraintuivo, podemos observar que o tamanho da árvore de recursão é logk/(k−1)nn=Θ(logn), e em cada passo executamos algo proporcional a n
- Qualquer divisão que não deixe um subvetor vazio já seria boa o suficiente (assintoticamente falando)
- 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! possíveis permutações dos elementos de A tem a mesma chance de ser a ordenação do vetor de entrada A.
- Seja Xa,b uma variável aleatória que que indica se o elemento xa é comparado com o elemento xb
Xa,b={10se xa eˊ comparado com xjse xa na˜o eˊ comparado com xjd
- O número total de comparações é:
a=1∑n−1b=a+1∑nXa,b
- O valor médio esperado E[Xa,b] dá uma estimativa do número médio esperado de comparações de Xa,b
E[Xa,b]=E[a=1∑n−1b=a+1∑nXa,b]=a=1∑n−1b=a+1∑nE[Xa,b]=a=1∑n−1b=a+1∑n(Pa,b=1)×1+(Pa,b=0)×0=a=1∑n−1b=a+1∑n(Pa,b=1)
-
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.
-
Seja o1,o2,⋯,on os elementos de A em ordem (depois de ordenados), ou seja, o1≤o2≤⋯≤on
-
Dados dois elementos quaisquer oa e ob, a<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.
- Seja Pa,b=1 a probabilidade de comparar os elementos oa e ob.
- Dado o subvetor O[a..b]=[oa,oa+1,⋯,ob], Pa,b é igual a probabilidade de oa ou ob sejam escolhidos como pivô.
- Se algum outro elemento oi, a<k<b for escolhido como pivô, então oa e ob vão para partes diferentes no final de Particiona com oi pivô, e nunca serão comparados
- Se qualquer elemento de O[a..b] tiver igual probabilidade de ser selecionado como pivô, como temos b−a+1 elementos em O[a..b], temos que
(Pa,b=1)=b−a+12
- Substituindo na equação do valor experado temos que:
E[Xa,b]=a=1∑n−1b=a+1∑n(Pa,b=1)=a=1∑n−1b=a+1∑nb−a+12
-
O valor médio esperado E[Xa,b] dá uma estimativa do número médio esperado de comparações Xa,b (se todas as permutações são igualmente prováveis)
-
Portanto, no caso médio:
T(n)=O(E(Xa,b))≤ca=1∑n−1b=a+1∑nb−a+12<2cnk=i∑nk1
-
É possivel provar que ∑k=ink1 é O(logn), poranto T(n)=O(nlogn)
-
Portanto, concluímos que o tempo medio de execução do Quicksort ́e O(nlogn).
-
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).
-
Assim, sem supor nada sobre a entrada do algoritmo, garantimos um tempo de execução esperado de O(nlogn).