kzhereb / kpi-acts-ta2019

Materials for "Algorithm Theory" course
3 stars 0 forks source link

Q04.6. (*) Adaptive due to sub-algorithms #40

Open vldnik opened 5 years ago

vldnik commented 5 years ago

Adaptive due to sub-algorithms Адаптивність за рахунок під-алгоритмів

Implement a recursive O(n log n) sorting algorithm (e.g. quicksort or merge sort), and for small sub-array sizes use one of the O(n^2) algorithms (insertion, selection, bubble, ...) Check if this will speedup the overall algorithm, and by how much Experimentally select the optimal threshold values ​​for the transition to a quadratic algorithm Consider adaptive and nonadaptive implementations of a quadratic algorithm and compare their work on those arrays where adaptive algorithms are more efficient. Determine the effect of the adaptivity of the quadratic sub-algorithm on the duration of the algorithm as a whole This task is more complex, but it is worth more points. Can be done in teams. You can implement various combinations of algorithms, in different programming languages, ...

Реалізувати рекурсивний алгоритм сортування quicksort або merge sort, причому для маленьких розмірів під-масивів використовувати один з квадратичних алгоритмів (insertion, selection, bubble, …) Перевірити, чи буде від цього прискорення загального алгоритму, і на скільки Підібрати експериментально оптимальні значення порогу для переходу до квадратичного алгоритму Розглянути адаптивні та неадаптивні реалізації квадратичного алгоритму і порівняти їх роботу для тих масивів, де адаптивність дає перевагу. Визначити, який вплив має адаптивність квадратичного під-алгоритму на час роботи алгоритму в цілому Це завдання є більш складним, але за нього буде більше балів. Можна виконувати в командах. Можна реалізовувати різні комбінації алгоритмів, на різних мовах програмування, …

alex-rogov commented 5 years ago

Insertion sort работает быстрее чем merge sort в случае с небольшими массивами. В ниже приведенном примере я заметил, что при заполнении массива случайными числами, и используя threshold (в данном случае значение, ниже которого выполняется сортировка вставкой) в диапазоне 16 – 64 сортировка вставкой проявила себя примерно в два раза быстрее, чем merge sort.

Разница становится заметней при увеличении изначально заданного массива чисел в размере. В программе я реализовал гибридную сортировка. Ее нельзя назвать стабильной сортировкой, ведь порядок одинаковых элементов может не сохраниться из-за перестановки во время выполнения merge сортировки.

При использовании массива в 500 000 целых чисел сортировка занимает около 1/8 секунды. Для эксперимента я увеличил начальное значение до 16 миллионов целых чисел, в данном случае сортировка дала результат в чуть более 4 секунд. Без использования insertion sort, сортировка занимает около 4, 5 секунд, а с ней и установленным threshold в размере 60 сортировка занимает чуть менее 4 секунд, что составляет около 10% увеличении производительности.

В данном эксперименте гибридная сортировка показала, что используя квадратичные алгоритмы для сортировки подмассивов увеличивает производительность алгоритма. И чем больше массив по размеру, тем заметней увеличение производительности.

static final int threshold = 60; // Insertion sort при <= threshold

public static void main(String[] args) {
    int[] unsortedArr = new int[16 * 1024 * 1024];
    Random random = new Random();

    for (int i = 0; i < unsortedArr.length; i++)
        unsortedArr[i] = random.nextInt();

    long start = System.currentTimeMillis();
    mergeSort(unsortedArr, 0, unsortedArr.length);
    long end = System.currentTimeMillis();

    System.out.println("Время (мс): " + (end - start));
}

static void swap(int[] arr, int first, int last) {
    int tmp = arr[first];
    arr[first] = arr[last];
    arr[last] = tmp;
}

static void merge(int[] arr, int a, int b, int c, int d, int e) {
    while (a < b && c < d)
        swap(arr, e++, arr[b] < arr[c] ? a++ : c++);
    while (a < b)
        swap(arr, e++, a++);
    while (c < d)
        swap(arr, e++, c++);
}

static void mainSort(int[] a, int b, int e, int w) {
    int m;
    if (e - b > 1) {
        m = b + (e - b) / 2;
        mergeSort(a, b, m);
        mergeSort(a, m, e);
        merge(a, b, m, m, e, w);
    } else {
        while (b < e) {
            swap(a, b++, w++);
        }
    }
}

static void mergeSort(int[] a, int b, int e) {
    int m, n, w, x;
    int t;
    // Используем Insertion sort если <= threshold
    if (e - b <= threshold) {
        for(n = b+1; n < e; n++) {
            t = a[n];
            m = n-1;
            while(m >= b && a[m] > t) {
                a[m+1] = a[m];
                m--;
            }
            a[m+1] = t;
        }
        return;
    }
    if (e - b > 1) {
        m = b + (e - b) / 2;
        w = b + e - m;
        wsort(a, b, m, w);
        while (w - b > 2) {
            // w = опорная точка (середина)
            n = w;
            w = b + (n - b + 1) / 2;
            x = b + n - w;
            wsort(a, w, n, b);
            merge(a, b, x, n, e, w);
        }
        for (n = w; n > b; --n) {
            t = a[n-1];
            for (m = n; m < e && a[m] < t; ++m)
                a[m-1] = a[m];
            a[m-1] = t;
        }
    }
}
FairyFox5700 commented 5 years ago
  MainSort QuickSort InsertionSort HeapSort
16 1046 368 3 1353
32 1198 460 7 1561
64 7155 576 25 2015
100 29403 3973 49 2097
1000 30785 1390 3254 3836
10000 41417 14559 410196 5909
20000 51313 29403 2619115 18815
50000 55735 56187 10412852 40084
80000 61916 84337 25013979 58720

Цей алгоритм поєднує відразу три різних види сортування, підібраних відповідно до розміру вхідного масиву. В його основі лежить алгоритм швидкого сортування. У разі, якщо розмір після розбиття вхідного масиву буде більше 16 елементів, тоді обирається сортування вставками. Як відомо, він добре працює на невеликих масивах та майже відсортованих, тому для елементів не більше 16, він прискорює загальний алгоритм. Найоптимальніший варіант 16-32 елементи. Якщо використовувати іншу кількість елементів наприклад більше 100, то сортування вставками буде працювати значно менш ефективно. Найкраще проявив себе, коли розмір був не більше 64 елементів. Далі його ефективність значно погіршується, що впливає на ефективність всього алгоритму в цілому. Якщо розмір перевищує (2log(n)) – де n – кількість елементів, тоді використовується Heap sort. Він забезпечує складність O(nlog(n)) в усіх випадках на відмінну від швидкого сортування, який в найгіршому випадку O(n^2). Якщо замість нього обрати merge sort, який також забезпечує таку складність, то буде використовуватись більше пам’яті. В такому випадку забезпечується ефективність відразу трьох алгоритмів, оскільки враховуються їхні найкращі результати. Після вимірювання часу виявилось, що цей алгоритм працює краще за швидке сортування для масивів значної довжини. Уже для понад 100000 він обігнав по швидкості швидке сортування. Щодо сортування вставками, то тут його застосовувати вже не доцільно, В таблиці наведена їх залежність від кількості елементів. Час позначено в секундах. Посилання на власну реалізацію: https://github.com/FairyFox5700/SortMethod/blob/master/IntroSort/Program.cs

IlliaKov commented 5 years ago

Pretty interesting algorithm. The main idea of it we can see below:

Abstract

We present a new deterministic algorithm for the sparse Fourier transform problem, in which we seek to identify k ≪ N significant Fourier coefficients from a signal of bandwidth N. Previous deterministic algorithms exhibit quadratic runtime scaling, while our algorithm scales linearly with k in the average case. Underlying our algorithm are a few simple observations relating the Fourier coefficients of time-shifted samples to unshifted samples of the input function. This allows us to detect when aliasing between two or more frequencies has occurred, as well as to determine the value of unaliased frequencies. We show that empirically our algorithm is orders of magnitude faster than competing algorithms.

galaxyair commented 5 years ago

Merge sort has a reliable workload / speed regardless of data size or the 'sortedness' of the data. It has good worst-case behaviour, but you can find plenty of other algorithms with a better best-case behaviour. Bubble sort actually has the best possible best-case sort behaviour (for a sorted list), because if the list is already sorted then nothing is changed and you've only needed to iterate through the list once to find that out. If you want a "better" algorithm that is more comparable to bubble sort, then quick sort (or its variants) are a more appropriate comparison. Quick sort is sort of like bubble sort, but skips a lot of intermediate steps (you switch a value with another value without switching all the intermediate values between). However, quick sort performs fairly badly on a sorted list, and can take of the order of n^2 operations to 'sort' a sorted list of size n.

BogdanDudnik commented 5 years ago

Sorting inserts works faster than sorting attachments in case of small arrays. At the bottom of the screen there was a case that it got in order (in this case, the value will be lower than selected) in the range 16-64 from the sorting.

The difference becomes appreciable when increasing the initial task. In the program I implemented hybrid sorting. It can not be called stable sorting, since the order of identical elements may not be retained due to permutations when performing a merge sort.