GrozI_Course_Work
.pdf10
bool swapped = true; int start = 0;
int end = n - 1;
while (swapped)
{
swapped = false;
for (int i = start; i < end; ++i)
{
if (arr[i] > arr[i + 1]) { swap(arr[i], arr[i + 1]); swapped = true;
}
}
if (!swapped) break;
swapped = false; --end;
for (int i = end - 1; i >= start; --i)
{
if (arr[i] > arr[i + 1]) { swap(arr[i], arr[i + 1]); swapped = true;
}
}
++start;
}
11
2.4 Сортування підрахунком (Counting sort)
Ідея алгоритму полягає в наступному: спочатку підрахувати скільки разів кожен елемент (ключ) зустрічається в вихідному масиві. Спираючись на ці дані можна одразу вирахувати на якому місці має стояти кожен елемент, а потім за один прохід поставити всі елементи на свої місця.. Нехай, наприклад, є масив A із чисел, по модулю менших 100. Тоді можна створити допоміжний масив B:array[-100..100] of integer для обчислення кількості елементів і у масиві А, «пробігти» весь вихідний масив і обчислювати частоту повторюваності кожного елемента — тобто якщо A[i]=х, то B[х] слід збільшити на одиницю. Потім «пробігти» лічильником i масив B, записуючи в новий масив A число i B[i] разів.
Аналіз сортування підрахунком
В алгоритмі присутні тільки прості цикли довжини N (довжина масиву), та один цикл довжини K (величина діапазону). Отже, обчислювальна складність роботи алгоритму становить В алгоритмі використовується додатковий масив. Тому алгоритм потребує
додаткової пам’яті.
В подібній реалізації алгоритм є стабільним. Саме ця його властивість дозволяє використовувати його як частину інших алгоритмів сортування (наприклад, сортування за розрядами). Використання даного алгоритму є доцільним тільки у випадку малих K.
Реалізація
int arr1[10];
int count_arr[10]; int x = arr[0];
for (int i = 1; i < n; i++) { if (arr[i] > x)
x = arr[i];
}
for (int i = 0; i <= x; ++i) { count_arr[i] = 0;
}
for (int i = 0; i < n; i++) { count_arr[arr[i]]++;
}
for (int i = 1; i <= x; i++) {
12
count_arr[i] += count_arr[i - 1];
}
for (int i = n - 1; i >= 0; i--) { arr1[count_arr[arr[i]] - 1] = arr[i]; count_arr[arr[i]]--;
}
for (int i = 0; i < n; i++) { arr[i] = arr1[i];
}
2.5 Швидке сортування (Quick sort)
Швидке сортування хоча і було розроблене досить давно, є найбільш широко застосовуваним і одним з найбільш ефективних алгоритмів.
Прнцип роботи алгоритму полягає в тому що :
1.Вибирається опорний елемент (наприклад, посередині масиву).
2.Масив проглядається зліва-направо і проводиться пошук найближчого елемента, більшого ніж опорний.
3.Масив проглядається справа-наліво і проводиться пошук найближчого елемента, меньшого ніж опорний.
4.Знайдені елементи міняються місцями.
5.Триває одночасний двосторонній перегляд по масиву з подальшими обмінами відповідно до пунктів 2-4.
6.В кінці кінців, перегляди зліва-напрво і справа-наліво сходяться в одній точці, яка ділить масив на два підмасива.
7.До кожного з двох підмасива рекурсивно застосовується "Швидке сортування".
Аналіз швидкого сортування
Для того щоб проаналізувати властивості швидкого сортування мі повинні досконало зрозуміти процес розбиття масиву. Після вибору границі , розбиттю підлягає вест масив. Таким чином виконуєть n порівнянь . Кількість обмінів можна оцінити за допомогою наступного міркування.
Припустимо, що масив даних що потрібно розділити складається з n ключів 1, ..., n, і ми обрали x як границю. Після поділу х буде займати в масиві позицію х. Число потрібних обмінів дорівнює числу елементів в лівій частині х-1, помноженому на вірогідність того, що ключ потрібно обміняти. Ключ обмінюється, якщо він не менише ніж х. Така ймовірність дорівнює (n−x+1)/n . Очікуване число обмінів обчислюється за допомогою підсумовування всіх можливих варіантів вибору
|
1 |
n |
x−1 |
|
n |
|
1 |
кордону і ділення цієї суми на n : |
M= n |
∑x=1 |
|
(n−x +1)= |
6 |
− |
|
n |
6 n |
12
Отже, очікуване число обмінів дорівнює приблизно n / 6.
Якщо припустити, що нам дуже щастить і ми завжди вибираємо в якості кордону медіану, то кожне поділ розбиває масив на дві рівні частини і число проходів, необхідних для сортування, дорівнює log(n) . Тоді загальне число порівнянь складе n log (n) , а загальне число обмінів n/6 log(n) .
Реалізація
static void swap(int* a, int* b)
{
int t = *a; *a = *b; *b = t;
}
static int partition (int arr[], int low, int high, bool direct)
{
int pivot = arr[high]; int i = (low - 1);
for (int j = low; j <= high- 1; j++)
{
if(direct)
{
if (arr[j] <= pivot)
{
i++;
swap(&arr[i], &arr[j]);
}
}
else
if (arr[j] >= pivot)
{
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]); return (i + 1);
}
static void quickSort(int arr[], int low, int high, bool direct)
{
if (low < high)
{
int pivot = partition(arr, low, high, direct); quickSort(arr, low, pivot - 1, direct);
14
quickSort(arr, pivot + 1, high, direct);
}
}
2.6 Сортування злиттям (Merge sort)
Алгоритм сортування злиттям заснований на ідеї, що два відсортованих масиви можна злити в один за час, що дорівнює сумарній довжині цих масивів.
Для цього порівняємо перші елементи даних масивів. Той елемент, який менше, скопіюємо в кінець результуючого масиву (який спочатку порожній) і в цьому масиві перейдемо до наступного елементу. Будемо повторювати цей процес (вибираємо з початку двох масивів найменший елемент, копіюємо його в результуючий масив), поки один з вихідних масивів не скінчиться. Після цього залишившиєся елементи (один з двох вихідних масивів буде непорожній) скопіюємо в результуючий масив.
Для того, щоб не видаляти початкові елементи з масивів, заведемо два індексу i і j, що вказують на поточні елементи в кожному масиві. Замість видалення елементів будемо пересувати ці індекси. В кінці додамо до результуючому масиву залишилися елементи з двох вихідних масивів A [i:] + B [j:]
Аналіз сортування злиттям
Оцінимо складність цього алгоритму. Нехай масив містить n елементів. Тоді за його можна розділити на дві частини і після сортування злити їх разом.
Кожна з цих двох частин має розмір n/2, і за кроків кожну з них можна поділити на дві частини розміром n/4 і потім після сортування злити їх разом. Аналогічно, чотири частини розміром n/4 за сумарне кроків діляться на частини розміром n/8 і зливаються разом. Цей процес «в глибину» триває стільки раз, скільки разів можна число n ділити на 2, до тих пір, поки розмір частини не стане дорівнює 1, тобто log2 (n) . Разом, загальна складність цього алгоритму
дорівнює O(n log2 n) .
Одним з недоліків сортування злиттям є той факт, що він вимагає багато допоміжної пам'яті (стільки ж, який розмір вихідного масиву) для реалізації.
15
Реалізація
static void merge(int a[], int Firstindex, int m, int Lastindex, bool direct); static void __mergeSort(int a[], int Firstindex, int Lastindex, bool direct)
{
if (Firstindex < Lastindex)
{
int m = Firstindex + (Lastindex - Firstindex)/2;
__mergeSort(a, Firstindex, m, direct); __mergeSort(a, m+1, Lastindex, direct);
merge(a, Firstindex, m, Lastindex, direct);
}
}
static void merge(int a[], int Firstindex, int m, int Lastindex, bool direct)
{
int x; int y; int z;
int sub1 = m - Firstindex + 1; int sub2 = Lastindex - m;
int First[sub1]; //temp array
int Second[sub2];
for (x = 0; x < sub1; x++) // copying data to temp arrays First[x] = a[Firstindex + x];
for (y = 0; y < sub2; y++) Second[y] = a[m + 1+ y];
x = 0; y = 0;
z = Firstindex;
while (x < sub1 && y < sub2)
{
if(direct)
{
if (First[x] <= Second[y])
{
a[z] = First[x]; x++;
}
16
else
{
a[z] = Second[y]; y++;
}
} else
{
if (First[x] >= Second[y])
{
a[z] = First[x]; x++;
}
else
{
a[z] = Second[y]; y++;
}
}
z++;
}
while (x < sub1)
{
a[z] = First[x]; x++;
z++;
}
while (y < sub2)
{
a[z] = Second[y]; y++;
z++;
}
}
17
2.7 Алгоритм пірамідального сортування (Heap sort)
Основна ідея - шукаємо максимальний елемент в невідсортованій частині масиву і ставимо його в кінець подмассіва. У пошуках максимуму підмасив перебудовується в так зване Сортувальне дерево (вона ж бінарна купа, вона ж піраміда), в результаті чого максимум сам "спливає" в початок масиву. Після цього переміщуємо максимум в кінець пыдмасива. Потім над частиною масиву , що залишилася знову здійснюється процедура перебудови в сортувальне дерево з подальшим переміщенням максимуму в кінець підмасива.
Сортувальне дерево - дерево у якого будь-який батько не менше ніж кожен з його нащадків. Якщо Сортувальне дерево незростаюча, то, відповідно, будь-який батько не більше ніж кожен з його нащадків.
Аналіз пірамідального сортування
Ми можемо стверджувати, що основна операція з купою Heapify виконується за
O(log(n)) часу, тому що купа має |
O(log(n)) |
рівні, і просіяний елемент |
рухається вниз на один рівень дерева після постійного обсягу роботи. |
||
Виходячи з цього, ми можемо зрозуміти що: |
|
|
1. Що для побудови купи потрібно |
O(n log(n)) |
часу , оскільки нам потрібно |
застосувати Heapify приблизно n / 2 рази (до кожного з внутрішніх вузлів), і (2) що потрібно ч асу на вилучення кожного з максимальних елементів, оскільки нам потрібно витягти приблизно n елементів, і кожне вилучення передбачає постійний обсяг роботи та один Heapify. Тому загальний час роботи пірамідального сортування дорівнює .
18
Реалізація
static void swap(int* a, int* b)
{
int t = *a; *a = *b; *b = t;
}
static void heapify(int arr[], int n, int root, bool direction)
{
int largest = root; // root is the largest element int l = 2*root + 1; // left
int r = 2*root + 2; // right
if(!direction)
{
if (l < n && arr[l] < arr[largest]) largest = l;
if (r < n && arr[r] < arr[largest]) largest = r;
}
else
{
if (l < n && arr[l] > arr[largest]) largest = l;
if (r < n && arr[r] > arr[largest]) largest = r;
}
if (largest != root)
{
swap(&arr[root], &arr[largest]);
heapify(arr, n, largest, direction);
}
}
void heap_sort(int arr[], int n, bool direction)
{
for (int i = n / 2 - 1; i >= 0; i--) heapify(arr, n, i, direction);
for (int i=n-1; i>=0; i--)
19
{
swap(&arr[0], &arr[i]);
heapify(arr, i, 0, direction);
}
}
Методологія тестування
1. Апаратне забезпечення Тестування проводилося на такому апаратному забезпеченні:
•Ryzen 5 2500U(2 — 3.6 Ghz)
•8Gb RAM (6.74Gb available)
2.Програмне забезпечення
Тестування проводилося на такому програмному забезпеченні:
•OС - GNU/Linux x86_64
•Дистрибутив - Manjaro Linux
•Версія ядра - 5.9.11
•IDE — Clion
•Компілятор — Clang
3.Тестові дані
Тестові дані представляють собою одновимірні масиви чисел типу int.
•(Тест 0) Випадково згенеровані дані
•(Тест 1) Відсортовані у зворотньому порядку дані
•(Тест 2) Частково (на половину) відсортовані дані
•(Тест 3) Відсортовані дані Розмірність даних являє собою :
•10
•100
•1000
•10000
•100000
Для першої групи сортувань, і :
•10
•100
•1000
•10000
•100000
•1000000
Для другої групи сортувань