Bubble sort: differenze tra le versioni

Contenuto cancellato Contenuto aggiunto
Nessun oggetto della modifica
Ortografia
 
(392 versioni intermedie di oltre 100 utenti non mostrate)
Riga 1:
{{F|programmazione|luglio 2012}}
{{da controllare|firma=[[Utente:DanGarb|DanGarb]]|motivo=ci sono errori in alcune implementazioni (vedi discussione)}}
{{Algoritmo
Il '''bubble sort''' o '''bubblesort''' (letteralmente: ''ordinamento a bollicine'') è un semplice [[algoritmo]] [[algoritmo di ordinamento|di ordinamento]] particolarmente indicato per l'ordinamento di [[array]]. Pur non essendo un algoritmo particolarmente efficiente (ha una [[complessità computazionale]] ''O(n²)'', molto superiore per esempio a quella del [[quicksort]]), è piuttosto noto e utilizzato (sia in ambito didattico che da parte di programmatori professionisti) in virtù della sua semplicità. Dell'algoritmo esistono numerose varianti, in alcuni casi sufficientemente note da aver meritato un nome distinto (per esempio, lo [[shakersort]]). Come tutti gli algoritmi di ordinamento, può essere usato per ordinare dati di un qualsiasi tipo su cui sia definita una [[relazione d'ordine]]; a fini illustrativi, in questo articolo ci riferiremo all'ordinamento di un array di [[numero intero|numeri interi]]. Il nome dell'algoritmo è dovuto al fatto che, durante l'applicazione del procedimento, i valori vengono spostati all'interno dell'array con una dinamica che ricorda il movimento delle bollicine in un bicchiere di [[champagne]]. In particolare, alcuni elementi attraversano l'array velocemente (come bollicine che emergono dal fondo del bicchiere), altri più lentamente (a differenza di quanto avviene nel caso del bicchiere di champagne, tuttavia, alcuni elementi ''salgono'' ma altri ''scendono'').
|classe = [[Algoritmo di ordinamento]]
|immagine = Sorting bubblesort anim.gif
|didascalia = Bubble sort in esecuzione
|struttura dati = [[Array]]
|tempo migliore = <math>O(n^2)</math>
Se si utilizza una guardia allora: <math>O(n)</math>
|tempo medio = <math>O(n^2)</math>
|tempo = <math>O(n^2)</math>
|spazio = <math>O(n)</math> totale, <math>O(1)</math> ausiliario
|ottimale = No
|completo =
}}
[[File:Bubblesort-edited-color.svg|alt=Bubble sort colore modificato|frame|Svolgimento dell'algoritmo Bubblesort, versione colorata]]
In [[informatica]] il '''Bubble sort''' o ''ordinamento a bolla'' è un semplice [[algoritmo di ordinamento]] di liste di dati. In esso l'insieme di dati viene scansionato, ogni coppia di elementi adiacenti viene comparata ed i due elementi vengono invertiti di posizione se sono nell'ordine sbagliato. L'algoritmo continua a ri-eseguire questi passaggi su tutta la lista fino a quando non vengono più eseguiti scambi, situazione che indica che la lista è ordinata.<ref>
{{Cita web
|url = https://stackoverflow.com/questions/tagged/bubble-sort
|titolo = Newest 'bubble sort' Questions
|sito = Stack Overflow
|citazione = Bubble sort is a simple sorting algorithm that works by repeatedly stepping through the list to be sorted, comparing each pair of adjacent items and swapping them if they are in the wrong order. The pass through the list is repeated until no swaps are needed, which indicates that the list is sorted.
|lingua = en
}}</ref>
 
== Denominazione ed efficienza ==
==Spiegazione astratta==
L'algoritmo deve il suo nome al modo in cui gli elementi vengono ordinati: quelli più piccoli "risalgono" verso un'estremità della lista, mentre quelli più grandi "affondano" verso l'estremità opposta della lista, come le bolle in un bicchiere contenente una bevanda frizzante. In particolare, alcuni elementi attraversano la lista velocemente (in gergo detti "''lepri''"), altri invece più lentamente (detti "''tartarughe''"). I primi vengono spostati nella stessa direzione in cui scorre l'indice dell'algoritmo, mentre i secondi nella direzione opposta.
 
Come tutti gli algoritmi di ordinamento, può essere usato per ordinare dati di qualsiasi tipo per i quali sia definita una [[relazione d'ordine]].
Il bubblesort è un algoritmo [[algoritmo iterativo|iterativo]], ovvero basato sulla ripetizione di un procedimento fondamentale. La singola iterazione dell'algoritmo prevede che gli elementi dell'array siano confrontati a due a due, scandendo l'algoritmo in un verso stabilito (se si scandisca l'array a partire dall'inizio in avanti, o a partire dal fondo all'indietro, è irrilevante; nel seguito ipotizzeremo che lo si scandisca partendo dall'inizio; supporremo inoltre che si voglia ordinare l'array in modo crescente). Per esempio, saranno confrontati il primo e il secondo elemento, poi il secondo e il terzo, poi il terzo e il quarto, e così via fino al confronto fra penultimo e ultimo elemento. Per ogni confronto, se i due elementi confrontati non sono ordinati, essi vengono scambiati. Durante ogni iterazione, almeno un valore viene spostato rapidamente fino a raggiungere la sua collocazione definitiva; in particolare, alla prima iterazione il numero più grande raggiunge l'ultima posizione dell'array. Il motivo è semplice, e si può illustrare con un esempio. Supponiamo che l'array sia inizialmente disposto come segue:
 
Il Bubble sort è più efficiente rispetto al più semplice algoritmo di Ordinamento Ingenuo perché, invece di continuare ad eseguire sempre fino alla fine i due cicli annidati, si interrompe quando l'ordinamento è completo. Si tratta comunque di un algoritmo non particolarmente efficiente, presentando una [[Teoria della complessità computazionale|complessità computazionale]] dell'ordine di ''[[O-grande|O]]<math>(n^2)</math>'' confronti con ''n'' elementi da ordinare, pertanto il suo utilizzo si limita a scopi didattici in virtù della sua semplicità e per introdurre i futuri programmatori al ragionamento algoritmico e alle misure di complessità.<ref>{{Cita libro|autore = Donald Knuth|titolo = The Art of Computer Programming Vol. 3|anno = 1973|editore = Addison Wesley|città = }}</ref>
 
Dell'algoritmo esistono numerose [[#Varianti e ottimizzazioni|varianti]], per esempio lo [[shaker sort]].
 
== Spiegazione astratta ==
[[File:Bubble-sort-example-300px.gif|upright=1.6|thumb|Esempio di ''bubble sort'' che ordina gli elementi in senso crescente. Partendo dall'inizio della lista e scorrendola progressivamente, si confrontano due elementi adiacenti e si scambiano le loro posizioni se non sono nel giusto ordine (ossia se il secondo elemento è minore del primo). Dopo la prima [[iterazione]] il massimo si troverà in ultima posizione, quella sua definitiva; dopo la seconda iterazione il secondo massimo si troverà in penultima posizione, anch'essa la sua definitiva; dopo la terza iterazione il terzo massimo si troverà in terz'ultima posizione, anch'essa la sua definitiva e così via. Ad ogni iterazione almeno un elemento si va a collocare nella sua giusta posizione: la procedura termina quando non ci sono più elementi da scambiare. Si noti infine come, per esempio, l'8 sia un ''coniglio'' che si muove velocemente verso destra, mentre il 2 sia una ''tartaruga'' che si muove lentamente verso sinistra.]]
Il bubble sort è un [[algoritmo iterativo]], ossia basato sulla ripetizione di un procedimento fondamentale. La singola iterazione dell'algoritmo prevede che gli elementi dell'array siano confrontati a due a due, procedendo in un verso stabilito (che si scorra l'array a partire dall'inizio in avanti, o a partire dal fondo all'indietro, è irrilevante; d'ora in poi ipotizzeremo che lo si scorra partendo dall'inizio).
 
Per esempio, saranno confrontati il primo e il secondo elemento, poi il secondo e il terzo, poi il terzo e il quarto e così via fino al confronto fra il penultimo e l'ultimo elemento. Ad ogni confronto, se i due elementi confrontati non sono ordinati secondo il criterio prescelto, vengono scambiati di posizione. Durante ogni iterazione almeno un valore viene spostato rapidamente fino a raggiungere la sua collocazione definitiva; in particolare, alla prima iterazione il numero più grande raggiunge l'ultima posizione dell'array, alla seconda il secondo numero più grande raggiunge la penultima posizione e così via.
 
Il motivo è semplice e si può illustrare con un esempio. Si supponga che l'array sia inizialmente disposto come segue:
 
15 6 4 10 11 2
Riga 11 ⟶ 45:
 
6 15 4 10 11 2
 
A questo punto il 15 viene confrontato col 4, e nuovamente scambiato:
 
Riga 19 ⟶ 53:
 
# se i numeri sono in tutto N, dopo N-1 iterazioni si avrà la garanzia che l'array sia ordinato;
# alla all'iterazione i-esima, le ultime i-1 celle dell'array ospitano i loro valori definitivi, per cui la sequenza di confronti può essere terminata col confronto deldei valorevalori dialle posizioneposizioni N-+1-i e N-i.
 
Ovviamente, a ogni iterazione può accadere che più numeri vengano spostati; per cui, oltre a portare il numero più grande in fondo all'array, ogni singola iterazione può contribuire anche a un riordinamento parziale degli altri valori. Anche per questo motivo, può accadere (e normalmente accade) che l'array risulti effettivamente ordinato ''prima'' che si sia raggiunta la N-1esima iterazione. Su questa osservazione sono basate alcunele versioni ottimizzazioniottimizzate dell'algoritmo.
 
=== Analisi dell'algoritmo ===
L'algoritmo comincia con un ciclo in cui ogni elemento, a partire dall'ultimo, è confrontato con l'elemento che lo precede; se i due elementi non si trovano nell'ordine esatto vengono scambiati. Alla fine del primo ciclo inevitabilmente l'elemento 'più leggero' come una bolla sarà risalito in prima posizione. Il secondo ciclo esegue le stesse operazioni escudendo il primo elemento e così facendo risalire il secondo elemento 'più leggero' e così si prosegue fino all'orinamento completo. Se N è la lunghezza del array, occorreranno N-1 di questi cicli.
Il bubble sort effettua all'incirca <math>\frac{N^2}{2}</math> confronti ed <math>\frac{N^2}{2}</math> scambi sia in media che nel caso peggiore.
Il tempo di esecuzione dell'algoritmo è <math>\Theta(n^2)</math>.
 
=== Varianti e ottimizzazioni ===
==Implementazioni==
Esistono numerose varianti del bubble sort, molte delle quali possono essere definite ottimizzazioni, in quanto mirano a ottenere lo stesso risultato finale (l'ordinamento dell'array) eseguendo, in media, meno operazioni, quindi impiegando meno tempo e meno risorse computazionali.
 
Un insieme di ottimizzazioni si basa sull'osservazione che, se in una data iterazione non avviene alcuno scambio, significa che l'array è sicuramente ordinato, quindi l'algoritmo può essere terminato anticipatamente (ovvero senza giungere alla N-1ª iterazione). Una tecnica di ottimizzazione può dunque essere applicata usando una variabile [[logica booleana|booleana]] (o equivalente) usata come "''[[flag]]''" che tiene traccia se nell'iterazione corrente si è eseguito almeno uno scambio. La variabile viene impostata a ''false'' all'inizio di ogni iterazione e modificata a ''true'' solo nel caso in cui si proceda a uno scambio. Se al termine di un'iterazione completa il valore della variabile ''flag'' è rimasto ''false'', ovvero nell'iterazione non sono stati eseguiti scambi, l'array è ordinato e l'intero algoritmo viene terminato. Questa tecnica produce una riduzione del tempo ''medio'' di esecuzione dell'algoritmo, pur con un certo [[overhead]] costante dato dall'assegnamento e dal confronto della variabile ''flag''.
Seguono alcune implementazioni in vari [[Linguaggio di programmazione|linguaggi]].
 
Una seconda linea di ottimizzazione (che può essere combinata con la prima) è basata sull'osservazione che (sempre assumendo una scansione dell'array, per esempio, in avanti, e ordinamento crescente) se una data iterazione non sposta nessun elemento di posizione maggiore di un dato valore ''i'', allora si può facilmente dimostrare che nessun'iterazione successiva eseguirà scambi in posizioni successive a tale valore ''i''. L'algoritmo può dunque essere ottimizzato memorizzando la posizione a cui avviene l'ultimo scambio durante un'iterazione e limitando le scansioni dell'array durante le iterazioni successive solo fino a tale posizione. Anche questa tecnica evidentemente introduce un piccolo ''overhead'', dato dalla gestione di una variabile aggiuntiva che indica la posizione alla quale la scansione della lista deve fermarsi di volta in volta.
Altre implementazioni si trovano [http://wikisource.org/wiki/bubble_sort qui] su [[wikisource]]
 
Un'altra variante già menzionata, lo ''shaker sort'', consente di ridurre la probabilità che si verifichi la situazione di [[analisi del caso peggiore|caso peggiore]], in cui tutte le ottimizzazioni precedentemente citate falliscono e quindi contribuiscono solo negativamente con i relativi ''overhead''; vedi [[Shaker sort|la voce relativa]].
=== [[linguaggio C|C]] ===
 
=== Conigli e tartarughe ===
void BubbleSort(int* array, int elemN)
Come detto, la struttura dell'algoritmo porta ad avere elementi che si spostano verso la posizione corretta più velocemente di altri. Tutti gli elementi che si spostano nella stessa direzione di scorrimento dell'indice avanzano più velocemente di quelli che lo fanno in senso contrario. Si riprende l'esempio precedente. Data la lista
{
register int a, b,tmp=0, scambio;
for (a=0; a<elemN-1; ++a)
{
scambio=0;
for (b=0; b<elemN-1; ++b)
{
if (array[b] > array[b+1]) // scambiate il '>' con '<' se volete un ordinamento decrescente
{
tmp = array[b+1];
array[b+1] = array[b];
array[b] = tmp;
scambio=1;
}
}
if (!scambio) return; //esco se non ci sono stati scambi, inutile proseguire l'array è già ordinato
}
}
 
15 6 4 10 11 2
''Due note di carattere tecnico:''
a,b,tmp sono variabili dichiarate come register, il chè significa che, se possibile, il [[compilatore]] C farà in modo che vengano collocate nei [[registro|registri]] della [[CPU]]; questo renderà più rapido l'accesso a dette variabili.
tmp è dichiarata di tipo int, quindi dovrà contenere interi; se l'array contiene elementi di tipo diverso, sarà sufficiente modificare la sua dichiarazione.
 
dopo la prima iterazione la disposizione dei numeri sarà:
=== [[C plus plus|C++]] ===
 
6 4 10 11 2 15
#include <algorithm>
template <typename container> void bubbleSort(container &array)
{
container::reverse_iterator i;
container::iterator j;
for(i = array.rbegin(); i != array.rend(); ++i)
for(j = array.begin(); j != i ; ++j)
if((*j) > *(j+1) /* compara gli elementi vicini */
std::swap(*j, *(j+1));
}
template<typename T> void bubble_sort(T *base, size_t n) {
T *p, *q, t;
while (n--) {
for (q = (p = base) + 1; p < base + n; ++q, ++p) {
(*p > *q) && (t = *p, *p = *q, *q = t);
}
}
}
 
Qui si osserva il "coniglio" 15 e la "tartaruga" 2: il primo numero, in una sola iterazione, ha attraversato tutta la lista, posizionandosi nella sua collocazione definitiva. Il secondo, invece, durante la stessa iterazione è avanzato di una sola posizione verso la sua collocazione corretta.
=== [[Linguaggio di programmazione Java|Java]] ===
 
Sono stati effettuati diversi tentativi per eliminare le tartarughe e aumentare l'efficienza del Bubble sort: l'algoritmo [[shaker sort]] risolve molto bene questo problema, utilizzando un indice interno la cui direzione di scorrimento è invertita a ogni passaggio; risulta più efficiente del Bubble sort, ma paga un dazio in termini di complessità (<math>O(n^2)</math>). Il [[Comb sort]] compara elementi divisi da una grossa separazione, potendo quindi spostare le tartarughe molto velocemente prima di procedere a controlli fra elementi via via sempre più vicini per rifinire l'ordine della lista (la sua velocità media è comparabile a quella di algoritmi molto più veloci quali il [[quicksort]]).
public void bubbleSort(int[] array)
{
int temp = 0;
for(int i = array.length - 1; i > 0; i--)
for(int j = 0; j < i; j++)
if(array[j] > array[j+1]) // compara gli elementi vicini
{ // scambio
temp = array[j+1];
array[j+1] = array[j];
array[j] = temp;
}
}
 
===[[BASIC]]= Pseudocodice ==
Di seguito viene riportato uno [[pseudocodice]] base relativo alla versione originale dell'algoritmo:
 
'''procedure''' BubbleSort(A''':'''lista degli elementi da ordinare)
Sub Bubblesort(Array() as Integer, Length as Integer)
scambio ← true
Dim I as Integer
'''while''' scambio '''do'''
Dim J as Integer
scambio ← false
Dim Temp as Integer
'''for''' i ← 0 '''to''' length(A)-1 ''' do'''
For I = Length'''if''' -1A[i] To> A[i+1] Step -1'''then'''
For J =swap( 0A[i], toA[i+1] I - 1)
scambio ← true
IF Array(J)>Array(J+1) THEN ' compara gli elementi vicini
Temp = Array(j)
Array(J) = Array(J+1)
Array(J+1) = Temp
End If
Next J
Next I
End Sub
 
Lo pseudocodice appena scritto sposta, ad ogni ciclo while, l'elemento più grande all'estrema destra.
===[[Perl]]===
 
sub bubble_sort(@) {
my @a = @_;
foreach $i (reverse 0..@#a) {
foreach $j (0..$i-1) {
($a[$j],$a[$j+1]) = ($a[$j+1],$a[$j]) if ($a[$j] > $a[$j+1]);
}
}
return @a;
}
 
Una versione alternativa potrebbe essere la seguente:
===[[Python]]===
'''procedure''' BubbleSort(A''':'''lista degli elementi da ordinare)
'''for''' i ← 0 '''to''' length(A)-2 ''' do'''
'''for''' j ← i+1 '''to''' length(A)-1 ''' do'''
'''if''' A[j] < A[i] '''then'''
swap( A[j], A[i] )
 
Questa versione, invece, sposta, ad ogni ciclo for esterno, l'elemento più piccolo all'estrema sinistra.
def bubblesort(iterable):
seq = list(iterable)
for passesLeft in range(len(seq)-1, 0, -1):
for index in range(passesLeft):
if seq[index] > seq[index + 1]:
seq[index], seq[index + 1] = seq[index + 1], seq[index]
return seq
 
===[[Fortran]] Ottimizzazioni ===
Le prestazioni del bubble sort possono essere leggermente migliorate tenendo conto del fatto che dopo la prima iterazione l'elemento più grande si troverà certamente nell'ultima posizione della lista, quella sua definitiva; alla seconda iterazione il secondo più grande si troverà in penultima posizione, quella sua definitiva, e così via. Ad ogni iterazione il ciclo dei confronti può accorciarsi di un passo rispetto al precedente, evitando di scorrere ogni volta tutta la lista fino in fondo: all'n-esima iterazione si può quindi fare a meno di trattare gli ultimi n-1 elementi che ormai si trovano nella loro posizione definitiva. Si può rappresentare quanto detto con questo secondo pseudocodice:
 
'''procedure''' BubbleSort(A:lista di elementi da ordinare)
SUBROUTINE Bubble (X,N)
scambio ← true
! X e' l'array da ordinare, di dimensione N
n ← length'''(A)''' - 2 //poiché l'indice parte da 0 e devo fermarmi al penultimo elemento del vettore devo togliere 2
IMPLICIT NONE
'''while''' (scambio) '''do'''
INTEGER :: N, J, I, JMAX, TEMP
scambio INTEGER :: X(N) false
'''for''' i ← 0 '''to''' n '''do'''
JMAX=N-1
'''if''' (A[i] > A[i + 1]) '''then''' //sostituire '>' con '<' per ottenere un ordinamento decrescente
spike1: DO I=1,N-1
swap spike2:( DOA[i], J=A[i+1,JMAX] )
scambio IF(X(J).GT.X(J+1)) GO TO 100true
n ← n-1 //ad ogni passaggio il ciclo for si accorcia di un'iterazione
TEMP=X(J)
X(J)=X(J+1)
X(J+1)=TEMP
100 END DO spike2
JMAX=JMAX-1
END DO spike1
RETURN
END
 
Un altro tipo di ottimizzazione deriva dall'osservazione che spesso, alla fine di un'iterazione, non uno bensì due o più elementi si trovano nella loro posizione definitiva: tutti gli elementi che si trovano a valle dell'ultimo scambio effettuato risultano ordinati e si può evitare di trattarli ancora nell'iterazione successiva.
===[[Lisp]]===
 
Si può rappresentare quanto detto con questo terzo pseudocodice:
(DEFUN bubble-sort (X)
(LET ((Bubble (bubble X)))
(IF (EQUAL X Bubble) X (bubble-sort Bubble))))
(DEFUN bubble (X)
(COND ((NULL X) X)
((= (LENGTH X) 1) X)
((LISTP X)
(IF (> (CADR X) (CAR X))
(CONS (CADR X)
(bubble (CONS (CAR X) (CDR X))))
(CONS (CAR X) (bubble (CDR X)))))
(T (ERROR "bubble expects a list"))))
 
'''procedure''' BubbleSort(A:lista di elementi da ordinare)
===[[AppleScript]]===
n ← length'''(A)''' - 1
ultimoScambiato ← n
'''while''' (ultimoScambiato > 0) '''do'''
ultimoScambiato ← 0
'''for''' i ← 0 '''to''' n '''do'''
'''if''' (A[i] > A[i + 1]) '''then''' //sostituire '>' con '<' per ottenere un ordinamento decrescente
swap ( A[i], A[i+1] )
ultimoScambiato ← i
n ← ultimoScambiato //ad ogni passaggio si accorcia il ciclo for fermandosi in corrispondenza dell'ultimo scambio effettuato
 
Il terzo pseudocodice accorcia ulteriormente, se possibile, il ciclo di for: mentre nello pseudocodice base ad ogni passaggio il ciclo si ripete sempre n-1 volte e nel secondo pseudocodice esso si accorcia progressivamente di un'unità, nel terzo pseudocodice l'accorciamento può essere ancora più marcato dipendendo dal punto in cui si è verificato l'ultimo scambio.
-- Nota: AppleScript è 1-based, cioè il primo elemento di una lista è 1
--
on bubblesort( array )
repeat with i from length of array to 2 by -1 --> go backwards
repeat with j from 1 to i - 1 --> go forwards
tell array
if item j > item ( j + 1 ) then
set { item j, item ( j + 1 ) } to { item ( j + 1 ), item j } -- swap
end if
end tell
end repeat
end repeat
end bubblesort
 
== Note ==
[[Categoria:Algoritmi di ordinamento]]
<references/>
 
== Voci correlate ==
[[de:Bubblesort]]
* [[Algoritmo di ordinamento]]
[[en:Bubble sort]]
* [[Shaker sort]]
[[es:Ordenamiento de burbuja]]
* [[Stupid sort]]
[[fi:Kuplalajittelu]]
 
[[fr:Tri à bulles]]
== Altri progetti ==
[[ja:バブルソート]]
{{interprogetto|b=Implementazioni di algoritmi/Bubble sort|b_oggetto=implementazioni|b_preposizione=del|preposizione=sul}}
[[lt:Burbulo rūšiavimo algoritmas]]
 
[[nl:Bubblesort]]
== Collegamenti esterni ==
[[pl:Sortowanie bąbelkowe]]
* {{FOLDOC|2=bubble sort}}
[[pt:Bubble sort]]
* {{en}} [http://corte.si/posts/code/visualisingsorting/index.html Visualising Sorting Algorithms] - Visualizzazione grafica di alcuni algoritmi di ordinamento
* {{en}} [http://www.sorting-algorithms.com Sorting Algorithm Animations] - Animazione grafica di alcuni algoritmi di ordinamento
 
{{Ordinamento}}
{{Portale|informatica}}
 
[[Categoria:Algoritmi di ordinamento]]