24 Apr 2023, 3:43 PM
24 Apr 2023, 3:43 PM

Algoritmi di ordinamento

Problema dell’ordinamento

Selection Sort

Cerco il minimo e lo metto in posizione corretta, riducendo il problema agli n1 restanti valori.
Esempio di esecuzione:

SelectionSortExecution.png|600

Implementazione in C++

void selectionSortvector<int>& array {
    int size = array.size();
    for(int i = 0; i < size; ++i) {
        int minIndex = getMin(array, i, size);
        std::swap(array[i], array[minIndex]);
    }
}

int getMinvector<int> array, int start, int end {
    int minIndex = start;
    for(int i = start + 1; i < end; ++i) {
        if(array[i] < array[minIndex])
            minIndex = i;
    }
    return minIndex;
}

Insertion Sort

Insertion sort is a simple sorting algorithm that works similar to the way you sort playing cards in your hands. The array is virtually split into a sorted and an unsorted part. Values from the unsorted part are picked and placed at the correct position in the sorted part.

Implementazione in C++

void insertionSortvector<int>& array {
    int size = array.size();
    for(int i = 1; i < size; ++i) {
        int j = i;
        int temp = array[j];
        for(; (j > 0) && (temp < array[j - 1]); --j)
            array[j] = array[j - 1];
        array[j] = temp;
    }
}

Merge Sort

Il merge sort è basato sulla tecnica divide-et-impera:

Idea: Si sfrutta il fatto che i due sottovettori sono già ordinati per ordinare più velocemente

MergeSortExecution.png

Implementazione in C++

void mergeSortvector<int>& array {
    mergeSortHelper(array, 0, array.size() - 1);
}

void mergeSortHelpervector<int>& array, int first, int last {
    if(first >= last)
        return;
    /* mid is the last element of the first half of the array.
        mid+1 is the first element of the second half of the array. */
    int mid = (first + last) / 2;
    mergeSortHelper(array, first, mid);
    mergeSortHelper(array, mid + 1, last);
    merge(array, first, mid, last);
}

void mergevector<int>& array, int first, int mid, int last {
    std::vector<int> sortedArray;
    int leftSide = first;
    int rightSide = mid + 1;
    /* We have first to mid and mid+1 to last already sorted.
        We have to merge the two parts in sortedArray. */
    while(leftSide < mid + 1 && rightSide < last + 1) {
        if(array.at(leftSide) > array.at(rightSide)) {
            sortedArray.push_back(array.at(rightSide));
            ++rightSide;
        }
        else {
            sortedArray.push_back(array.at(leftSide));
            ++leftSide;
        }
    }
    /* Insert all the remaining values from i to mid into sortedArray */
    for(; leftSide < mid + 1; ++leftSide)
        sortedArray.push_back(array.at(leftSide));
    /* Insert all the remaining values from mid+1 to last into sortedArray */
    for(; rightSide < last + 1; ++rightSide) 
        sortedArray.push_back(array.at(rightSide));
    /* Assign sorted data stored in sortedArray to array */
    for(int i = first; first < last + 1; ++first)
        array.at(first) = sortedArray.at(first - i);
}
Come abbiamo ottenuto questo?

Costo computazionale di Merge Sort:

T(n)={cn=12T(n2)+dnn>1

È possibile risolvere questa ricorrenza nel modo seguente:

T(n)=2T(n2)+dn=2(2T(n4)+d2n)+dn=4T(n4)+dn+dn=8T(n8)+dn+dn+dn=2iT(1)+dn+ ... +dn+dnlognda cuiT(n)=dnlogn+c=Θ(nlogn)

Per maggiori informazioni leggere 1. Risolvere le ricorrenze.
Riassunto del merge sort:

MergeSortExplanation.png

E se dividessi l'array in più di 2 parti?

Si consideri una variante di MergeSort chiamata MergeSortK che, invece di suddividere l’array da ordinare in 2 parti, lo suddivide in k parti, ri-ordina ognuna di esse applicando ricorsivamente MergeSortK, e le riunifica usando un’opportuna variante MergeK di Merge, che fonde k sottoarray invece di 2.

Merge Sort dividendo l'array in 3 parti:

T(n)={cn=13T(n3)+3nn>1
da cui

T(n)=3nlog3n+n=Θ(nlogn)

Merge Sort dividendo l'array in k parti:

T(n)={cn=1kT(nk)+knn>1
da cui

T(n)=knlogkn+n=Θ(nlogn)

Sembra quindi che più sono le parti in cui dividiamo l'array e più efficiente diventa l'algoritmo.
Ma allora perchè quello più usato è il 2-way Merge sort? 🤔
In realtà, nonostante l'algoritmo sembri più efficiente, il numero di comparazioni da fare aumenta proporzionalmente al numero di parti in cui dividiamo l'array.
Infatti, se dividessi in n parti un array da n elementi:

T(n)={cn=1nT(nn)+nnn>1
da cui

T(n)=n+nnlognn=n2+n=Θ(n2)

MergeSortComplexity.png
Il Merge Sort più efficiente quindi è quello che divide l'array in 2 parti.