Salta al contenuto
Note per Studenti Algoritmi di ordinamento

Algoritmi di ordinamento

In questa pagina 7

Problema: disporre gli elementi di una lista in ordine non decrescente. Tutti gli algoritmi qui sotto ordinano per confronti (<, <=), quindi funzionano con numeri, stringhe, tuple.

Proprietà da considerare:

Selection sort

A ogni passo i si cerca il minimo di v[i:] e lo si scambia con v[i].

python
def selection_sort(v):
    n = len(v)
    for i in range(n - 1):
        im = i
        for j in range(i + 1, n):
            if v[j] < v[im]:
                im = j
        v[i], v[im] = v[im], v[i]
  • Invariante: v[:i] contiene gli i elementi più piccoli, in ordine.
  • Confronti: n(n−1)/2n(n-1)/2 sempre → O(n2)O(n^2) in tutti i casi. Al più n−1n - 1 scambi.
  • Sul posto, non stabile.

Insertion sort

Si scorre la lista; ogni elemento viene inserito al posto giusto nella parte iniziale già ordinata, spostando a destra i maggiori.

python
def insertion_sort(v):
    for i in range(1, len(v)):
        x = v[i]
        j = i - 1
        while j >= 0 and v[j] > x:
            v[j + 1] = v[j]
            j -= 1
        v[j + 1] = x
  • Invariante: v[:i] è ordinata (ma contiene gli elementi originali di v[:i], non i più piccoli).
  • Caso peggiore (lista al contrario) O(n2)O(n^2); caso migliore (già ordinata) O(n)O(n): molto buono su liste quasi ordinate.
  • Sul posto, stabile (grazie a > stretto nel while).

Bubble sort

Si scorrono le coppie adiacenti scambiando quelle fuori ordine; dopo il passo k gli ultimi k elementi sono definitivi. Con un flag ci si ferma se in un passo non ci sono scambi.

python
def bubble_sort(v):
    n = len(v)
    for k in range(n - 1):
        scambi = False
        for j in range(n - 1 - k):
            if v[j] > v[j + 1]:
                v[j], v[j + 1] = v[j + 1], v[j]
                scambi = True
        if not scambi:
            break

O(n2)O(n^2) nel caso peggiore, O(n)O(n) se già ordinata; sul posto e stabile. Più lento di insertion sort in pratica.

Merge sort

Divide et impera (RicorsioneFunzioni che chiamano se stesse: caso base e passo ricorsivo, stack delle chiamate, esempi su numeri, stringhe e liste, ricorsione multipla e suo costo, divide et impera, confronto con l'iterazione.Ricorsione →): si divide la lista a metà, si ordinano ricorsivamente le due metà, si fondono le due metà ordinate (fusione con due indici, vedi Pattern algoritmici iterativiSchemi ricorrenti nei cicli: accumulatore, contatore, massimo e minimo, ricerca con uscita anticipata, verifica universale, filtro e trasformazione, finestra scorrevole, due indici, elaborazione di coppie.Pattern algoritmici iterativi →).

python
def merge_sort(v):
    if len(v) <= 1:
        return v
    m = len(v) // 2
    a = merge_sort(v[:m])
    b = merge_sort(v[m:])
    # fusione
    out, i, j = [], 0, 0
    while i < len(a) and j < len(b):
        if a[i] <= b[j]:        # <= rende l'algoritmo stabile
            out.append(a[i]); i += 1
        else:
            out.append(b[j]); j += 1
    out.extend(a[i:])
    out.extend(b[j:])
    return out
  • Costo T(n)=2T(n/2)+O(n)T(n) = 2T(n/2) + O(n) → O(nlog⁡n)O(n \log n) in tutti i casi: log⁡2n\log_2 n livelli di ricorsione, ciascuno con lavoro complessivo O(n)O(n) per le fusioni.
  • Memoria aggiuntiva O(n)O(n) (non sul posto); stabile.
  • Questa versione restituisce una lista nuova: v = merge_sort(v).

Confronto

Algoritmo Peggiore Migliore Sul posto Stabile
Selection sort O(n2)O(n^2) O(n2)O(n^2) sì no
Insertion sort O(n2)O(n^2) O(n)O(n) sì sì
Bubble sort (con flag) O(n2)O(n^2) O(n)O(n) sì sì
Merge sort O(nlog⁡n)O(n \log n) O(nlog⁡n)O(n \log n) no sì

Nessun ordinamento per confronti può fare meglio di O(nlog⁡n)O(n \log n) nel caso peggiore.

In Python: sort e sorted

Nella pratica si usa l'ordinamento predefinito (Timsort: O(nlog⁡n)O(n \log n), stabile, O(n)O(n) su dati già ordinati).

python
v.sort()                                  # sul posto, restituisce None
w = sorted(v)                             # nuova lista
sorted(v, reverse=True)                   # decrescente
sorted(parole, key=str.lower)             # ignorando maiuscole/minuscole
sorted(studenti, key=lambda s: (-s[1], s[0]))   # voto decrescente, poi nome

key è una funzione applicata a ogni elemento: si confrontano i risultati. La stabilità permette ordinamenti a più livelli in passaggi successivi.

Errori tipici

Teoria collegata