Anteprima
Vedrai una selezione di 4 pagine su 11
Quicksort - Algoritmi e strutture dati Pag. 1 Quicksort - Algoritmi e strutture dati Pag. 2
Anteprima di 4 pagg. su 11.
Scarica il documento per vederlo tutto.
Quicksort - Algoritmi e strutture dati Pag. 6
Anteprima di 4 pagg. su 11.
Scarica il documento per vederlo tutto.
Quicksort - Algoritmi e strutture dati Pag. 11
1 su 11
D/illustrazione/soddisfatti o rimborsati
Disdici quando
vuoi
Acquista con carta
o PayPal
Scarica i documenti
tutte le volte che vuoi
Estratto del documento

Tutto il meccanismo sta nella magia del processo di

partizionamento, cioè scoprire qual è il posto giusto di

un elemento partendo dal primo, prendo il primo e lo

metto al posto giusto, poi prendo i primi dei sub-array e

li metto al posto giusto, poi prendo i primi dei sub-array

di questi sub-array e li metto al posto giusto e via così,

cioè l’elemento che vado a mettere al posto giusto è

sempre il primo del sub-array su cui sto lavorando.

E’ matematicamente dimostrato che questo algoritmo

raggiunge lo scopo per cui è previsto, ci sono algoritmi

per cui il raggiungimento dello scopo è ipotizzato ma

non dimostrato

E’ un algoritmo divide-and-conquer ma ha un’altra

caratteristica, è un esempio di un algoritmo

randomizzato perché ha il primo passo iniziale di

permutazione casuale degli elementi che permette di

creare artificialmente degli input che non hanno

strutture particolari che purtroppo sono frequenti nei

casi prativi, ad esempio se usiamo questo algoritmo per

ordinare array parzialmente ordinati, il quicksort lavora

particolarmente bene su un array completamente

casuale, se l’array è parzialmente ordinato cioè ha una

sua struttura interna allora il quicksort potrebbe lavorare

peggio quindi la situazione migliore per il quicksort è

avere un array casuale, quindi la randomizzazione che

vogliamo fare è un algoritmo che scombina le cose.

Dopo aver scombinato tutto la probabilità di ottenere

anche una sola configurazione ordinata è molto bassa,

come quella di qualsiasi altra configurazione, però le

configurazioni che ci vanno bene sono molte di più di

quelle che non ci vanno bene.

Vediamo i due pezzi del quicksort:

Questo codice è in java eliminando alcune parti che

servono solo al compilatore.

La prima è la parte “non magica”, mediante shuffle

scombiniamo il vettore, dopo di che ordiniamo il vettore

a da 0 ad a.length-1 chiamando la funzione sort definita

sotto, essa lavora su sub-array e contiene una serie di

condizioni che ci dicono se, stringendo l’array, sono

arrivato a sovrapporre hi e lo allora ho finito (hi<=lo, non

dovrebbe mai succedere che hi<lo ma per sicurezza è

bene metterlo), ho cioè un array di dimensione 0 e

quindi si conclude. Naturalmente per trovare la j chiama

la funzione partition chiedendo quindi la dimensione in

cui deve mettere il primo elemento nel sub-array che va

da lo a hi, quindi partition trova il punto in cui mettere

questo elemento, una volta trovato il punto metto lì

l’elemento e vado avanti a partizionare ricorsivamente il

sub-array più basso e il sub-array più alto.

Vediamo ora la funzione partition:

scelgo un elemento da collocare, chiamo con v il primo

elemento del sub-array da ordinare, nonché pivot,

comincio poi a leggere il sub-array in salita e andiamo a

verificare se quel sub-array per caso è già fatto di

elementi tutti inferiori, questo mediante la prima while

dentro il while(true), infatti mediante la less mi chiedo se

a[++i] è meno del pivot, con ++i intendiamo che

incremento i -quindi parto dal secondo elemento del sub-

array- poi prendo a[i] e vado a vedere se è minore di v, il

ciclo si ferma in due casi: quando incontra un elemento

che non è minore, quindi un elemento che dovrebbe

stare nel vettore alto ma sta nel vettore basso, oppure si

ferma anche quando trovo i==hi, cioè ho finito. Il while

successivo fa il giro complementare, nell’altro sub-array,

quindi parte da hi e andando in giù, si ferma quando

incontra qualcosa che sta in altro ma dovrebbe stare in

basso oppure quando j==lo perché è arrivato in fondo.

Siccome queste due cose si muoveranno, cioè avrò i in

salita e j in discesa, se per caso si scambiano mi fermo,

quindi la salita che sto facendo e la discesa che sto

facendo non possono scambiarsi e andare avanti.

Mediante exch scambio gli elementi i e j ma e questo

punto ripeto l’operazione con quel che resta, dopodichè

alla fine scambio a con l’ultimo j.

Vediamo la traccia:

La prima riga è l’array scombinato, quindi dopo lo

shuffle, poi segue lo scan left cioè quella che nel codice

era la prima while dopo il while(true), poi faccio lo scan

right, nella seconda riga parto da R e mi chiedo se

minore di K, quindi mi fermo subito perché in questo

caso al primo tentativo fallisce la condizione less(a[++i],

v), quindi siccome sono uscito dal ciclo vado a fare il

secondo ciclo while, quindi sempre nella seconda riga

parto da S in fondo e mi chiedo se S è maggiore di K, si

allora continuo con il while, O è maggiore di K? Si quindi

procedo, X è più grande di K? Si allora ancora altro giro,

C è più grande di K? No allora mi fermo, esco da questo

while. Ora ho trovato in due punti dell’array, su a[i] un

elemento che è più grande del pivot e dovrebbe essere

più piccolo, e dall’altra parte un elemento più piccolo del

pivot e dovrebbe essere più grande, quindi li scambio,

scambio cioè R e C, cioè che succede alla terza riga,

questo mediante la funzione exch nel codice. Arrivati in

fondo al codice ricominciamo da capo grazie al

while(true) ma senza scambiare i e j, quindi

ricominciamo da dove siamo arrivati, infatti alla quarta

riga abbiamo: A<K? Si, T<K? No quindi ci fermiamo, poi

dall’altra parte? Q>K? Si, M>K? Si, I>K? No abbiamo così

trovato un’altra coppia di elementi che deve essere

scambiata, abbiamo infatti quello grande che dovrebbe

essere piccolo e viceversa, quindi mediante la riga 5 li

scambiamo. Si va poi avanti in questo modo finchè non

arriviamo nella riga 8 dove E<K ma L>K quindi K viene

prima di L, scambio la E con K e ho trovato il posto con

K, questo è quello che succede quando if(i>=j) ritorna

true quindi faccio quello che nel codice viene chiamato

final exchange.

Questo meccanismo sfrutta bene la località.

Notiamo che con questo meccanismo a ogni passaggio

io ottengo un elemento che in questo caso è K a posto e

il sub-array inferiore è tutto minore di K, il sub-array

superiore è tutto fatto da elementi maggiori di K,

nessuno dei due è ordinato.

A questo punto viene rifatta l’operazione sul primo sub-

array e sul secondo sub-array.

Notiamo che mentre mergesort, soprattutto nella

versione top-down, prende elementi spaccando a metà il

vettore originale, questo potrebbe spaccare gli elementi

a caso, potrebbe anche succede che il posto giusto del

pivot è esattamente dov’è, cioè in prima posizione.

Le prestazioni di questo algoritmo, quindi il numero di

giri che devo fare qui dentro, cambia significativamente

in funzione dell’input, cioè mentre mergesort impiega

sempre lo stesso numero di confronti qualunque sia

l’input, questo invece no perché non sappiamo dove sarà

la posizione del pivot. L’aver fatto la fase di shuffle rende

un po' più probabile ma non certo che a ogni giro

l’elemento pivot stia approssimativamente a metà tra

tutti gli altri, cosa che non è garantita succedere e

succede con probabilità sempre più bassa quanto più ci

sono degli elementi duplicati perché si ammucchiano

tutti da una parte del vettore

Vediamo la traccia completa dell’algoritmo applicata alla

stringa quicksortexample:

Ogni singolo passaggio di questa traccia richiede un

partizionamento, quindi quello che è interessante è il

partizionamento e non la sequenza

Vediamo la performance del quicksort

All’inizio si pensava non funzionasse pubblicando

l’algoritmo ha pubblicato anche la dimostrazione proprio

perché Hoare era un matematico dimostrò che

quell’algoritmo a ogni passo andava a creare condizioni

che convergevano verso un array ordinato, perché a ogni

passo il nostro array è un po' più ordinato del passo

precedente perché ha almeno un elemento al suo posto.

Ne esiste anche una versione iterativa e non ricorsiva

che è preferibile perché non lavora sullo stack ma solo

muovendo gli indici, però quella ricorsiva si capisce cosa

sta facendo leggendo il codice.

La complessità nel caso peggiore è n^2 perché nel caso

peggiore non devo mettere a posto due array più piccoli

ma un elemento è al suo posto quindi devo fare n

passaggi per confrontare gli elementi e cercare il loro

posto e poi n passaggi per trovare un posto a tutti gli

elementi, quindi n^2, in realtà poi è (n^2)/2 perché devo

trovare posto a sempre meno elementi. Inoltre i dati che

dobbiamo ordinare dopo la randomizzazione riescono ad

andare in convergenza molto velocemente, cioè il mio

pivot finisce quasi sempre più o meno in mezzo all’array.

Ci sono anche altri vantaggi, ad esempio che l’inenr

loop, quello del metodo partition, è talmente sintetico

che è difficile pensare a qualcosa di più veloce. Notiamo

inoltre che nel metodo partition, a differenza del

mergesort, viene utilizzato un valore fisso, infatti v viene

preso una volta per tutte come elemento di confronto,

mergesort invece deve fare sempre due accessi in

memoria per prendere i due elementi da confrontare, in

questo caso invece il pivot è in memoria,

nell’implementazione sarà sul registro della macchina

quindi il confronto sarà più veloce perché verrà fatto solo

1 accesso per ogni confronto, invece mergesort ne fa 2.

Notiamo che inoltre gli scambi li fa solo se incontra degli

elementi che non sono a posto, questo vale anche per

mergesort, il problema però di mergesort è che deve

comunque fare le copie per poter poi fare il merge.

Inoltre in totale il numero di confronti che quicksort fa è

abbastanza piccolo.

La performance di quicksort dipende da quanto è

fortunato nel partizionare l’array, se il pivot cade sempre

più o meno a metà è praticamente equivalente a

mergesort perché sta facendo partizionamento in parti

uguali, se invece il pivot tende a cadere all’inizio o alla

fine dell’array si crea uno squilibrio nelle dimensioni dei

due array e, nel caso peggiore che si verifica quando il

pivot è sempre all’inizio o sempre alla fine, l’altro array

da ordinare è massimale quindi devo fare N operazioni

per riordinarli tutti.

Quicksort usa confronti (e scambi) in

media per ordin

Dettagli
Publisher
A.A. 2023-2024
11 pagine
SSD Scienze matematiche e informatiche INF/01 Informatica

I contenuti di questa pagina costituiscono rielaborazioni personali del Publisher ab502 di informazioni apprese con la frequenza delle lezioni di Algoritmi e strutture dati e studio autonomo di eventuali libri di riferimento in preparazione dell'esame finale o della tesi. Non devono intendersi come materiale ufficiale dell'università Università degli Studi di Pavia o del prof Barili Antonio.