Um dos algoritmos de ordenação mais populares é o QuickSort. É um método do tipo “dividir para conquistar”, que escolhe repetidamente um elemento pivô do array, separa o array em três partes – elementos menores, iguais e maiores do que o pivô – e repete esse processo até que todo o array esteja ordenado.
Então, a cada iteração:
Escolhe-se um elemento pivô do array
Movem-se os elementos menores do que o pivô para a parte esquerda
Movem-se os elementos maiores do que o pivô para a parte direita
Reúnem-se todos os elementos iguais ao pivô no meio
Repete-se esse processo tanto para a parte esquerda quanto para a parte direita
def quicksort(a):
if len(a) <= 1:
return a
pivot = a[len(a) // 2]
left = [x for x in a if x < pivot]
middle = [x for x in a if x == pivot]
right = [x for x in a if x > pivot]
return quicksort(left) + middle + quicksort(right)
Nesta implementação do QuickSort, são usados arranjos adicionais para guardar as partes left, middle e right. É possível fazê-lo funcionar no próprio array, utilizando índices e rearranjando os elementos diretamente nele.
def quicksort(a, start, end):
if start >= end: # Não há elementos para ordenar
return
pivot = a[start] # Escolhe um pivô
l, r = start + 1, end # Lida com a faixa a[start+1 ... end]
while l <= r: # Enquanto houver elementos nessa faixa
if a[l] < pivot: # se a[l] < pivot => deixa a[l] como está
l += 1 # Avança o ponteiro da esquerda
else: # caso contrário, move a[l] para o fim
a[l], a[r] = a[r], a[l] # Troca a[l] com a[r] -> movendo para o fim
r -= 1 # Reduz o ponteiro da direita
a[start], a[r] = a[r], a[start] # Troca o pivô com a[r]
quicksort(a, start, r - 1) # Ordena a faixa a[start ... r-1]
quicksort(a, r + 1, end) # Ordena a faixa a[r+1 ... end]
Talvez tenha notado que a escolha do pivot varia nessas duas implementações. O algoritmo QuickSort não impõe uma forma fixa de escolher o pivô. Existem várias opções para fazer isso. Aqui vão algumas ideias:
Primeiro elemento: a[start], como mostrado na segunda implementação
Último elemento: a[end]
Elemento do meio: a[(start + end + 1) // 2]
Elemento aleatório: escolher qualquer elemento em a[start ... end]
Elemento mediano: escolher a mediana do segmento atual. Assim, cada divisão gera partes left e right de tamanho igual, mas demanda uma implementação mais trabalhosa.
O tempo médio de execução do QuickSort é . No entanto, dependendo da escolha do pivô, ele pode levar a uma performance quadrática .
🤔 Consegue pensar em um exemplo em que o algoritmo executaria operações caso escolhêssemos sempre o start como pivô?
Challenge
Dado uma lista de n inteiros e q pivôs, o objetivo é deslocar todos os elementos menores do que o pivô para a esquerda e todos os elementos maiores do que o pivô para a direita do elemento pivô, mantendo todos aqueles que são iguais juntos (ao lado uns dos outros, à direita dos menores e à esquerda dos maiores).
Entrada
A primeira linha da entrada contém um único inteiro n (1 ≤ n ≤ 100 000).
A linha seguinte contém uma lista de n inteiros separados por espaço ( ≤ ≤ ).
A terceira linha contém um único inteiro q (1 ≤ q ≤ 10).
A próxima linha contém uma lista de n inteiros separados por espaço (1 ≤ ≤ n) representando o índice do elemento pivô.
Saída
Para cada um dos q pivôs, o programa deve imprimir o array resultante depois de fazer o rearranjo. Se houver várias respostas possíveis, qualquer uma delas pode ser impressa.