Снятие рекурсии в Ярославском Dual Pivot Quicksort

Я начал работать над модифицированной версией Yaroslavskiys Dual Pivot Quicksort и в качестве первого шага попытался удалить рекурсию (код основан на рекурсивной версии в этой статье). Возможно, это немного старомодно, но я все же хотел бы попробовать. Вот код C, который у меня есть. Он работает, как задумано, но меня немного беспокоят патологические случаи, когда локальный стек может переполняться:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define QS_STACK_SIZE     96
#define QS_ARR_SIZE       10000000

#define INT_SWAP(x_,y_)\
do {\
    int tmp_ = (x_);\
    (x_) = (y_);\
    (y_) = tmp_;\
} while(0)

void qsort_yaro(int *a, int left, int right, int div);

int main(void)
{
    int i;
    static int a[QS_ARR_SIZE];

    srand (time (NULL));

    for (i = 0; i < QS_ARR_SIZE; i ++)
        a[i] = rand();

    printf ("Sorting %i elements ...\n", QS_ARR_SIZE);
    qsort_yaro (a, 0, QS_ARR_SIZE - 1, 3);

    for (i = 0; i < QS_ARR_SIZE - 1; i ++)
        if (a[i] > a[i + 1])
            {
            printf ("ERROR : Sequence not ascending!\n");
            break;
            }

    if (i == QS_ARR_SIZE - 1)
        printf ("OK : Sequence ascending!\n");

    return 0;
}

void qsort_yaro(int *a, int left, int right, int div)
{
    int i, j, k, third;
    int len;
    int m1, m2;
    int pivot1, pivot2;
    int less, great;
    int dist;
    int hi[QS_STACK_SIZE];
    int lo[QS_STACK_SIZE];
    int lvl = -1;
    int max_stack = 0;

    lo[++lvl] = left;
    hi[lvl] = right;

    while (lvl >= 0)
        {
        left = lo[lvl];
        right = hi[lvl--];

        while ((len = right - left) >= 27)
            { // insertion sort for tiny array
            third = len / div;
            //
            // "medians"
            m1 = left + third;
            m2 = right - third;

            if (m1 <= left)
                m1 = left + 1;
            if (m2 >= right)
                m2 = right - 1;
            if (a[m1] < a[m2])
                {
                INT_SWAP(a[m1], a[left]);
                INT_SWAP(a[m2], a[right]);
                }
            else
                {
                INT_SWAP(a[m1], a[right]);
                INT_SWAP(a[m2], a[left]);
                }

            // pivots
            pivot1 = a[left];
            pivot2 = a[right];
            // pointers
            less = left + 1;
            great = right - 1;
            // sorting
            for (k = less; k <= great; k++)
                {
                if (a[k] < pivot1)
                    {
                    INT_SWAP(a[k], a[less]);
                    less++;
                    }
                else if (a[k] > pivot2)
                    {
                    while (k < great && a[great] > pivot2)
                        great--;
                    INT_SWAP(a[k], a[great]);
                    great--;
                    if (a[k] < pivot1)
                        {
                        INT_SWAP(a[k], a[less]);
                        less++;
                        }
                    }
                }
            // swaps
            dist = great - less;
            if (dist < 13)
                div++;

            INT_SWAP(a[less - 1], a[left]);
            INT_SWAP(a[great + 1], a[right]);

//            lo[++lvl] = left;
//            hi[lvl]   = less - 2;
//            lo[++lvl] = great + 2;
//            hi[lvl]   = right;

            // Push the smaller interval on top for less stack usage
            // subarrays
            if (less - 2 - left > right - great - 2)
                {
                lo[++lvl] = left;
                hi[lvl]   = less - 2;
                lo[++lvl] = great + 2;
                hi[lvl]   = right;
                }
            else
                {
                lo[++lvl] = great + 2;
                hi[lvl]   = right;
                lo[++lvl] = left;
                hi[lvl]   = less - 2;
                }
            if (max_stack < lvl)
                max_stack = lvl;
            // equal elements
            if (dist > len - 13 && pivot1 != pivot2)
                {
                for (k = less; k <= great; k++)
                    {
                    if (a[k] == pivot1)
                        {
                        INT_SWAP(a[k], a[less]);
                        less++;
                        }
                    else if (a[k] == pivot2)
                        {
                        INT_SWAP(a[k], a[great]);
                        great--;
                        if (a[k] == pivot1)
                            {
                            INT_SWAP(a[k], a[less]);
                            less++;
                            }
                        }
                    }
                }
            // subarray
            if (pivot1 < pivot2)
                {
                left  = less;
                right = great;
                }
            else
                {
                left  = lo[lvl];
                right = hi[lvl--];
                }
            }

        for (i = left + 1; i <= right; i++)
            for (j = i; j > left && a[j] < a[j - 1]; j--)
                INT_SWAP (a[j], a[j - 1]);
        }
    printf ("Max Stack Size : %i\n", max_stack);
}

Я подправил оригинальную реализацию этим небольшим изменением

        if (less - 2 - left > right - great - 2)
            {
            lo[++lvl] = left;
            hi[lvl]   = less - 2;
            lo[++lvl] = great + 2;
            hi[lvl]   = right;
            }
        else
            {
            lo[++lvl] = great + 2;
            hi[lvl]   = right;
            lo[++lvl] = left;
            hi[lvl]   = less - 2;
            }

которая основана на той же идее, что и в обычной итеративной реализации 1-pivot, где большие части помещаются в стек, чтобы ограничить размер стека до log(n). Здесь меньшая часть помещается сверху стека, чтобы вернуть его как можно раньше.

Я провел несколько экспериментов со случайно сгенерированными массивами из 10.000.000 элементов и мог реально сохранить до 5 уровней стека этим методом (максимум ~45).

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

0 ответов

Другие вопросы по тегам