Algoritmos 2Revisão em C

Aula 4 de 6

Bubble sort

Trocas entre vizinhos.

Ao final desta aula você será capaz de
  • Executar o bubble sort à mão e dizer o que fica garantido após cada passagem.
  • Implementá-lo em C, com parada antecipada, para números, letras e structs.
  • Prever quantas trocas ele fará contando as inversões do vetor.
  • Explicar por que ele é rápido em listas já ordenadas e lento demais para listas grandes e bagunçadas.

Palavras novas nesta aula: Estabilidade (ordenação estável), Invariante, Inversão, Merge sort, Passagem (bubble sort). Cada uma tem uma explicação curta no glossário.

O problema

Uma fila de fichas numeradas está fora de ordem. A única operação permitida é comparar duas fichas vizinhas e trocá-las de lugar.

Repetindo comparações da esquerda para a direita, qual número chega obrigatoriamente ao fim da fila? Quantas vezes é preciso repetir?

Experimente com as fichas 5, 2, 4 e 1 antes de continuar. Acompanhe o 5.

Como funciona

O bubble sort percorre o vetor comparando cada par de vizinhos, v[j] e v[j+1]. Se o da esquerda for maior, troca os dois. Um percurso completo, do início ao fim, é uma passagem. Durante a passagem, o maior valor encontrado é “carregado” pelas trocas, como uma bolha subindo na água, até o fim da parte ainda desorganizada.

Bubble sort passo a passo no vetor 5, 2, 4, 1 Passagem 1: 5 e 2 trocam, 5 e 4 trocam, 5 e 1 trocam; o 5 chega à última posição. Passagem 2: 2 e 4 ficam, 4 e 1 trocam; o 4 fica na penúltima posição. Passagem 3: 2 e 1 trocam, e o vetor fica 1, 2, 4, 5. Total: 6 comparações e 5 trocas. Passagem 1 52415 > 2: troca 25415 > 4: troca 24515 > 1: troca 24155 no lugar 3 comparações · 3 trocas Passagem 2 24152 < 4: mantém 24154 > 1: troca 21454 no lugar 2 comparações · 1 troca Passagem 3 21452 > 1: troca 1245ordenado! 1 comparação · 1 troca Total: 6 comparações e 5 trocas. Amarelo: o par comparado. Verde: posições que já estão no lugar final.
Cada linha mostra o par comparado (amarelo) antes da decisão. Ao final de cada passagem, mais um valor fica definitivamente no lugar (verde), e a passagem seguinte pode parar uma posição antes.
O que fica garantido a cada passagem

Depois da 1ª passagem, o maior valor está na última posição. Depois da 2ª, os dois maiores estão nas duas últimas, e assim por diante. Após k passagens, as k últimas posições estão prontas e não mudam mais. Com n − 1 passagens, o vetor inteiro está ordenado.

Uma garantia como essa, que continua verdadeira ao final de cada repetição de um laço, se chama invariante. Saber o invariante de um algoritmo é o que permite prever o estado do vetor em qualquer ponto da execução.

Parada antecipada: se uma passagem inteira terminar sem nenhuma troca, todos os vizinhos já estão em ordem, e o vetor está ordenado. Não há por que continuar. É essa regra que torna o bubble sort rápido em listas que já chegam ordenadas.

Inversões: a medida da bagunça

Quanto trabalho o bubble sort vai ter? Depende de quão bagunçado está o vetor, e existe um jeito simples de medir isso. Uma inversão é qualquer par de elementos em que o maior aparece antes do menor, sejam eles vizinhos ou não.

As cinco inversões do vetor 5, 2, 4, 1 Arcos ligam os pares fora de ordem: 5 e 2, 5 e 4, 5 e 1, 2 e 1, 4 e 1. São 5 inversões, e o bubble sort faz exatamente 5 trocas, porque cada troca de vizinhos desfaz um arco. 5 > 2 4 > 1 2 > 1 5 > 4 5 > 1 5v[0]2v[1]4v[2]1v[3] 5 inversões= 5 trocas do bubble sortCada troca de vizinhosdesfaz exatamente um arco.Vetor ordenado: 0 arcos.Ordem decrescente: todos os parestêm arco, se não houver repetidos.
Em [5, 2, 4, 1], há 5 pares fora de ordem. O par (2, 4) não conta, porque já está na ordem certa.
Trocas = inversões

Quando o bubble sort troca dois vizinhos fora de ordem, ele conserta exatamente um par invertido, e nenhum outro par muda. Por isso, o número de trocas é sempre igual ao número de inversões. Na figura das passagens, foram 5 trocas, os mesmos 5 arcos. Com isso, dá para prever o trabalho sem executar o algoritmo.

Na prática

Laboratório de ordenação

Informe de 2 a 8 inteiros entre −99 e 99, separados por vírgula. Conte as inversões antes de começar e confira se o total de trocas bate. Experimente também 2, 3, 4, 5, 1 e 5, 1, 2, 3, 4 e veja as “tartarugas” e “lebres” do exercício 4.

    • Par comparado
    • Posição final garantida

    Implementação em C

    Funções: copie para um programa que tenha main
    void bubble_sort(int v[], int n) {
        for (int fim = n - 1; fim > 0; fim--) {      // depois de fim, tudo está pronto
            int trocou = 0;
            for (int j = 0; j < fim; j++) {           // compara v[j] com v[j+1]
                if (v[j] > v[j + 1]) {                // > estrito: iguais não trocam
                    int temp = v[j];
                    v[j] = v[j + 1];
                    v[j + 1] = temp;
                    trocou = 1;
                }
            }
            if (!trocou) break;                        // passagem sem trocas: acabou
        }
    }

    Para usar: declare int v[] = {5, 2, 4, 1}; e chame bubble_sort(v, 4);. O resultado é [1, 2, 4, 5], com as 6 comparações e 5 trocas da figura.

    Exemplo real: classificação de um concurso

    Um concurso publica a classificação por nota, da maior para a menor. O edital diz que, em caso de empate, fica à frente quem se inscreveu primeiro. O vetor já chega em ordem de inscrição. Basta ordenar por nota com o cuidado certo:

    Programa completo: compile e execute
    #include <stdio.h>
    
    typedef struct {
        char nome[20];
        float nota;
    } Candidato;
    
    // Maior nota primeiro. Em empate, quem se inscreveu antes continua na frente.
    void classificar(Candidato c[], int n) {
        for (int fim = n - 1; fim > 0; fim--) {
            int trocou = 0;
            for (int j = 0; j < fim; j++) {
                if (c[j].nota < c[j + 1].nota) {      // < estrito: empatados não trocam
                    Candidato temp = c[j];            // troca o registro inteiro
                    c[j] = c[j + 1];
                    c[j + 1] = temp;
                    trocou = 1;
                }
            }
            if (!trocou) break;
        }
    }
    
    int main(void) {
        Candidato c[] = {                   // em ordem de inscrição
            {"Ana", 8.5f}, {"Bia", 7.0f}, {"Caio", 8.5f}, {"Davi", 9.0f}
        };
        int n = sizeof c / sizeof c[0];
        classificar(c, n);
        for (int i = 0; i < n; i++) {
            printf("%d. %s (%.1f)\n", i + 1, c[i].nome, c[i].nota);
        }
        return 0;
    }
    1. Davi (9.0) 2. Ana (8.5) 3. Caio (8.5) 4. Bia (7.0)

    Ana e Caio empataram com 8.5. Ana se inscreveu antes e ficou à frente, como manda o edital. Isso só acontece por causa de um detalhe: o sinal < estrito.

    Estabilidade: por que um sinal muda tudo

    Um algoritmo de ordenação é estável quando elementos empatados mantêm entre si a ordem em que chegaram. O bubble sort é estável porque só troca vizinhos quando estão estritamente fora de ordem. Dois empatados nunca trocam, então nunca se ultrapassam. Basta trocar < por <= para perder essa garantia:

    Ranking com empate: versão estável e versão instável Inscrição: Ana 8.5, Bia 7.0, Caio 8.5, Davi 9.0. Ordenando por nota com o sinal menor estrito, o resultado é Davi, Ana, Caio, Bia: Ana, que se inscreveu antes, continua à frente de Caio. Com menor ou igual, o resultado é Davi, Caio, Ana, Bia: Caio passou à frente de Ana sem motivo. Ordem de inscrição 1º Ana8.52º Bia7.03º Caio8.54º Davi9.0 Com < (estável) 1º Davi9.02º Ana8.53º Caio8.54º Bia7.0 ✓ Ana se inscreveu antese continua à frente de Caio. Com <= (instável) 1º Davi9.02º Caio8.53º Ana8.54º Bia7.0 ✗ Caio passou à frentede Ana sem nenhum motivo.
    As duas versões ordenam por nota corretamente. Só a versão com < respeita o critério de desempate do edital. Em um concurso real, a versão com <= geraria recurso na justiça.

    Estabilidade importa sempre que a ordem de chegada carrega informação: inscrição, horário do pedido, posição em uma lista anterior. Também permite ordenar por vários critérios em etapas: ordene primeiro por nome e depois, de forma estável, por nota. O resultado fica por nota e, nos empates, por nome.

    Armadilhas frequentes
    • j <= fim compara v[fim] com v[fim + 1]. Na primeira passagem, isso lê fora do vetor e pode até trazer lixo para dentro dele.
    • Declarar trocou = 0 fora do laço externo faz a parada antecipada nunca mais funcionar depois da primeira troca.
    • Usar >= (ou <=) troca elementos iguais: o resultado continua ordenado, mas perde a estabilidade e faz trocas inúteis.
    • Trocar só o campo da nota, como c[j].nota no exemplo do concurso, separa a nota do nome. Troque a struct inteira.

    Quanto custa na prática?

    Medimos o bubble sort desta aula com três tipos de entrada:

    Medições reais: comparações, trocas e tempo
    ElementosJá ordenadoAleatórioOrdem decrescente
    1 000999 comparações, 0 trocas, < 0,1 ms499 mil comparações, 250 mil trocas, 1,1 ms499 500 comparações e trocas, 0,5 ms
    10 0009 999 comparações, 0 trocas, < 0,1 ms50 milhões de comparações, 0,11 s50 milhões, 0,06 s
    50 00049 999 comparações, 0 trocas, < 0,1 ms1,25 bilhão de comparações, 2,9 s1,25 bilhão, 1,4 s

    Os números da coluna decrescente seguem exatamente a fórmula n(n−1)/2: 1 000 × 999 / 2 = 499 500, e 50 000 × 49 999 / 2 ≈ 1,25 bilhão.

    Três lições saem dessa tabela:

    1. Lista já ordenada: uma passagem e pronto. Graças à parada antecipada, 50 mil elementos ordenados custam só 49 999 comparações.
    2. Lista bagunçada: o custo cresce com o quadrado. Com 5 vezes mais elementos (10 mil para 50 mil), o tempo aumentou cerca de 26 vezes, perto dos 25 que o quadrado prevê. Com 1 milhão de elementos, a estimativa é de uns 20 minutos. No pior caso, são n(n−1)/2 comparações: cada elemento é comparado com praticamente todos os outros.
    3. Contar operações não conta tudo. O vetor aleatório fez menos trocas que o decrescente e mesmo assim foi mais lento. O número de operações não conta toda a história do tempo.

    Comparação com as outras ordenações da revisão

    Comportamento das três ordenações elementares
    AlgoritmoLista já ordenadaLista bagunçadaMovimentos de dadosEstável
    Bubble (com parada)uma passagem, rápidoquadrático, lentomuitos: uma troca por inversãoSim
    Insertionuma passagem, rápidoquadrático; nas medições, o mais rápido dos trêsum deslocamento por inversão, mais barato que uma trocaSim
    Selectionquadrático, mesmo ordenadaquadrático, lentopoucos: no máximo n − 1 trocasNão

    Na prática, o bubble sort raramente é a melhor escolha: o insertion sort, da próxima aula, faz o mesmo serviço movendo menos dados. O valor do bubble sort está no que ele ensina: invariantes, inversões e estabilidade.

    Exercícios progressivos

    Use as figuras da aula como modelo: desenhe o vetor e marque o par comparado em cada etapa. A dica não entrega a resposta: ela indica a pergunta certa a fazer.

    1. A pilha de provas Básico

    Uma professora organiza as provas pelo número de chamada. A pilha está com os números [5, 2, 4], e ela segue o bubble sort à mão. Mostre como a pilha fica depois de cada comparação da primeira passagem. Qual número está garantido no lugar ao final dela?

    Ver uma dica
    Uma passagem compara os pares (posição 0, posição 1) e depois (posição 1, posição 2). Compare sempre com o vetor atualizado, e não com o original. Qual valor vai sendo “carregado” para a direita?
    Conferir a resposta
    1. Compara 5 e 2: 5 > 2, troca. A pilha fica [2, 5, 4].
    2. Compara 5 e 4: 5 > 4, troca. A pilha fica [2, 4, 5].

    O 5, o maior, foi carregado até o fim e está garantido no lugar. Neste caso, o vetor já ficou ordenado. A segunda passagem compara 2 e 4, não troca nada e encerra pela parada antecipada.

    2. A nota fantasma Básico

    Um colega mudou o laço interno para for (int j = 0; j <= fim; j++). Às vezes o programa funciona. Às vezes, o ranking aparece com uma nota que nenhum aluno tirou, e um aluno desaparece. Explique o que está acontecendo.

    Ver uma dica
    Na primeira passagem, fim vale n − 1. Qual é o último valor de j com <=? Qual posição a comparação acessa nesse momento? Ela existe no vetor?
    Conferir a resposta

    Com j <= fim, o último j da primeira passagem é n − 1, e a comparação lê v[n]: a posição logo após o fim do vetor. Ali há qualquer coisa que estiver na memória (lixo).

    Acessar fora do vetor é comportamento indefinido: a linguagem não garante nada sobre o que acontece. Um cenário comum, que explica o sintoma: se o lixo, comparado com o último aluno, parecer “fora de ordem”, o if manda trocar. O lixo entra no vetor como uma nota fantasma, e o registro verdadeiro vai para fora do vetor e some. A escrita em v[n] também pode corromper outra variável ou derrubar o programa. Por isso o bug é intermitente: depende do que está na memória naquele instante.

    Regra: se o corpo do laço acessa v[j + 1], o laço precisa parar em j < fim, para que j + 1 nunca passe de fim.

    3. Prever sem simular Intermediário

    O juiz de uma maratona de programação quer saber quantas trocas o bubble sort fará em duas listas, mas sem executar o algoritmo: [3, 1, 2] e [4, 3, 2, 1]. Responda contando inversões, como na figura dos arcos.

    Ver uma dica
    Para cada elemento, conte quantos elementos menores aparecem à direita dele. Some tudo. Por que essa soma é igual ao número de arcos?
    Conferir a resposta

    [3, 1, 2]: o 3 tem dois menores à direita (1 e 2), e o 1 não tem nenhum. Total: 2 trocas.

    [4, 3, 2, 1]: o 4 tem 3 menores à direita, o 3 tem 2, o 2 tem 1. Total: 3 + 2 + 1 = 6 trocas. Todos os pares estão invertidos, o pior caso possível para 4 elementos.

    Contar “menores à direita” de cada elemento lista cada arco exatamente uma vez, pelo seu lado esquerdo. Por isso a soma é o número de inversões.

    4. Lebres e tartarugas Intermediário

    Uma planilha de vendas já estava ordenada, mas um item novo foi colocado no lugar errado. Para cada caso, quantas passagens e quantas comparações o bubble sort (com parada antecipada) faz?

    1. [1, 2, 3, 5, 4]: dois vizinhos trocados no final.
    2. [5, 1, 2, 3, 4]: o maior foi parar no início.
    3. [2, 3, 4, 5, 1]: o menor foi parar no fim.

    Os casos 2 e 3 têm o mesmo número de inversões. Por que um custa mais que o outro?

    Ver uma dica
    Em uma passagem, quantas posições um valor grande consegue andar para a direita? E quantas posições um valor pequeno consegue andar para a esquerda? Lembre que, com a parada antecipada, uma passagem sem trocas encerra o algoritmo. Se todas as n − 1 passagens trocarem algo, não há essa passagem extra.
    Conferir a resposta
    CasoPassagensComparaçõesTrocas
    [1, 2, 3, 5, 4]24 + 3 = 71
    [5, 1, 2, 3, 4]24 + 3 = 74
    [2, 3, 4, 5, 1]44 + 3 + 2 + 1 = 104

    Um valor grande é carregado pelas trocas sucessivas e pode atravessar o vetor inteiro em uma passagem: é uma lebre. Um valor pequeno anda só uma posição para a esquerda por passagem: é uma tartaruga. No caso 3, o 1 precisa de 4 passagens para chegar ao início.

    Por isso, o bubble sort é rápido quando um item fora do lugar é grande e lento quando é pequeno. Uma variante chamada cocktail sort alterna passagens da esquerda para a direita e da direita para a esquerda, justamente para mover as tartarugas mais depressa.

    5. Detector de anagramas Avançado

    Um jogo de palavras precisa verificar se duas palavras são anagramas: usam exatamente as mesmas letras, em outra ordem. Por exemplo, “amor” e “roma”, ou “pedra” e “padre”. Escreva int anagramas(const char *a, const char *b), que retorna 1 se forem anagramas, usando o bubble sort para ordenar as letras. Considere palavras em minúsculas, sem acentos e com menos de 50 letras.

    anagramas("amor", "roma") -> 1 anagramas("pedra", "padre") -> 1 anagramas("casa", "cama") -> 0 anagramas("ab", "abc") -> 0
    Ver uma dica
    O que acontece com “amor” e com “roma” se você colocar as letras de cada uma em ordem alfabética? Um char é um número pequeno em C, então o bubble sort funciona com ele sem mudança na lógica. Atenção: os parâmetros são const char *, e você não pode alterar as palavras originais. Onde vai ordenar?
    Conferir a resposta
    Funções: copie para um programa que tenha main
    #include <string.h>
    
    void ordenar_letras(char s[]) {
        int n = (int)strlen(s);
        for (int fim = n - 1; fim > 0; fim--) {
            int trocou = 0;
            for (int j = 0; j < fim; j++) {
                if (s[j] > s[j + 1]) {             // compara os códigos das letras
                    char temp = s[j];
                    s[j] = s[j + 1];
                    s[j + 1] = temp;
                    trocou = 1;
                }
            }
            if (!trocou) break;
        }
    }
    
    int anagramas(const char *a, const char *b) {
        char x[50], y[50];
        if (strlen(a) != strlen(b) || strlen(a) >= sizeof x) return 0;
        strcpy(x, a);                  // copia: as originais não podem ser alteradas
        strcpy(y, b);
        ordenar_letras(x);             // "amor" -> "amor", "roma" -> "amor"
        ordenar_letras(y);
        return strcmp(x, y) == 0;
    }

    Ordenar as letras transforma qualquer anagrama na mesma “assinatura”: “amor” e “roma” viram “amor”, e “pedra” e “padre” viram “adepr”. Basta comparar as assinaturas com strcmp.

    Por que copiar? Uma chamada como anagramas("amor", "roma") passa textos literais, que ficam em memória somente leitura. Tentar ordená-los diretamente é comportamento indefinido e costuma derrubar o programa. Se você compilar com o MSVC, ele mostra o aviso C4996 em strcpy, recomendando uma versão própria da Microsoft. Aqui o aviso pode ser ignorado com segurança, porque o tamanho é verificado antes da cópia. O teste de tamanho no início descarta palavras de tamanhos diferentes e protege as cópias contra overflow.

    ./desafio --nivel=hacker

    Desafio hacker: o juiz que conta trocas

    • Tipo: opcional
    • Dificuldade: ★★★★
    • Tema: inversões e dividir para conquistar
    • Limite: n ≤ 200 000, 1 segundo

    Um juiz online quer saber quantas trocas o bubble sort faria em um vetor, mas não pode simular o algoritmo. Com n = 200 000, a simulação levaria cerca de 2 × 10¹⁰ operações. Para um vetor aleatório, extrapolando a tabela de medições (16 vezes o tempo de 50 mil), seriam uns 45 segundos. Para o vetor decrescente do exemplo, uns 22. O limite é 1 segundo.

    Atenção: este desafio vai além da aula. Ele usa a ideia do merge sort, um algoritmo de ordenação mais rápido que você verá em detalhes mais adiante no curso. As dicas levam até lá passo a passo.

    Escreva long long contar_inversoes(int v[], int n), que conte as inversões sem comparar todos os pares. Contrato: a função pode deixar o vetor reordenado, pode usar um vetor auxiliar de n posições e deve retornar −1 se faltar memória.

    Entrada[5, 2, 4, 1]
    Saída5
    Entrada200 000, 199 999, …, 1
    Saída19999900000
    Dica 1: por que long long?

    Com 200 000 elementos em ordem decrescente, todos os pares estão invertidos: são quase 20 bilhões. Um int de 32 bits vai só até cerca de 2,1 bilhões. A resposta precisa de um inteiro de 64 bits.

    Dica 2: divida ao meio

    Corte o vetor ao meio. Toda inversão é de um destes tipos: os dois elementos estão na metade esquerda, os dois estão na metade direita, ou um está em cada metade. Os dois primeiros tipos são o mesmo problema, menor: use recursão. Falta descobrir como contar o terceiro tipo depressa.

    Dica 3: intercalar conta de graça

    Se as duas metades estiverem ordenadas, junte-as em uma só lista ordenada, escolhendo sempre o menor dos dois primeiros, como quem junta duas pilhas de provas já ordenadas. Quando o escolhido vier da direita, o que você sabe sobre ele em relação aos elementos que ainda restam na esquerda?

    Solução comentada

    A figura mostra a ideia com o exemplo [5, 2, 4, 1]:

    Contando inversões ao intercalar duas metades ordenadas O vetor 5, 2, 4, 1 é dividido em 5, 2 e 4, 1. Cada metade é ordenada, contando uma inversão em cada: ficam 2, 5 e 1, 4. Na intercalação, o 1 vem da direita enquanto restam 2 e 5 na esquerda: mais 2 inversões. O 2 vem da esquerda: nada. O 4 vem da direita enquanto resta o 5: mais 1. O 5 vem da esquerda. Total: 1 + 1 + 3 = 5 inversões. Metade esquerda[5, 2] ordenada: 1 inversãoMetade direita[4, 1] ordenada: 1 inversão 2514 1+2restam 2 e 5 2+0 4+1resta o 5 5+0 Resultadointercalado: Quando o próximo vem da direita, ele é menor que todos os que ainda restam na esquerda. Total: 1 + 1 + 3 = 5.
    As inversões dentro de cada metade são contadas pela recursão. As inversões entre as metades são contadas ao intercalar: cada elemento que vem da direita “pula” todos os que restam na esquerda.
    Funções: copie para um programa que tenha main
    #include <stdlib.h>
    
    // Conta inversões em v[ini..fim) e deixa o trecho ordenado.
    // fim é exclusivo: contar(v, tmp, 0, 4) olha v[0], v[1], v[2] e v[3].
    long long contar(int v[], int tmp[], int ini, int fim) {
        if (fim - ini < 2) return 0;                 // 0 ou 1 elemento: nenhuma inversão
        int meio = ini + (fim - ini) / 2;
        long long inv = contar(v, tmp, ini, meio)    // inversões só na esquerda
                      + contar(v, tmp, meio, fim);   // inversões só na direita
    
        int i = ini, j = meio, k = ini;              // intercalação
        while (i < meio && j < fim) {
            if (v[i] <= v[j]) {                      // <= : iguais NÃO são inversão
                tmp[k++] = v[i++];
            } else {
                inv += meio - i;                     // v[j] é menor que todos de v[i..meio-1]
                tmp[k++] = v[j++];
            }
        }
        while (i < meio) tmp[k++] = v[i++];
        while (j < fim)  tmp[k++] = v[j++];
        for (k = ini; k < fim; k++) v[k] = tmp[k];
        return inv;
    }
    
    long long contar_inversoes(int v[], int n) {
        if (n < 2) return 0;
        int *tmp = malloc(n * sizeof *tmp);
        if (tmp == NULL) return -1;
        long long r = contar(v, tmp, 0, n);
        free(tmp);
        return r;                                    // atenção: v termina ordenado
    }

    Por que funciona. Durante a intercalação, as duas metades estão ordenadas. Se v[j], da direita, é menor que v[i], ele também é menor que todos os que vêm depois de v[i] na esquerda, pois esses são ainda maiores. Assim, meio − i inversões são contadas de uma só vez, sem olhar par por par.

    Por que é rápido. O vetor é cortado ao meio cerca de 18 vezes até sobrar um elemento (200 000 → 100 000 → … → 1). Em cada um desses níveis, cada elemento é intercalado uma única vez. Isso dá uma ordem de grandeza de 18 × 200 000 ≈ 3,6 milhões de passos de intercalação. Cada passo envolve uma comparação e algumas cópias, contra 20 bilhões de comparações da simulação: milissegundos em vez de dezenas de segundos. A memória extra é o vetor temporário de n posições, mais a pilha da recursão, com cerca de 18 níveis. Quem chama deve verificar o retorno −1, que indica falta de memória. Esse “cortar ao meio e juntar” é o algoritmo merge sort, e o custo dele é chamado de O(n log n).

    Detalhes que derrubam soluções: usar < em vez de <= conta elementos iguais como inversões, mas o bubble sort não troca iguais. Usar int no acumulador estoura. E a função altera o vetor: se precisar do original, passe uma cópia.

    Como testar: compare com uma força bruta que verifica todos os pares, em milhares de vetores pequenos e aleatórios, com valores repetidos. Nesta página, os dois métodos coincidiram em 3000 vetores, e o vetor decrescente de 200 000 elementos deu exatamente 19 999 900 000.

    Revisão rápida

    • Cada passagem leva o maior valor restante para o fim. Depois de k passagens, as k últimas posições estão prontas.
    • Uma passagem sem trocas encerra o algoritmo: lista ordenada custa uma passagem só.
    • Trocas = inversões. Contar os pares fora de ordem prevê o trabalho.
    • O < estrito na ordenação decrescente, e o > estrito na crescente, tornam a ordenação estável: como iguais nunca são trocados, empatados mantêm a ordem de chegada.
    • Valores grandes andam rápido (lebres), e valores pequenos andam uma casa por passagem (tartarugas).
    • Para listas grandes e bagunçadas, o custo explode. Use algoritmos melhores, como o merge sort do desafio.