Teoria+Esercizi di Sistemi Algoritmi di ordinamento Parte I. Teoria 1 Introduzione L’ordinamento di una sequenza di numeri (o di stringhe) è un problema solo apparentemente semplice, per un motivo principalmente pratico. Gli ordinamenti sono spesso usati in applicazioni che hanno bisogno di un risultato in tempo reale oppure in tempi ridotti (interfacce grafiche o database), quindi l’algoritmo di ordinamento usato deve essere molto veloce. La lentezza degli algoritmi più banali ha spesso portato all’elaborazione di nuovi algoritmi, tanto che oggi se ne contano almeno una decina. Tratteremo approfonditamente solo gli algoritmi più semplici. Tutti questi algoritmi operano sugli array. 2 Algoritmi principali 2.1 Bubble Sort Il B S (ordinamento a bolla) è uno dei più semplici algoritmi di ordinamento, ma è anche uno dei più lenti. 2.1.1 Algoritmo standard 1. Assumiamo che nessun elemento nell’array sia in ordine. 5 2 9 1 4 2. Consideriamo i primi due elementi dell’array. 5 2 9 1 4 3. Li confronto: se il primo è maggiore del secondo, li scambio. 5>2, perciò: 5 2 9 1 4 2 5 9 1 4 4. Considero il paio di elementi successivo e li scambio come al passaggio 3. Vado avanti così finchè non ho finito di leggere l’array. 2 5 9 1 4 2 5 9 1 4 2 5 1 1 9 4 2 5 1 4 9 5. Ora, l’elemento più grande dell’array è sicuramente ordinato in fondo; infatti, essendo maggiore di tutti gli altri, è stato sempre scambiato fino alla fine dell’array. Ripartiamo dal punto 2, considerando la parte di array non ordinata, a meno che tutti gli elementi tranne uno non siano già ordinati. 2 5 1 4 9 2 1 4 5 9 1 2 4 5 9 1 2 4 5 9 6. Se tutti gli altri elementi sono ordinati anche questo è ordinato. Quindi l’array è ordinato. 1 2 4 5 9 Lepri, tartarughe e bolle Come si può vedere, la lettura dell’array procede lentamente in più passaggi; il percorso somiglia a quello di una bolla d’aria che, dal fondo, lentamente arriva alla superficie dell’acqua. Il nome dell’algoritmo deriva da questa caratteristica. Inoltre, gli elementi che devono migrare verso la fine dell’array si spostano più velocemente, perchè il percorso che devono seguire è lo stesso di quello compiuto dall’algoritmo; per questo, tali elementi vengono chiamati lepri. Gli elementi che invece si devono spostare nella direzione contraria sono molto lenti, perciò vengono chiamati tartarughe. 2.1.2 Varianti L’estrema semplicità del Bubble Sort controbilancia la sua lentezza: infatti è spesso usato su array piccoli. Per migliorare la velocità dell’algoritmo sono nate diverse varianti: • Al posto di interrompere il processo quando non rimangono implicitamente elementi da ordinare, viene svolto un controllo esplicito: l’algoritmo si ferma quando non vengono registrati scambi durante la lettura. • Invece di leggere l’array a senso unico, la lettura viene eseguita a senso alternato. Questo accorgimento uniforma la velocità delle tartarughe a quella delle lepri. 2.1.3 Implementazione in Pascal program BubbleSort; const MAX = 32; var i, j: integer; temp: integer; Vett: array [1..MAX] of integer; begin {Riempimento dell'array} randomize; for i:=1 to MAX do Vett[i] := random(128); {Ordinamento} for i:=MAX-1 downto 1 do for j:=1 to i do if Vett[j] > Vett[j+1] then begin temp := Vett[j]; Vett[j] := Vett[j+1]; Vett[j+1] := temp; end; 2 {Scrittura dell'array} for i:=1 to MAX do write(Vett[i]:4); end. 2.2 Selection Sort Il S S (ordinamento a selezione), talvolta abbreviato in S-S, è un altro semplice algoritmo. È molto lento, ma necessita di scambiare i valori meno spesso rispetto al Bubble Sort, quindi, nella pratica, è leggermente più veloce. 2.2.1 Algoritmo Standard 1. Assumiamo che nessun elemento nell’array sia in ordine. 5 2 9 1 4 2. Operiamo sul primo elemento dell’array. 5 2 9 1 4 3. Confronto il primo elemento con quelli successivi. Se il primo elemento è maggiore del secondo, li inverto prima di continuare. 5 2 9 1 4 2 5 9 1 4 2 5 9 1 4 1 5 9 2 4 4. A questo punto il primo elemento è in ordine. 1 5 9 2 4 5. Torno al passaggio 3 operando sull’elemento successivo, a meno che non sia l’ultimo elemento. 1 5 9 2 4 1 2 9 5 4 1 2 4 9 5 1 2 4 5 9 6. Siccome tutti gli altri elementi sono in ordine, anche l’ultimo elemento è in ordine. Quindi tutto l’array è ordinato. 1 2.2.2 2 4 5 9 Varianti Nonostante il leggero (e non costante) vantaggio rispetto al Bubble Sort, il Selection Sort è comunque molto lento. È possibile migliorarne marginalmente la velocità applicando le seguenti varianti: • Quando si confronta un elemento con quelli successivi, in linea di massima si sta stabilendo qual è il minore tra essi. Quindi, al posto di effettuare uno scambio ogni volta che si trova un numero minore del primo, si può effettuare un solo scambio tra il primo elemento e il minore tra gli elementi successivi (se viene trovato). • Normalmente, gli elementi ordinati si accumulano dal minore al maggiore. È possibile accumularli contemporaneamente anche dal maggiore al minore, leggendo l’array una volta nel senso normale, una volta nel senso contrario, e così via. 3 2.2.3 Implementazione in Pascal program SelectionSort; const MAX = 256; var i, j: integer; temp: integer; Vett: array [1..MAX] of integer; begin {Riempimento dell'array. Qui usiamo dei numeri casuali.} randomize; for i:=1 to MAX do Vett[i] := random(128); {Ordinamento.} for i:=1 to MAX-1 do for j:=i+1 to MAX do if Vett[i] > Vett[j] then begin temp := Vett[i]; Vett[i] := Vett[j]; Vett[j] := temp; end; {Visualizzazione dell'array ordinato} for i:=1 to MAX do write(Vett[i]:4); end. 2.3 Note di applicazione 2.3.1 Ordinamenti su array non di numeri È chiaro come questi ordinamenti possano lavorare su dati di tipo numerico (in Pascal, di tipo integer oppure real), ma talvolta è necessario ordinare dati non numerici, come delle stringhe. In Pascal, gli operatori >, < e = funzionano su qualsiasi tipo di dato, seguendo dei criteri di ordinamento convenzionale. Nella maggior parte dei casi, dato che nella memoria del computer qualsiasi tipo di dato è memorizzato come un numero, il confronto di variabili non numeriche viene effettuato sulla rappresentazione in memoria di queste variabili. Nel caso di char e string, i numeri corrispondenti ai vari caratteri sono stabiliti dallo standard (American Standard Code for Information Interchange), e l’ordinamento viene stabilito in base a questo codice. Vedi la dispensa Teoria+Esercizi di Sistemi: Stringhe, paragrafo 1.2.2, per maggiori dettagli su come funziona il confronto. 2.3.2 Ordinamenti in senso decrescente Finora abbiamo visto algoritmi che ordinano un array in ordine crescente, ma non è difficile ottenere, da questi algoritmi, la loro controparte che ordina in modo decrescente: basta sostituire i segni > con dei <, e viceversa. 2.3.3 Determinazione del tempo di esecuzione Per conoscere precisamente quanto tempo ha impiegato un algoritmo di ordinamento a portare a termine l’operazione, è possibile utilizzare la funzione GetTime: Utilizzo: GetTime(Ore, Minuti, Secondi, Centesimi); 4 dove Ore, Minuti, Secondi e Centesimi sono le variabili (rigorosamente di tipo word, variante del tipo integer) che riceveranno i dati dell’orario in vigore nel momento in cui la funzione viene utilizzata. La funzione GetTime si trova nell’unità1 Dos, quindi, per poterla usare, è necessario aggiungere la seguente riga al di sotto della clausola program: Uses Dos; Si può chiamare GetTime prima e dopo l’inizio dell’ordinamento, sottraendo poi i due orari, per sapere quanto tempo ha impiegato il processo ad eseguire. 3 Altri algoritmi Proponiamo una rapida carrellata di altri algoritmi di ordinamento spesso usati in contesti reali. 3.1 Insertion Sort Nell’I S (ordinamento ad inserimento) l’array ordinato viene costruito un elemento alla volta. Ogni elemento viene prelevato in sequenza, confrontato con quelli precedenti, e posizionato tra l’elemento minore più prossimo e quello maggiore pìù prossimo. Ciò implica che, per ogni spostamento, parte dell’array dovrà essere spostato verso destra. 3.2 Merge Sort L’array, nel M S (ordinamento a integrazione) viene suddiviso a metà ripetutamente, fino a ottenere tanti piccoli array di un elemento. Poi, questi array vengono riuniti a cascata, in modo da mantenere, in ogni passaggio successivo, l’ordine crescente degli elementi. Al termine di questo processo, si ottiene un unico array ordinato. 3.3 QuickSort Il QS (ordinamento veloce) consiste nel dividere ripetutamente l’array a metà; ogni volta che viene divisa una metà, gli elementi minori di quello centrale vengono messi nella metà sinistra, e gli elementi maggiori alla destra. Ogni metà viene divisa a sua volta finchè non si ottiene una parte lunga meno di tre elementi. Al termine del processo l’array è ordinato. Parte II. Esercizi 1. RRRQ Scrivere un programma che metta in ordine crescente una sequenza di 10 numeri, inserita da tastiera, utilizzando l’algoritmo B S. 2. RRQQ Scrivere un programma che metta in ordine decrescente una sequenza di 15 numeri, scelta casualmente, utilizzando l’algoritmo S S. 3. RRQQ Scrivere un programma che, messa in ordine crescente una sequenza di 15 numeri, inserita da tastiera, con un algoritmo a piacere, trovi quante ripetizioni del numero 5 si trovano nell’array. 1 Un’unità o libreria è una collezione di funzioni, già pronte da poter usare. Il Pascal carica, per ogni programma, la libreria System, che include le funzioni writeln, readln, le stringhe e altro ancora. 5 4. RRQQ Scrivere un programma che ordini una sequenza di 64 numeri, scelta casualmente, in ordine decrescente, utilizzando sia l’algoritmo S S che l’algoritmo B S, verificando che entrambi gli algoritmi diano come risultato lo stesso array. 5. RQQQ Scrivere un programma che, mettendo in ordine crescente una sequenza di 64 numeri, scelta casualmente dal programma, utilizzando l’algoritmo S S, calcoli quanti scambi sono stati necessari per eseguire l’ordinamento. 6. RQQQ Scrivere un programma che, dati in input da tastiera 10 nomi, li riscriva in ordine alfabetico. Usare l’algoritmo B S. 7. RQQQ Scrivere un programma che, dopo aver messo in ordine crescente 128 numeri scelti casualmente da 1 a 10, scriva quante volte compare ogni numero nell’array. Usare l’algoritmo S S. 8. QQQQ Scrivere un programma che, dopo aver messo in ordine crescente 128 numeri scelti casualmente da 1 a 10, scriva quante volte compare ogni numero nell’array. Usare un algoritmo a piacere. 6