Suponha que temos um vetor de tamanho N desordenado. Se ordenamos cada uma das metades separadamente, podemos juntar as metades em O(n = tamanho do vetor), certo? Explico melhor:
Temos as duas metades ordenadas. Para juntar elas, criamos um novo vetor. O menor elemento é ou o primeiro elemento da primeira metade ou o primeiro da segunda:
Ponto interessante. A gente também está fazendo inversões aqui. Quando colocamos o 1 na frente, o que fizemos na verdade foram sucessivas inversões:
| Total de 4 inversões |
Então a ideia é ordenar a primeira parte, ordenar a segunda, e juntar para um vetor de 8 elementos. E o que é ordenar a primeira parte?
A primeira parte é um vetor de 4 elementos, e segue o mesmo esquema. Divide em duas partes, ordena a primeira, a segunda e junta. E essa nova primeira parte?
É um vetor de 2 elementos. Divide em duas partes (dois vetores de um elemento), ordena a primeira parte, a segunda e junta.
E o que é ordenar um vetor de um elemento? Não fazer nada.
Ou seja, definimos um algoritmo recursivo:
//Left eh onde comeca o vetor e right onde termina
void MergeSort(int * vet, int left, int right) {
//Se soh tem um elemento, ou se o fim ta antes do começa
//Acaba aqui mesmo
if(left >= right) return;
//Se nao, tem mais de um elemento
int mid = (left+right)/2;
//Ordena primeira parte
MergeSort(vet, left, mid);
//Ordena a segunda
MergeSort(vet, mid+1, right);
//Agora, a funcao que junta as partes
Merge(vet, left, right);
return;
}
E o tempo de execução é O(nlogn). O que consome tempo é juntar as duas partes, certo?A função que junta as partes é a seguinte:
//Funcao que divide o vetor em duas partes
//Assume que a primeira parte estah ordenada
//E que a segunda parte estah ordenada
//Mistura as duas e forma uma soh ordenada
void Merge(int * vet, int left, int right) {
int aux_count = 0;
//A posicao do meio no vetor
int mid = (left+right)/2;
//P1 e P2 sao os ponteiros que vao percorrer o vetor
//P1 percorre a primeira metade (de left ate mid)
//P2 percorre a segunda metade (de mid+1 ate right)
int p1, p2;
p1 = left;
p2 = mid+1;
//Enquanto nao percorreu nenhuma das metades inteira
while(p1 <= mid && p2 <= right) {
if(vet[p1] <= vet[p2]) //Se o da primeira metade eh menor
aux[aux_count++] = vet[p1++];
else //Se o da segunda metade eh menor
aux[aux_count++] = vet[p2++];
}
//Ja percorreu uma metade inteira
//Agora soh colocar os elementos da outra metade
//Os elementos da outra metade jah estao ordenados entre si
while(p1 <= mid)
aux[aux_count++] = vet[p1++];
while(p2 <= right)
aux[aux_count++] = vet[p2++];
//Copia os dados do vetor auxiliar pro vetor original
for(int i = 0; i < aux_count; i++) {
vet[left++] = aux[i];
}
return;
}
Com uma pequena alteração, podemos computar também o número de inversões, que ocorrem quando um elemento da segunda metade deve ficar na frente de um da primeira, mas isso fica como sugestão.Problemas sugeridos:
URI 1088: Bolhas e baldes [link]
SPOJ BR (BALE11): Balé (problema da OBI) [link]
Continuar para parte 3 [link].
Códigos no github [link].
Muito bom o blog. Recomendo que vc continue abordando estruturas de dados e algoritmos.
ResponderExcluirComo ficaria o codigo com o o contador de inversões?
ResponderExcluirOlá! A inversão acontece quando, na função Merge, um elemento da segunda metade é menor que o da primeira. Por exemplo, se estivermos dando Merge nesses dois vetores:
ExcluirA = {2, 4, 6, 8}
B = {1, 3, 5, 7}
Quando colocamos 1, ele fica na frente de todos os elementos de A. Nesse caso, acontece 4 inversões. No código, seria mais ou menos essa mudança:
while(p1 <= mid && p2 <= right) {
if(vet[p1] <= vet[p2])
aux[aux_count++] = vet[p1++];
else {
aux[aux_count++] = vet[p2++];
inversoes += (mid - p1 + 1); // Linha adicionada
}
}