Подтвердить что ты не робот

Почему функция quicksort C работает намного медленнее (сопоставление лент, замена ленты), чем функция сортировки пузырьков?

Я собираюсь реализовать игрушку-ленту "мейнфрейм" для студентов, показывая быстроту функций класса "quicksort" (рекурсивных или нет, на самом деле не имеет значения, из-за медленного аппаратного обеспечения и известных методов смены столов ) по сравнению с классом функций "bubblesort" . Итак, хотя я понимаю, что аппаратная реализация и контроллеры, я догадался, что функция quicksort намного быстрее, чем другие, с точки зрения последовательности, порядка и сравнения (гораздо быстрее перематывать ленту с середины, чем от самой конец, из-за разной скорости перемотки назад).

К сожалению, это неверно; этот простой "пузырьковый" код показывает большие улучшения по сравнению с функциями "быстрой сортировки" с точки зрения сравнения расстояний, направления и количества сравнений и записей.

У меня есть 3 вопроса:

  • Есть ли у меня ошибка в моей реализации функции quicksort?
  • Есть ли у меня ошибка в моей реализации функции bubblesoft?
  • Если нет, то почему функция "bubblesort" намного быстрее (операции сравнения и записи), чем функция "quicksort"?

У меня уже есть функция "быстрой сортировки":

void quicksort(float *a, long l, long r, const compare_function& compare)
{
    long i=l, j=r, temp, m=(l+r)/2;
    if (l == r) return;
    if (l == r-1)
    {
        if (compare(a, l, r))
        {
            swap(a, l, r);
        }
        return;
    }
    if (l < r-1)
    {
        while (1)
        {
            i = l;
            j = r;
            while (i < m && !compare(a, i, m)) i++;
            while (m < j && !compare(a, m, j)) j--;
            if (i >= j)
            {
                break;
            }
            swap(a, i, j);
        }
        if (l < m) quicksort(a, l, m, compare);
        if (m < r) quicksort(a, m, r, compare);
        return;
    }
}

и у меня есть собственная реализация функции "bubblesort" :

void bubblesort(float *a, long l, long r, const compare_function& compare)
{
    long i, j, k;
    if (l == r)
    {
        return;
    }
    if (l == r-1)
    {
        if (compare(a, l, r))
        {
            swap(a, l, r);
        }
        return;
    }
    if (l < r-1)
    {
        while(l < r)
        {
            i = l;
            j = l;
            while (i < r)
            {
                i++;
                if (!compare(a, j, i))
                {
                    continue;
                }
                j = i;
            }
            if (l < j)
            {
                swap(a, l, j);
            }
            l++;
            i = r;
            k = r;
            while(l < i)
            {
                i--;
                if (!compare(a, i, k))
                {
                    continue;
                }
                k = i;
            }
            if (k < r)
            {
                swap(a, k, r);
            }
            r--;
        }
        return;
    }
}

Я использовал эти функции сортировки в тестовом примерном коде, например:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <conio.h>

long swap_count;
long compare_count;

typedef long (*compare_function)(float *, long, long );
typedef void (*sort_function)(float *, long , long , const compare_function& );

void init(float *, long );
void print(float *, long );

void sort(float *, long, const sort_function& );
void swap(float *a, long l, long r);

long less(float *a, long l, long r);
long greater(float *a, long l, long r);

void bubblesort(float *, long , long , const compare_function& );
void quicksort(float *, long , long , const compare_function& );

void main()
{
    int n;
    printf("n=");

    scanf("%d",&n);
    printf("\r\n");

    long i;
    float *a = (float *)malloc(n*n*sizeof(float));

    sort(a, n, &bubblesort);
    print(a, n);

    sort(a, n, &quicksort);
    print(a, n);

    free(a);
}

long less(float *a, long l, long r)
{
    compare_count++;
    return *(a+l) < *(a+r) ? 1 : 0;
}

long greater(float *a, long l, long r)
{
    compare_count++;
    return *(a+l) > *(a+r) ? 1 : 0;
}

void swap(float *a, long l, long r)
{
    swap_count++;

    float temp;

    temp = *(a+l);
    *(a+l) = *(a+r);
    *(a+r) = temp;
}

float tg(float x)
{
    return tan(x);
}

float ctg(float x)
{
    return 1.0/tan(x);
}

void init(float *m,long n)
{
    long i,j;
    for (i = 0; i < n; i++)
    {
        for (j=0; j< n; j++)
        {
            m[i + j*n] = tg(0.2*(i+1)) + ctg(0.3*(j+1));
        }
    }
}

void print(float *m, long n)
{
    long i, j;
    for(i = 0; i < n; i++)
    {
        for(j = 0; j < n; j++)
        {
            printf("  %5.1f", m[i + j*n]);
        }
        printf("\r\n");
    }
    printf("\r\n");
}

void sort(float *a, long n, const sort_function& sort)
{
    long i, sort_compare = 0, sort_swap = 0;

    init(a,n);

    for(i = 0; i < n*n; i+=n)
    {
        if (fmod (i / n, 2) == 0)
        {
            compare_count = 0;

            swap_count = 0;
            sort(a, i, i+n-1, &less);

            if (swap_count == 0)
            {
                compare_count = 0;
                sort(a, i, i+n-1, &greater);
            }

            sort_compare += compare_count;
            sort_swap += swap_count;
        }
    }

    printf("compare=%ld\r\n", sort_compare);
    printf("swap=%ld\r\n", sort_swap);

    printf("\r\n");
}
4b9b3361

Ответ 1

Я думаю, что проблема в том, что большинство реализаций quicksort полагаются на шаг раздела, который чередует чтение и запись на противоположных концах области, подлежащей сортировке. В модели с произвольным доступом это совершенно нормально (все чтения - это, по существу, O (1)), но на ленте это может быть чрезвычайно дорогостоящим, так как переключение между разными концами диапазона, подлежащего сортировке, может занимать O ( n), когда ленточная катушка полностью перевернута вперед и назад. Это превращает то, что обычно представляет собой шаг разбиения O (n) на то, что потенциально O (n 2), доминируя над временем выполнения функции. Более того, поскольку время, требуемое для поиска ленты, вероятно, в тысячи или миллионы раз медленнее, чем частота процессора, эта работа O (n 2) имеет огромный постоянный коэффициент.

У Bubble sort, с другой стороны, нет этой проблемы, потому что он всегда сравнивает соседние ячейки в массиве. Он делает не более O (n) по массиву и, следовательно, требует перемотки ленты только n раз. Логика обработки, безусловно, дороже в сортировке пузырьков - больше, чем почти любая другая сортировка O (n 2), но это ничто по сравнению с сохраненным временем, не ищущим ленту назад и вперед.

Короче говоря, quicksort, вероятно, будет работать намного медленнее на ленте, чем просто пузырь, просто потому, что она требует, чтобы лента двигалась намного больше во время выполнения. Так как поиск ленты дорогой, естественное преимущество quicksort во время съёмки будет съедено на этом этапе, и пузырьковая камера должна выглядеть намного быстрее.

Ответ 2

templatetypedef ответ прямо на деньги. Доступ не только к доступу к пузырькам, но и к минимуму, он работает на месте. Я подозреваю, что это на самом деле лучший алгоритм сортировки для машины, имеющей одиночную, произвольно медленную ленту и только O (1) RAM. [EDIT: фактически сортировка коктейлей (двунаправленная версия bubblesort) должна быть лучше, поскольку она позволяет избежать расточительных перемотки - спасибо Стив Джессоп.]

Если у вас есть 4 ленточных накопителя, тогда mergesort управляет roost. Можно использовать только 3 ленты, более благоприятную версию mergesort.

Ответ 3

Одна из причин, по которой QuickSort быстрее, чем сортировка пузырьков, заключается в том, что она мгновенно перемещает элементы на большие расстояния. Если QuickSort перемещает элемент вверх по 50 элементам, а затем вниз 20, вверх по 10, вверх по 5 и вниз 2 до того, как он окажется в правильном месте, элемент будет состоять из 43 слотов с того места, где он был запущен, но только 5 раз. Сорт Bubble переместил бы элемент 43 раза. Если перемещение элемента одного слота стоит так же, как перемещение его на 50, это крупная победа. Если, однако, стоимость перемещения элемента пропорциональна расстоянию, QuickSort переместит элемент на общее расстояние 87 слотов - в два раза больше, чем у пузырей.

Если вы застряли в работе с ленточными накопителями, оптимальный алгоритм будет во многом зависеть от того, как диски физически работают. Например, на некоторых дисках только операции перемотки назад и подготовка к записи (эффективно стирание ленты в процессе), перемотка назад и подготовка к чтению и обработка следующего байта (чтение или запись в зависимости от режима перемотки). Другие диски позволяют случайным образом получать доступ к отдельным блокам и заменять их на любом месте ленты. Некоторые диски ограничены чтением в одном направлении. Другие (например, QIC-ленты) имеют несколько дорожек, которые читаются в одном направлении, а некоторые - в другом направлении. Я не знаю, могли ли какие-либо диски разрешить чтение или запись одного и того же блока данных в обоих направлениях, но такая вещь была бы, по крайней мере, теоретически возможной.