# Algoritmos de Ordenação em JavaScript

Este material reúne os principais algoritmos de ordenação para apoiar a modelagem da atividade de Ordenação.

## Como ler este guia

- Todas as funções abaixo recebem um array numérico.
- Para facilitar testes e evitar efeitos colaterais, as implementações retornam um novo array quando possível.
- Cada algoritmo inclui uma explicação breve e sua complexidade.

---

## 1) Bubble Sort (Ordenação por Bolha)

Compara pares adjacentes e troca quando estão fora de ordem. A cada passada, o maior elemento "sobe" para o fim.

- Melhor: O(n) (com otimização de parada)
- Médio/Pior: O(n²)
- Espaço: O(1)
- Estável: Sim

```javascript
export function bubbleSort(input) {
  const arr = [...input];
  const n = arr.length;

  for (let i = 0; i < n - 1; i++) {
    let trocou = false;

    for (let j = 0; j < n - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
        trocou = true;
      }
    }

    if (!trocou) break;
  }

  return arr;
}
```

## 2) Selection Sort (Ordenação por Seleção)

Encontra o menor elemento da parte não ordenada e o coloca na posição correta.

- Melhor/Médio/Pior: O(n²)
- Espaço: O(1)
- Estável: Não

```javascript
export function selectionSort(input) {
  const arr = [...input];
  const n = arr.length;

  for (let i = 0; i < n - 1; i++) {
    let min = i;
    for (let j = i + 1; j < n; j++) {
      if (arr[j] < arr[min]) min = j;
    }
    if (min !== i) [arr[i], arr[min]] = [arr[min], arr[i]];
  }

  return arr;
}
```

## 3) Insertion Sort (Ordenação por Inserção)

Constrói a parte ordenada inserindo cada elemento em sua posição correta.

- Melhor: O(n)
- Médio/Pior: O(n²)
- Espaço: O(1)
- Estável: Sim

```javascript
export function insertionSort(input) {
  const arr = [...input];

  for (let i = 1; i < arr.length; i++) {
    const chave = arr[i];
    let j = i - 1;

    while (j >= 0 && arr[j] > chave) {
      arr[j + 1] = arr[j];
      j--;
    }

    arr[j + 1] = chave;
  }

  return arr;
}
```

## 4) Merge Sort (Ordenação por Intercalação)

Divide o array ao meio recursivamente e intercala as metades já ordenadas.

- Melhor/Médio/Pior: O(n log n)
- Espaço: O(n)
- Estável: Sim

```javascript
function merge(esq, dir) {
  const resultado = [];
  let i = 0;
  let j = 0;

  while (i < esq.length && j < dir.length) {
    if (esq[i] <= dir[j]) {
      resultado.push(esq[i++]);
    } else {
      resultado.push(dir[j++]);
    }
  }

  return [...resultado, ...esq.slice(i), ...dir.slice(j)];
}

export function mergeSort(input) {
  if (input.length <= 1) return [...input];

  const meio = Math.floor(input.length / 2);
  const esq = mergeSort(input.slice(0, meio));
  const dir = mergeSort(input.slice(meio));

  return merge(esq, dir);
}
```

## 5) Quick Sort (Ordenação Rápida)

Escolhe um pivô e particiona os elementos em menores e maiores que ele, ordenando recursivamente.

- Melhor/Médio: O(n log n)
- Pior: O(n²)
- Espaço: O(log n) em média (recursão)
- Estável: Não

```javascript
export function quickSort(input) {
  const arr = [...input];

  function particionar(inicio, fim) {
    const pivo = arr[fim];
    let i = inicio - 1;

    for (let j = inicio; j < fim; j++) {
      if (arr[j] <= pivo) {
        i++;
        [arr[i], arr[j]] = [arr[j], arr[i]];
      }
    }

    [arr[i + 1], arr[fim]] = [arr[fim], arr[i + 1]];
    return i + 1;
  }

  function ordenar(inicio, fim) {
    if (inicio >= fim) return;
    const p = particionar(inicio, fim);
    ordenar(inicio, p - 1);
    ordenar(p + 1, fim);
  }

  ordenar(0, arr.length - 1);
  return arr;
}
```

## 6) Heap Sort

Transforma o array em um heap máximo e remove repetidamente o maior elemento para o fim.

- Melhor/Médio/Pior: O(n log n)
- Espaço: O(1)
- Estável: Não

```javascript
export function heapSort(input) {
  const arr = [...input];

  function heapify(n, i) {
    let maior = i;
    const esq = 2 * i + 1;
    const dir = 2 * i + 2;

    if (esq < n && arr[esq] > arr[maior]) maior = esq;
    if (dir < n && arr[dir] > arr[maior]) maior = dir;

    if (maior !== i) {
      [arr[i], arr[maior]] = [arr[maior], arr[i]];
      heapify(n, maior);
    }
  }

  for (let i = Math.floor(arr.length / 2) - 1; i >= 0; i--) {
    heapify(arr.length, i);
  }

  for (let i = arr.length - 1; i > 0; i--) {
    [arr[0], arr[i]] = [arr[i], arr[0]];
    heapify(i, 0);
  }

  return arr;
}
```

## 7) Shell Sort

Generaliza o Insertion Sort usando intervalos (gaps) que diminuem ao longo das passadas.

- Melhor: depende da sequência de gaps
- Médio/Pior: em geral entre O(n^1.3) e O(n²)
- Espaço: O(1)
- Estável: Não

```javascript
export function shellSort(input) {
  const arr = [...input];
  let gap = Math.floor(arr.length / 2);

  while (gap > 0) {
    for (let i = gap; i < arr.length; i++) {
      const temp = arr[i];
      let j = i;

      while (j >= gap && arr[j - gap] > temp) {
        arr[j] = arr[j - gap];
        j -= gap;
      }

      arr[j] = temp;
    }

    gap = Math.floor(gap / 2);
  }

  return arr;
}
```

## 8) Counting Sort

Conta quantas vezes cada valor aparece e reconstrói o array ordenado. Muito eficiente para inteiros em faixa limitada.

- Melhor/Médio/Pior: O(n + k), onde k é a faixa de valores
- Espaço: O(k)
- Estável: Pode ser estável (implementação abaixo é estável)

```javascript
export function countingSort(input) {
  if (input.length === 0) return [];

  const arr = [...input];
  const min = Math.min(...arr);
  const max = Math.max(...arr);
  const faixa = max - min + 1;

  const contagem = new Array(faixa).fill(0);
  const saida = new Array(arr.length);

  for (const valor of arr) contagem[valor - min]++;
  for (let i = 1; i < contagem.length; i++) contagem[i] += contagem[i - 1];

  for (let i = arr.length - 1; i >= 0; i--) {
    const valor = arr[i];
    const idx = valor - min;
    saida[contagem[idx] - 1] = valor;
    contagem[idx]--;
  }

  return saida;
}
```

## 9) Radix Sort (LSD)

Ordena inteiros por dígitos, do menos significativo para o mais significativo, usando um sort estável por dígito.

- Melhor/Médio/Pior: O(d * (n + b))
: d = número de dígitos, b = base (10)
- Espaço: O(n + b)
- Estável: Sim

```javascript
export function radixSort(input) {
  if (input.length === 0) return [];
  if (input.some((n) => n < 0)) {
    throw new Error("radixSort desta versão aceita apenas inteiros não negativos.");
  }

  let arr = [...input];
  const max = Math.max(...arr);

  for (let exp = 1; Math.floor(max / exp) > 0; exp *= 10) {
    const saida = new Array(arr.length);
    const contagem = new Array(10).fill(0);

    for (const valor of arr) {
      const digito = Math.floor(valor / exp) % 10;
      contagem[digito]++;
    }

    for (let i = 1; i < 10; i++) contagem[i] += contagem[i - 1];

    for (let i = arr.length - 1; i >= 0; i--) {
      const valor = arr[i];
      const digito = Math.floor(valor / exp) % 10;
      saida[contagem[digito] - 1] = valor;
      contagem[digito]--;
    }

    arr = saida;
  }

  return arr;
}
```

## 10) Bucket Sort

Distribui valores em "baldes" por faixa, ordena cada balde e concatena. Funciona bem para dados distribuídos uniformemente.

- Melhor/Médio: próximo de O(n)
- Pior: O(n²) (se concentrar muitos valores no mesmo balde)
- Espaço: O(n + k)
- Estável: Depende da ordenação interna

```javascript
export function bucketSort(input, bucketSize = 5) {
  if (input.length === 0) return [];

  const arr = [...input];
  const min = Math.min(...arr);
  const max = Math.max(...arr);

  const qtdBuckets = Math.floor((max - min) / bucketSize) + 1;
  const buckets = Array.from({ length: qtdBuckets }, () => []);

  for (const valor of arr) {
    const idx = Math.floor((valor - min) / bucketSize);
    buckets[idx].push(valor);
  }

  const resultado = [];
  for (const bucket of buckets) {
    if (bucket.length > 0) {
      bucket.sort((a, b) => a - b);
      resultado.push(...bucket);
    }
  }

  return resultado;
}
```

## 11) Comb Sort

Melhora o Bubble Sort comparando elementos com uma distância (gap) maior que vai diminuindo.

- Melhor: O(n log n)
- Médio/Pior: O(n²)
- Espaço: O(1)
- Estável: Não

```javascript
export function combSort(input) {
  const arr = [...input];
  const fator = 1.3;
  let gap = arr.length;
  let trocou = true;

  while (gap > 1 || trocou) {
    gap = Math.floor(gap / fator);
    if (gap < 1) gap = 1;

    trocou = false;

    for (let i = 0; i + gap < arr.length; i++) {
      if (arr[i] > arr[i + gap]) {
        [arr[i], arr[i + gap]] = [arr[i + gap], arr[i]];
        trocou = true;
      }
    }
  }

  return arr;
}
```

## 12) Cocktail Shaker Sort

Variação do Bubble Sort que percorre em dois sentidos (ida e volta), movendo maiores e menores em cada ciclo.

- Melhor: O(n)
- Médio/Pior: O(n²)
- Espaço: O(1)
- Estável: Sim

```javascript
export function cocktailShakerSort(input) {
  const arr = [...input];
  let inicio = 0;
  let fim = arr.length - 1;
  let trocou = true;

  while (trocou) {
    trocou = false;

    for (let i = inicio; i < fim; i++) {
      if (arr[i] > arr[i + 1]) {
        [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]];
        trocou = true;
      }
    }

    if (!trocou) break;
    trocou = false;
    fim--;

    for (let i = fim; i > inicio; i--) {
      if (arr[i - 1] > arr[i]) {
        [arr[i - 1], arr[i]] = [arr[i], arr[i - 1]];
        trocou = true;
      }
    }

    inicio++;
  }

  return arr;
}
```

## 13) Gnome Sort

Parecido com Insertion Sort, caminhando para trás quando encontra inversões e para frente quando está em ordem.

- Melhor: O(n)
- Médio/Pior: O(n²)
- Espaço: O(1)
- Estável: Sim

```javascript
export function gnomeSort(input) {
  const arr = [...input];
  let i = 1;

  while (i < arr.length) {
    if (i === 0 || arr[i - 1] <= arr[i]) {
      i++;
    } else {
      [arr[i], arr[i - 1]] = [arr[i - 1], arr[i]];
      i--;
    }
  }

  return arr;
}
```

## 14) Tim Sort (versão didática simplificada)

Algoritmo híbrido usado em engines reais (como Python e Java). Combina Insertion Sort em pequenos blocos e Merge Sort para juntar blocos.

- Melhor: O(n)
- Médio/Pior: O(n log n)
- Espaço: O(n)
- Estável: Sim

```javascript
export function timSortDidatico(input) {
  const arr = [...input];
  const RUN = 32;

  function insertionRange(esq, dir) {
    for (let i = esq + 1; i <= dir; i++) {
      const temp = arr[i];
      let j = i - 1;
      while (j >= esq && arr[j] > temp) {
        arr[j + 1] = arr[j];
        j--;
      }
      arr[j + 1] = temp;
    }
  }

  function merge(esq, meio, dir) {
    const esquerda = arr.slice(esq, meio + 1);
    const direita = arr.slice(meio + 1, dir + 1);

    let i = 0;
    let j = 0;
    let k = esq;

    while (i < esquerda.length && j < direita.length) {
      if (esquerda[i] <= direita[j]) arr[k++] = esquerda[i++];
      else arr[k++] = direita[j++];
    }

    while (i < esquerda.length) arr[k++] = esquerda[i++];
    while (j < direita.length) arr[k++] = direita[j++];
  }

  for (let i = 0; i < arr.length; i += RUN) {
    insertionRange(i, Math.min(i + RUN - 1, arr.length - 1));
  }

  for (let size = RUN; size < arr.length; size *= 2) {
    for (let esq = 0; esq < arr.length; esq += 2 * size) {
      const meio = Math.min(esq + size - 1, arr.length - 1);
      const dir = Math.min(esq + 2 * size - 1, arr.length - 1);
      if (meio < dir) merge(esq, meio, dir);
    }
  }

  return arr;
}
```

---

## Resumo rápido para uso pedagógico

- Começo didático (conceitos de comparação e troca): Bubble, Selection, Insertion.
- Transição para eficiência: Merge e Quick.
- Estruturas/variações importantes: Heap e Shell.
- Ordenação não baseada em comparação (cenários específicos): Counting, Radix e Bucket.
- Extensões para desafio e curiosidade: Comb, Cocktail, Gnome e Tim Sort simplificado.

## Observação importante

Não existe um único algoritmo "melhor" para todos os cenários. A escolha depende de:

- tamanho da lista,
- distribuição dos dados,
- necessidade de estabilidade,
- limite de memória,
- custo de implementação/ensino.
