5.11. O Merge Sort¶
Agora nós voltamos nossa atenção para usar a estratégia de “dividir para
conquistar” como uma forma de melhorar o desempenho dos algoritmos de ordenação.
O primeiro algoritmo que iremos estudar é o merge sort, um algoritmo
recursivo que divide uma lista continuamente pela metade. Se a lista estiver
vazia ou tiver um único item, ela está ordenada por definição (o caso base).
Se a lista tiver mais de um item, dividimos a lista e invocamos recursivamente
um merge sort em ambas as metades. Assim que as metades estiverem ordenadas,
a operação fundamental, chamada de intercalação, é realizada. Intercalar
é o processo de pegar duas listas menores ordenadas e combiná-las de modo
a formar uma lista nova, única e ordenada. A Figura 10
mostra nossa lista familiar sendo dividida pelo mergeSort
. A
Figura 11 mostra como as listas mais simples,
agora ordenadas, são intercaladas.
A função mergeSort
mostrada em ActiveCode 1 começa
perguntando pelo caso base. Se o tamanho da lista form menor ou igual a um,
então já temos uma lista ordenada e nenhum processamento adicional é necessário.
Se, por outro lado, o tamanho da lista for maior do que um, então usamos
a operação de slice
do Python para extrair a metades esquerda e direita.
É importante observar que a lista pode não ter um número par de elementos.
Isso, contudo, não importa, já que a diferença de tamanho entre as listas
será de apenas um elemento.
Quando a função mergeSort
é chamada nas metades esquerda e direita
(linhas 8-9), pressupõe-se que elas já estão ordenadas. O resto da função
(linhas 11-31) é responsável por intercalar as duas listas ordenadas
menores em uma lista ordenada maior. Note que a operação de intercalação
coloca um item por vez de volta na lista original (alist
) ao tomar
repetidamente o menor item das listas ordenadas.
A função mergeSort
foi aumentada com uma chamada de print
(linha 2)
para mostrar o conteúdo da lista sendo ordenada no começo de cada chamada.
Também há uma declaração de print
(linha 32) para mostrar o processo
de intercalação. O transcrito abaixo mostra o resultado de executar a
função na nossa lista de exemplo. Observe que a lista com 44, 55 e 20 não
irá ser dividida igualmente. A primeira metade resulta em [44] enquanto a
segunda em [55,20]. É fácil perceber que o procesos de divisão irá gerar
em algum momento uma lista que poderá ser imediatamente intercalada com
outras listas ordenadas.
Para analisar a função mergeSort
, precisamos considerar os dois processos
distintos que compõem sua implementação. Primeiro, a lista é dividida em
duas metades. Nós já computamos (na busca binária) que podemos dividir uma
lista ao meio \(\log n\) vezes, onde n é o tamanho da lista. O segundo
processo é a intercalação. Cada item na lista irá ser processado em algum
momento e colocado na lista ordenada. Então a operação de intercalação que
resulta em uma lista de tamanho n requer n operações. O resultado
desta análise é: \(\log n\) divisões, cada qual custando \(n\),
totalizando \(n\log n\) operações. Logo, o merge sort é um algoritmo
\(O(n\log n)\).
Lembre-se de que o operador “slice” é \(O(k)\), onde k é o tamanho
do corte. Para garantir que a função mergeSort
seja \(O(n\log n)\),
precisamos remover o operador “slice”. Isso é possível se simplesmente
passarmos os índices de início e fim junto com a lista quando fazemos a
chamada recursiva. Deixamos essa implementação como um exercício.
É importante notar que a função mergeSort
requer espaço extra para
armazenar as duas metades conforme elas são extraídas no processo de divisão.
Esse espaço adicional pode ser um fator crítico se a lista for grande e
pode tornar a ordenação problemática em conjuntos grandes de dados.
Autoavaliação
- [16, 49, 39, 27, 43, 34, 46, 40]
- Esta é a segunda metade da lista.
- [21,1]
- Sim, o merge sort irá continuar recursivamente em direção ao começo da lista até chegar ao caso base.
- [21, 1, 26, 45]
- Lembre-de de que o merge sort não atua na metade direita da lista até que a esquerda esteja ordenada.
- [21]
- Esta é a lista depois de 4 chamadas recursivas.
Q-2: Dada a seguinte lista de números: <br> [21, 1, 26, 45, 29, 28, 2, 9, 16, 49, 39, 27, 43, 34, 46, 40] <br> qual resposta mostra a lista que deveria ser ordenada depois de 3 chamadas recursivas do merge sort?
- [21, 1] e [26, 45]
- As primeiras duas listas intercaladas irão formar as listas do caso base, mas não chegamos ainda ao caso base.
- [[1, 2, 9, 21, 26, 28, 29, 45] e [16, 27, 34, 39, 40, 43, 46, 49]
- Estas serão as últimas duas listas intercaladas.
- [21] e [1]
- As listas [21] e [1] são os dois primeiros exemplos de caso base encontrados pelo merge sort e serão as duas primeiras listas a serem intercaladas.
- [9] e [16]
- Embora o 9 e o 16 estejam próximos um do outro, eles estão em diferentes metades da lista que sofre a primeira divisão.
Q-3: Dada a seguinte lista de números: <br> [21, 1, 26, 45, 29, 28, 2, 9, 16, 49, 39, 27, 43, 34, 46, 40] <br> qual resposta exibe as duas primeiras listas a serem intercaladas.