La bandiera nazionale olandese, i cui tre colori danno il nome al problema.

Il problema della bandiera nazionale olandese (in inglese Dutch national flag problem) è un celebre problema computazionale inerente all'ordinamento, formulato per la prima volta nel 1976 dall'informatico olandese Edsger Dijkstra nel suo libro A Discipline of Programming.[1]

Il problema prende il nome dalla bandiera dei Paesi Bassi, che è composta da tre bande orizzontali di colori diversi: rosso (in alto), bianco (al centro) e blu (in basso). La formulazione classica richiede di immaginare una fila disordinata di palline di questi tre colori: il compito dell'algoritmo è quello di disporle in modo tale che tutte le palline dello stesso colore siano adiacenti e che i rispettivi gruppi di colori seguano l'ordine corretto (rosso, poi bianco, poi blu).

Dal punto di vista informatico, rappresenta un problema di ordinamento e partizionamento di un array i cui elementi possono assumere solo tre valori distinti (o appartenere a tre categorie prestabilite). La sua soluzione ottima è un algoritmo che opera in-place (senza memoria aggiuntiva) e in un'unica passata (complessità temporale lineare).

Il problema e l'approccio algoritmico

modifica

In termini formali, il problema consiste nell'ordinare un array in cui ogni elemento appartiene a una di tre categorie (ad esempio: elementi minori, uguali o maggiori rispetto a un dato valore, oppure tre chiavi distinte come , e ). L'obiettivo è riordinare l'array in modo che tutti gli elementi della prima categoria precedano quelli della seconda, che a loro volta precedono quelli della terza.

visualizzazione del problema della bandiera olandese

Invariante di ciclo

modifica

La soluzione proposta da Dijkstra risolve il problema mantenendo tre puntatori (o indici) che scandiscono l'array, suddividendolo logicamente in quattro sotto-sequenze. Chiamiamo i tre indici (limite superiore del primo gruppo), (elemento corrente in esame) e (limite inferiore del terzo gruppo).

Durante l'esecuzione, l'invariante di ciclo garantisce le seguenti proprietà, assumendo un array con indici da a :

  • Gli elementi dall'indice fino a appartengono al primo gruppo (es. Rosso / Minori).
  • Gli elementi dall'indice fino a appartengono al secondo gruppo (es. Bianco / Uguali).
  • Gli elementi dall'indice fino a non sono ancora stati esaminati (gruppo misto).
  • Gli elementi dall'indice fino a appartengono al terzo gruppo (es. Blu / Maggiori).

Ad ogni iterazione, l'elemento di indice viene esaminato, il che porta a tre casi possibili:

  1. Se appartiene al primo gruppo, viene scambiato con l'elemento di indice , ed entrambi gli indici e vengono incrementati.
  2. Se appartiene al terzo gruppo, viene scambiato con l'elemento all'indice , e solo l'indice viene decrementato (l'indice non viene incrementato perché il nuovo elemento scambiato deve ancora essere valutato).
  3. Se appartiene al secondo gruppo (quello centrale), si trova già nella posizione corretta rispetto a quanto esaminato finora, quindi si incrementa semplicemente l'indice .

L'algoritmo termina quando , ovvero quando non ci sono più elementi da esaminare e la funzione di terminazione (la distanza ) raggiunge lo zero o diventa negativa.

Pseudocodice

modifica

Di seguito lo pseudocodice per il partizionamento a tre vie di un array A. Si suppone di voler ordinare gli elementi rispetto a un valore di riferimento (il "perno" o mid_value):

procedura bandiera_olandese(A : array di valori, mid_value : valore):
    i ← 0
    j ← 0
    k ← dimensione(A) - 1

    mentre j <= k:
        se A[j] < mid_value:
            scambia(A[i], A[j])
            i ← i + 1
            j ← j + 1
        altrimenti se A[j] > mid_value:
            scambia(A[j], A[k])
            k ← k - 1
        altrimenti:
            j ← j + 1

Analisi della complessità

modifica
  • Complessità temporale: . L'algoritmo compie esattamente un'ispezione per ogni elemento dell'array (una singola passata). Ad ogni iterazione del ciclo mentre, o aumenta o diminuisce, riducendo strettamente il numero di elementi non esaminati.
  • Complessità spaziale: (o ). L'algoritmo opera rigorosamente in-place, utilizzando solo memoria costante per i tre indici e per le operazioni di scambio, senza richiedere array ausiliari.

Confronto con altri algoritmi e applicazioni

modifica

Relazione con il Quicksort

modifica

La soluzione alla bandiera nazionale olandese riveste un'importanza fondamentale nell'ottimizzazione dell'algoritmo Quicksort. Le implementazioni standard del Quicksort utilizzano un partizionamento a due vie (minori e maggiori del pivot) che risulta inefficiente e degrada a una complessità di se l'array contiene molti elementi ripetuti o tutti uguali. L'introduzione del partizionamento a tre vie (minori, uguali e maggiori del pivot), basato esattamente sull'algoritmo della bandiera olandese, rende il Quicksort altamente robusto agli elementi duplicati. Una variante famosa che sfrutta questa tecnica è il Multi-key Quicksort, utilizzato per l'ordinamento rapido di stringhe.[2]

Confronto con il Counting Sort

modifica

Se il numero totale di categorie (colori) è superiore a 3, ma pur sempre limitato a una costante , è possibile utilizzare un algoritmo di Counting sort. Quest'ultimo scorre prima l'array per contare le occorrenze di ogni colore, e in una seconda fase sovrascrive l'array con le quantità esatte per ciascun colore in ordine. Sebbene anche il Counting Sort garantisca un tempo lineare , la soluzione di Dijkstra (Bandiera Olandese) per il caso a 3 colori è spesso preferibile perché:

  1. Effettua una sola passata sui dati (contro le due richieste dal Counting Sort).
  2. Esegue solo scambi sui dati reali, risultando l'ideale nei casi in cui l'array non contiene semplici chiavi, ma oggetti complessi in cui la sostituzione distruttiva (o la copia) risulterebbe onerosa o pericolosa.

Va notato, tuttavia, che man mano che il numero di categorie cresce diventando nell'ordine di grandezza di , il problema generalizzato non è più risolvibile con questo approccio lineare ma ricade nella normale complessità teorica del sorting comparativo, pari a .

Voci correlate

modifica

Note

modifica
  1. ^ Edsger W. Dijkstra, A Discipline of Programming, Prentice-Hall, 1976.
  2. ^ Eunsang Kim e Kunsoo Park, Improving multikey Quicksort for sorting strings with many equal elements, in Information Processing Letters, 109, fascicolo 9, 2009, pp. 454–459, DOI:10.1016/j.ipl.2009.01.007.

Collegamenti esterni

modifica
  Portale Informatica: accedi alle voci di Wikipedia che trattano di Informatica

📚 Artikel Terkait di Wikipedia

Metodo degli elementi finiti

\left[{\begin{array}{c}U_{1}\\U_{2}\\\vdots \\U_{m}\end{array}}\right]=\left[{\begin{array}{c}f_{1}\\f_{2}\\\vdots \\f_{m}\end{array}}\right],} dove

Array dinamico

sull'array dinamico NIST Dictionary of Algorithms and Data Structures: Dynamic array, su nist.gov. VPOOL - C language implementation of dynamic array

Struttura dati

addirittura nello stesso linguaggio. Lo stesso argomento in dettaglio: Array. Un array è una struttura dati omogenea, che contiene un numero finito di elementi

Codice Gray

On-line Dictionary of Computing. Disponibile con licenza GFDL (EN) Paul E. Black, Gray code, in Dictionary of Algorithms and Data Structures, NIST, 22 gennaio

Lista concatenata

precedente) nell'array. Non tutti i nodi nell'array devono esser usati. Se neppure i record sono supportati, possono esser usati gli array paralleli. Come

Ada (linguaggio di programmazione)

A: array (Integer range 1..5, Integer range 2..7) of Float; -- array di dimensione 2 B: array (Integer range 2..7) of array (Integer range 1..5) of Float;

Teoria della plasticità

Z. Bazant, Inelastic Analysis of Structures, Wiley, 2001, ISBN 0-471-98716-6. Yehia M. Haddad, "Mechanical Behaviour of Engineering Materials", vol. 1

Circuito integrato

Devaiya e Marvin H. Zoellner, Adaptive Epitaxy of C-Si-Ge-Sn: Customizable Bulk and Quantum Structures, in Advanced Materials, n/a, n/a, pp. 2506919,