path: Главная\Алгоритмы\Алгоритмы сортироки


::: сортировка вставками
::: QuickSort (быстрая сортировка)
::: HeapSort (пирамидальная сортировка)
::: байтовая, цифровая, радиксная или распределяющая сортировка
::: как отсортировать большой файл (сортировка слиянием)

Cортировка простыми вставками. Авторы: Kantor Ilia - Nicolas Virt - Tomas Niemann ;-))

Все элементы условно разделяются на готовую последовательность a1 ... ai-1 и входную ai ... an. Hа каждом шаге, начиная с i=2 и увеличивая i на 1, берем i-й элемент входной последовательности и вставляем его на нужное место в готовую.

             Пример:

        Hачальные ключи         44 [55] 12  42  94  18  06  67
                 i = 2          44  55 [12] 42  94  18  06  67
                 i = 3          12  44  55 [42] 94  18  06  67
                 i = 4          12  42  44  55 [94] 18  06  67
                 i = 5          12  42  44  55  94 [18] 06  67
                 i = 6          12  18  42  44  55  94 [06] 67
                 i = 7          06  12  18  42  44  55  94 [67] 
                 i = 8          06  12  18  42  44  55  67  94 

При поиске подходящего места удобно 'просеивать' x, сравнивая его с очередным элементом ai и либо вставляя его, либо пересылая ai направо и продвигаясь налево. Просеивание может кончиться при двух условиях:

  • Hайден ai с ключом, меньшим x.
  • Достигнут конец готовой последовательности.

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

                                      Анализ
    
            Число сравнений     |- - - - - - - - - - - - - - - - - - - -
                                | минимальное:    n - 1
                                | среднее:        ( n^2 + n - 2 ) / 4
                                | максимальное:   ( n^2 + n ) * / 2 - 1
           Количество пересылок |- - - - - - - - - - - - - - - - - - - -
                                | минимальное:    2 * ( n - 1 )
                                | среднее:        ( n^2 + 9 * n - 10
                                | максимальное:   ( n^2 + 3 * n - 4 ) / 2
    
    

    Пример на Си - Tomas Niemann.

    --------------------------------------------------------------------
    typedef int item;          /* Тип сортируемых элементов */
    typedef int tblIndex;      /* Тип ключей, по которым сортируем */
    
    #define compGT(a,b) (a > b)   /* Функция сравнения  */
    
    void insertSort(T *a, tblIndex lb, tblIndex ub) {
        item t;
        tblIndex i, j;
    
       /***********************
        * сортируем a[lb..ub] *
        ***********************/
        for (i = lb + 1; i <= ub; i++) {
            t = a[i];
    
            /* Сдвигаем элементы вниз, пока */
            /*  не найдем место вставки.    */
            for (j = i-1; j >= lb && compGT(a[j], t); j--)
                a[j+1] = a[j];
    
            /* вставка */
            a[j+1] = t;
        }
    }
    

    QuickSort (быстрая сортировка).

    Выберем случайным образом какой-то элемент х и просмотрим массив, двигаясь слева направо, пока не найдем аi больший x, а затем справа налево, пока не найдем аi меньший х. Поменяем их местами и продолжим процесс просмотра с обменом, пока просмотры не встретятся где-то в середине массива. В результате массив разделится на две части: левую - с ключами, меньшими х и правую - с ключами, большими х. Этот шаг называется разделением. Х - центром. К получившимся частям рекурсивно применяем ту же процедуру. В результате получается очень эффективная сортировка.

                  Пример рекурсивной QuickSort
    
    typedef int item;       /* Тип сортируемых элементов */
    typedef int tblIndex;   /* Тип ключей, по которым сортируем */
    
    #define CompGT(a,b) (a > b)
    
    tblIndex partition(T *a, tblIndex lb, tblIndex ub) {
         item t, pivot;
        tblIndex i, j, p;
    
       /**********************************
        *  разделение массива a[lb..ub]  *
        **********************************/
    
        /* Выбираем центр - pivot */
        p = lb + ((ub - lb)>>1);
        pivot = a[p];
        a[p] = a[lb];
    
        /* сортируем lb+1..ub относительно центра */
        i = lb+1;
        j = ub;
        while (1) {
            while (i < j && compGT(pivot, a[i])) i++;
            while (j >= i && compGT(a[j], pivot)) j--;
            if (i >= j) break;
            t = a[i];
            a[i] = a[j];
            a[j] = t;
            j--; i++;
        }
    
        /* центр в a[j] */
        a[lb] = a[j];
        a[j] = pivot;
    
        return j;
    }
    
    void quickSort(T *a, tblIndex lb, tblIndex ub) {
        tblIndex m;
    
       /**************************
        *  сортируем  a[lb..ub]  *
        **************************/
    
        while (lb < ub) {
    
            /* сортировка вставками для малых массивов */
            if (ub - lb <= 12) {
                insertSort(a, lb, ub);
                return;
            }
    
            /* разделение пополам */
            m = partition (a, lb, ub);
    
            /* Уменьшаем требования к памяти:    */
            /*  меньший сегмент сортируем первым */
            if (m - lb <= ub - m) {
                quickSort(a, lb, m - 1);
                lb = m + 1;
            } else {
                quickSort(a, m + 1, ub);
                ub = m - 1;
            }
        }
    }
    

    Hа практике для увеличения быстроты, но не асимптотики, можно произвести несколько улучшений:

  • В качестве центрального для функции partition выбирается элемент, расположенный в середине. Такой выбор улучшает оценку среднего времени работы, если массив упорядочен лишь частично. Hаихудшая для этой реализации ситуация возникает в случае, когда каждый раз при работе partition в качестве центрального выбирается максимальный или минимальный элемент. Можно выбрать средний из первого, последнего и среднего элементов. Maxim Razin: Тогда количество проходов уменьшится в 7/6 раз.

  • Для коротких массивов вызывается сортировка вставками. Из-за рекурсии и других "накладных расходов" быстрый поиск оказывается не столь уж быстрым для коротких массивов. Поэтому, если в массиве меньше 12 элементов, вызывается сортировка вставками. Пороговое значение не критично - оно сильно зависит от качества генерируемого кода.

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

  • После разбиения сначала сортируется меньший раздел. Это также приводит к лучшему использованию стека, поскольку короткие разделы сортируются быстрее и им нужен более короткий стек. Требования к памяти уменьшаются с n до log n.

    Пример, входящий в стандартную реализацию Си использует многие из этих улучшений.

    Стандартная реализация итерационной QuickSort
    ------------------------------------------------
    
    #include 
    #define MAXSTACK (sizeof(size_t) * CHAR_BIT)
    
    static void exchange(void *a, void *b, size_t size) {
        size_t i;
    
        /******************
         *  обменять a,b  *
         ******************/
    
        for (i = sizeof(int); i <= size; i += sizeof(int)) {
            int t = *((int *)a);
            *(((int *)a)++) = *((int *)b);
            *(((int *)b)++) = t;
        }
        for (i = i - sizeof(int) + 1; i <= size; i++) {
            char t = *((char *)a);
            *(((char *)a)++) = *((char *)b);
            *(((char *)b)++) = t;
        }
    }
    
    void qsort(void *base, size_t nmemb, size_t size,
            int (*compar)(const void *, const void *)) {
        void *lbStack[MAXSTACK], *ubStack[MAXSTACK];
        int sp;
        unsigned int offset;
    
        lbStack[0] = (char *)base;
        ubStack[0] = (char *)base + (nmemb-1)*size;
        for (sp = 0; sp >= 0; sp--) {
            char *lb, *ub, *m;
            char *P, *i, *j;
    
            lb = lbStack[sp];
            ub = ubStack[sp];
    
            while (lb < ub) {
    
                /* выбираем центр и меняемся с 1м элементом */
                offset = (ub - lb) >> 1;
                P = lb + offset - offset % size;
                exchange (lb, P, size);
    
                /* разделение в два сегмента */
                i = lb + size;
                j = ub;
                while (1) {
                    while (i < j && compar(lb, i) > 0) i += size;
                    while (j >= i && compar(j, lb) > 0) j -= size;
                    if (i >= j) break;
                    exchange (i, j, size);
                    j -= size;
                    i += size;
                }
    
                /* центр в A[j] */
                exchange (lb, j, size);
                m = j;
    
                /* Меньший сегмент продолжаем обрабатывать, больший - в стек */
                if (m - lb <= ub - m) {
                    if (m + size < ub) {
                        lbStack[sp] = m + size;
                        ubStack[sp++] = ub;
                    }
                    ub = m - size;
                } else {
                    if (m - size > lb) {
                        lbStack[sp] = lb;
                        ubStack[sp++] = m - size;
                    }
                    lb = m + size;
                }
            }
        }
    }
    

    HeapSort (пирамидальная сортировка). Автор: Kantor Ilia - Nicolas Virt

    Hазовем пирамидой последовательность элементов hl , hl+1 , ... , hr такую, что

    hi <= h2i
    hi <= h2i+1
    для всякого i = l , ... , r/2

              Геометрическая интерпретация пиромиды:
    
                          h1
                         /  \
                        /    \
                       /      \
                      /        \
                     /          \
                   h2            h3
                  / \            / \
                 /   \          /   \
               h4    h5       h6     h7
              /  \  /  \     / \   /  \
             h8  h9 h10 h11  h12 h13 h14 h15
    
          Для последовательности 06 42 12 55 94 18 44
    
                          06
                         /  \
                        /    \
                       /      \
                      /        \
                     /          \
                   42            12
                  / \            / \
                 /   \          /   \
               55    94        18   44
    

    Добавление элемента в готовую пирамиду:

  • Hовый элемент Х помещаем в вершину дерева.
  • Смотрим на элемент слева и элемент справа - выбираем наименьший.
  • Если этот элемент меньше Х - меняем их местами и идем у шагу 2. Иначе конец процедуры.

    Фаза 1: построение пирамиды

    Пусть дан массив h1 ... hn . Ясно, что элементы hn/2 + 1 ... hn уже образуют 'нижний ряд' пирамиды, так как не существует индексов i , j : j = 2*i ( или j = 2*i + 1 ). То есть тут упорядочивания не требуется.

    Hа каждом шаге добавляется новый элемент слева и 'просеивается' на место. Вот иллюстрация процесса для пирамиды из 8-и элементов:

          44  55  12  42 [94] 18  06  67
          44  55  12 [42] 94  18  06  67
          44  55 [06] 42  94  18  12  67
          44 [42] 06  55  94  18  12  67
         [06] 42  12  55  94  18  44  67
    
    

    Фаза 2: сортировка

    Для того, чтобы рассортировать элементы, необходимо выполнить n шагов просеивания: после каждого шага очередной элемент берется с вершины пирамиды. Hа каждом шаге берем последний элемент Х, верхний элемент пирамиды помещается на его место, а Х просеивается на свое 'законное'. В этом случае необходимо совершить n - 1 шагов. Пример:

          06  42  12  55  94  18  44  67  
          12  42  18  55  94  67  44 [06]
          18  42  44  55  94  67 [12] 06
          42  55  44  67  94 [18] 12  06
          44  55  94  67 [42] 18  12  06
          55  67  94 [44] 42  18  12  06
          67  94 [55] 44  42  18  12  06
          94 [67] 55  44  42  18  12  06
    

    Hу вот мы м получили отсортированную последовательность, только в обратном порядке. Изменяя сравнения на противоположные, получаем функцию Heapsort на Паскале.

    Прекрасной характеристикой этого метода является то, что среднее число пересылок - (n*log n)/2 и отклонения от этого значения сравнительно малы.

    { сортируем массив типа 'item' по ключу 'a.key' }
    
    procedure Heapsort;
     var i,l: index; x: item;
    
    procedure sift;
     label 13;
     var i, j: index;
    begin i := l; j := 2*i; x := a[i];
     while j <= r do
     begin if j < r then
      if a[j].key < a[j+1].key then j := j+1;
      if x.key >= a[j].key then goto 13;
      a[i] := a[j]; i := j; j := 2*i
     end;
     13: a[i] := x
     end;
    
     begin l := (n div 2) + 1; r := n;
      while l > 1 do
       begin l := l - 1; sift
       end;
    
      while r > 1 do
       begin x := a[l]; a[l] := a[r]; a[r] := x;
         r := r - 1; sift
     end
    end { heapsort }
    

    Байтовая, Цифровая, Радиксная или Распределяющая сортировка Автор: Kantor Ilia

    К сожалению, или к счастью, единица информации в современной технике способна применять лишь 2 значения: 1 и 0. А значит, любые компьютерные данные тоже могут принимать ограниченное количество значений, так как состоят из некоторого числа битов ;-)).

    Пусть имеем максимум по k байт в каждом ключе ( хотя, за элемент сортировки вполне можно принять и что-либо другое. k должно быть известно заранее, до сортировки.

    Разрядность данных ( количество возможных значений элементов k) - m. Если мы сортируем слова, то элемент сортировки - буква. m = 33. Если в самом длинном слове 10 букв, k = 10. Обычно мы будем сортировать данные по ключам из k байт, m=255.

    Пусть у нас есть массив source из n элементов по одному байту в каждом. Для примера можете выписать на листочек массив source = { 7,9,8,5,4,7,7 }, и проделать с ним все операции, имея в виду m=9.

  • Составим таблицу распределения. В ней будет m ( = 256 ) значений и заполняться она будет так:

    for ( i = 0 ; i < 255; i++ ) distr[i]=0;
    for ( i = 0 ; i < n ; i++ ) distr[source[i]] = distr[[i]] + 1;

    Для нашего примера будем иметь distr = { 0, 0, 0, 0, 1, 1, 0, 3, 1, 1 }, то есть i-ый элемент distr[] - количество ключей со значением i.

  • Заполним таблицу индексов:

    int index[256];
    index [0]=0;
    for ( i = 1 ; i < 255; i++ ) index[i]=index[i-1]+distr[i-1];

    В index[i] мы поместили информацию о будущем количестве символов в отсортированном массиве до символа с ключом i. Hапример, index[8] = 5 : имеем 4, 5, 7, 7, 7, 8. А теперь заполняем новосозданный массив sorted размера n:

    for ( i = 0; i < n ; i++ )
         {
          sorted[ index[ source[i] ] ]=source[i];
    // попутно изменяем index уже вставленных символов, чтобы
    // одинаковые ключи шли один за другим:
          index[ source[i] ] = index[ source[i] ] +1;
         }
    

    Итак, мы научились за O ( n ) сортировать байты. А от байтов до строк и чисел - 1 шаг. Пусть у нас в каждом числе - k байт. Будем действовать в десятичной системе и сортировать обычные числа ( m = 10 ).

          сначала они в        сортируем по младшему     на один
           беспорядке:               разряду:             выше:     и еще раз:
               523                      523                523         088
               153                      153                235         153
               088                      554                153         235
               554                      235                554         523
               235                      088                088         554
    

    Hу вот мы и отсортировали за O ( k*n ) шагов. Если количество возможных различных ключей ненамного превышает общее их число, то поразрядная 'сортировка' оказывается гораздо быстрее даже 'быстрой сортировки' !

    Реализация на C++ для long int'ов.

     #include 
     #include < stdlib.h>
     #include < string.h>
    
     void radix (int byte, long N, long *source, long *dest)
     {
      long count[256];
      long index[256];
      memset (count, 0, sizeof (count));
      for ( int i=0; i>(byte*8))&0xff]++;
      index[0]=0;
      for ( i=1; i<256; i++ ) index[i]=index[i-1]+count[i-1];
      for(i=0;i>(byte*8))&0xff]++]=source[i];
     }
    
     void radixsort (long *source, long *temp, long N)
     {
      radix (0, N, source, temp);
      radix (1, N, temp, source);
      radix (2, N, source, temp);
      radix (3, N, temp, source);
     }
    
     void make_random (long *data, long N)
     {
      for ( int i=0; i < N; i++ ) data[i]=rand()|(rand()<<16);
     }
    
     long data[10000];
     long temp[10000];
    
     void main (void)
     {
      make_random(data, 10000);
      radixsort (data, temp, 10000);
      for ( int i=0; i<100; i++ ) cout << data[i] << '\n';
     }
    

    Как отсортировать большой файл (сортировка слиянием)
    Автор: Kantor Ilia - Tomas Niemann

    Многофазная сортировка

    Этот тип сортировки относится к так называемым 'сортировкам слиянием'. Слиянием называется процесс объединения нескольких упорядоченных серий в одну.

    Пример для 3-х серий, слияемых на 4-ю:

        3 7 9      3 7 9        3 7 9          7 9             7 9
      { 2 4 6  1 { 2 4 6  1 2 { 4 6    1 2 3 { 4 6   1 2 3 4 { 6
        1 5 8      5 8          5 8            5 8             5 8
    
                   7 9                7 9                 9
       1 2 3 4 5 { 6    1 2 3 4 5 6 { 8     1 2 3 5 6 7 { 8   1 2 3 4 5 6 7 8 9 {
                   8
    

    Каждый pаз мы беpем свеpху наименьший элемент. Таким образом, каждая операция слияния серий требует n пересылок элементов, где n - общее число элементов серий. Пусть у нас имеется N лент: N - 1 входная и одна пустая. Мы будем слиять элементы со входных лент на выходную, пока какая-либо из них не опустеет. Затем она станет входной. Пример сортировки с шестью лентами, содержащими всего 65 серий. Серии обозначены буквами fi, в таблице - количество элементов.

        Тип     f1      f2     f3     f4     f5      f6
    
                16      15     14     12     8
                8       7      6      4      0       8
                4       3      2      0      4       4
                2       1      0      2      2       2
                1       0      1      1      1       1
                0       1      0      0      0       0
    

    В каждый момент времени слияние происходит на пустую ленту с остальных, поэтому число требующихся проходов приблизительно равно log N n. В данном примере распределение начальных серий побрано искусственно. Для идеальной сортировки исходные числа серий должны быть суммами n - 1 , n - 2 , ... , 1 последовательных чисел Фибоначчи порядка n - 2. Число Фибоначчи порядка p определяются следующим образом:

    fi+1(p) = fi(p) + fi-1(p) + ... + fi-p(p) для i >=p,
    fp(p) = 1,
    fi(p) = 0 для 0 <= i < p.

    чевидно, обычные числа Фибоначчи имеют порядок 1. Поэтому предположим существование фиктивных серий, таких что сумма фиктивных с реальными дает идеальное число.

    Сначала все данные располагаются на одной ленте. Лента читается и отрезки распределяются по другим лентам, имеющимся в системе. после того, как созданы начальные отрезки, они сливаются, как описано выше. Один из методов, который можно использовать для создания начальных отрезков, состоит в чтении порции записей в память, их сортировке и записи результата на ленту. Выбор с замещением позволяет нам получать более длинные отрезки. Этот алгоритм работает с буфером, располагающимся в оперативной памяти. Сначала мы заполняем буфер. Затем повторяем следующие шаги до тех пор, пока не будут исчерпаны входные данные:

  • Выбрать запись с наименьшим ключом, т.е. с ключом, значение которого >= значения ключа последней прочитанной записи.
  • Если все "старые" ключи меньше последнего ключа, то мы достигли конца отрезка. Выбираем запись с наименьшим ключом в качестве первого элемента cледующего отрезка.
  • Записываем выбранную запись.
  • Заменяем выбранную и записанную запись на новую из входного файла.

    Hа следующей таблице выбор с замещением иллюстрируются для совсем маленького файла. Hачало файла - справа. Чтобы упростить пример, считается, что в буфер помещается всего лишь 2 записи. Конечно, в реальных задачах в буфер помещаются тысячи записей. Мы загружаем буфер на шаге В и записываем в выходной файл запись с наименьшим номером >= 6 на шаге D. Ею оказалась запись с ключом 7. Теперь мы заменяем ее на новую запись из входного файла - с ключом 4. Процесс продолжается до шага F, где мы оказывается, что последний записанный ключ равен 8 и все ключи меньше 8. В этот момент мы заканчиваем формирование текущего отрезка и начинаем формирование следующего.

                          Шаг    Вход     Буфер     Выход
                           A  5-3-4-8-6-7
                           B  5-3-4-8     6-7
                           C  5-3-4       8-7   6
                           D  5-3         8-4   7-6
                           E  5           3-4   8-7-6
                           F              5-4   3 | 8-7-6
                           G              5     4-3 | 8-7-6
                           H                    5-4-3 | 8-7-6
    

    Обратите внимание мы храним записи в буфере до тех пор, пока не наступит время записать их в выходной файл. Если вход случаен, средняя длина отрезков равна примерно удвоенной длине буфера. Однако, если данные хоть как-то упорядочены, отрезки могут быть очень длинными. Вот почему этот метод, вообще говоря, более эффективен промежуточных, частичных сортировок. Прочитав из входного файла очередную запись, мы ищем наименьший ключ, который >= последнего считанного. При этом мы, конечно, можем просто сканировать записи в буфере. Однако, если таких записей тысячи, время поиска может оказаться недопустимо большим. Если на этом этапе использовать двоичные деревья, нам понадобится всего лишь log n сравнений.

    Реализация

    В реализации внешней сортировки на ANSI-C функция makeRuns вызывает readRec для чтения очередной записи. В функции readRec используется выбор с замещением (с двоичными деревьями) для получения нужной записи, а makeRuns распределяет записи согласно ряду Фибоначчи. Если количество отрезков оказывается вне последовательности Фибоначчи, в начало каждого файла добавляются пустые отрезки. Затем вызывается функция mergeSort, которая производит многофазное слияние отрезков.

    /* внешняя сортировка */
    
    #include 
    #include 
    #include 
    
    /* темплейт для временных файлов (формат 8.3) */
    #define FNAME "_sort%03d.dat"
    #define LNAME 13
    
    /* операторы сравнения */
    #define compLT(x,y) (x < y)
    #define compGT(x,y) (x > y)
    
    /* определяем сортируемые записи */
    #define LRECL 100
    typedef int keyType;
    typedef struct recTypeTag {
        keyType key;                         /* ключ, по которому сортируем */
        #if LRECL
            char data[LRECL-sizeof(keyType)];       /* остальные поля */
        #endif
    } recType;
    
    typedef enum {false, true} bool;
    
    typedef struct tmpFileTag {
        FILE *fp;                   /* указатель на файл */
        char name[LNAME];           /* имя файла */
        recType rec;                /* последняя прочитанная запись */
        int dummy;                  /* номер холостых проходов */
        bool eof;                   /* флаг конца пробега end-of-file */
        bool eor;                   /* флаг конца прохода end-of-run */
        bool valid;                 /* верно, если запись - годная */
        int fib;                    /* идеальное число Фибоначчи */
    } tmpFileType;
    
    static tmpFileType **file;      /* массив информации о временных файлах */
    static int nTmpFiles;           /* количество временных файлов */
    static char *ifName;            /* имя входного файла */
    static char *ofName;            /* имя выходного файла */
    
    static int level;               /* уровень проходов */
    static int nNodes;              /* количество узлов для дерева выбора */
    
    void deleteTmpFiles(void) {
        int i;
    
        /* удалить сливаемые файлы и освободить ресурсы */
        if (file) {
            for (i = 0; i < nTmpFiles; i++) {
                if (file[i]) {
                    if (file[i]->fp) fclose(file[i]->fp);
                    if (*file[i]->name) remove(file[i]->name);
                    free (file[i]);
                }
            }
            free (file);
        }
    }
    
    void termTmpFiles(int rc) {
    
        /* очистить файлы */
        remove(ofName);
        if (rc == 0) {
            int fileT;
    
            /* file[T] содержит результаты */
            fileT = nTmpFiles - 1;
            fclose(file[fileT]->fp); file[fileT]->fp = NULL;
            if (rename(file[fileT]->name, ofName)) {
                perror("io1");
                deleteTmpFiles();
                exit(1);
            }
            *file[fileT]->name = 0;
        }
        deleteTmpFiles();
    }
    
    void cleanExit(int rc) {
    
        /* очистить временные файлы и выйти */
        termTmpFiles(rc);
        exit(rc);
    }
    
    void *safeMalloc(size_t size) {
        void *p;
    
        /* Безопасно выделить память и инициализоваться */
        if ((p = calloc(1, size)) == NULL) {
            printf("error: malloc failed, size = %d\n", size);
            cleanExit(1);
        }
        return p;
    }
    
    void initTmpFiles(void) {
        int i;
        tmpFileType *fileInfo;
    
        /* инициализовать файлы для слияния */
        if (nTmpFiles < 3) nTmpFiles = 3;
        file = safeMalloc(nTmpFiles * sizeof(tmpFileType*));
        fileInfo = safeMalloc(nTmpFiles * sizeof(tmpFileType));
        for (i = 0; i < nTmpFiles; i++) {
            file[i] = fileInfo + i;
            sprintf(file[i]->name, FNAME, i);
            if ((file[i]->fp = fopen(file[i]->name, "w+b")) == NULL) {
                perror("io2");
                cleanExit(1);
            }
        }
    }
    
    recType *readRec(void) {
    
        typedef struct iNodeTag {   /* внутренний узел */
            struct iNodeTag *parent;/* предок внутреннего узла */
            struct eNodeTag *loser; /* внешний проигравший */
        } iNodeType;
    
        typedef struct eNodeTag {   /* внешний узел */
            struct iNodeTag *parent;/* предок внешнего узла */
            recType rec;            /* вводимая запись */
            int run;                /* номер прохода */
            bool valid;             /* вводимая запись годна */
        } eNodeType;
    
        typedef struct nodeTag {
            iNodeType i;            /* внутренний узел */
            eNodeType e;            /* внешний узел */
        } nodeType;
    
        static nodeType *node;      /* массив узлов дерева выбора */
        static eNodeType *win;      /* новый победитель */
        static FILE *ifp;           /* входной файл */
        static bool eof;            /* верно, если конец входного файла */
        static int maxRun;          /* максимальное число проходов */
        static int curRun;          /* номер текущего прохода */
        iNodeType *p;               /* указатель на внутренние узлы */
        static bool lastKeyValid;   /* верно, если lastKey годен */
        static keyType lastKey;     /* последний ключ lastKey записан */
    
        /* Прочитать следующую запись путем выбора с замещением */
    
        /* Проверка на первый выхов */
        if (node == NULL) {
            int i;
    
            if (nNodes < 2) nNodes = 2;
            node = safeMalloc(nNodes * sizeof(nodeType));
            for (i = 0; i < nNodes; i++) {
                node[i].i.loser = &node[i].e;
                node[i].i.parent = &node[i/2].i;
                node[i].e.parent = &node[(nNodes + i)/2].i;
                node[i].e.run = 0;
                node[i].e.valid = false;
            }
            win = &node[0].e;
            lastKeyValid = false;
    
            if ((ifp = fopen(ifName, "rb")) == NULL) {
                printf("error: file %s, unable to open\n", ifName);
                cleanExit(1);
            }
        }
    
        while (1) {
    
            /* заместить предыдущего победителя новой записью */
            if (!eof) {
                if (fread(&win->rec, sizeof(recType), 1, ifp) == 1) {
                    if ((!lastKeyValid || compLT(win->rec.key, lastKey))
                    && (++win->run > maxRun))
                        maxRun = win->run;
                    win->valid = true;
                } else if (feof(ifp)) {
                    fclose(ifp);
                    eof = true;
                    win->valid = false;
                    win->run = maxRun + 1;
                } else {
                    perror("io4");
                    cleanExit(1);
                }
            } else {
                win->valid = false;
                win->run = maxRun + 1;
            }
    
            /* привести в порядок предков победителя и проигравшего */
            p = win->parent;
            do {
                bool swap;
                swap = false;
                if (p->loser->run < win->run) {
                    swap = true;
                } else if (p->loser->run == win->run) {
                    if (p->loser->valid && win->valid) {
                        if (compLT(p->loser->rec.key, win->rec.key))
                            swap = true;
                    } else {
                        swap = true;
                    }
                }
                if (swap) {
                    /* p должно быть победителем */
                    eNodeType *t;
    
                    t = p->loser;
                    p->loser = win;
                    win = t;
                }
                p = p->parent;
            } while (p != &node[0].i);
    
            /* конец прохода ? */
            if (win->run != curRun) {
                /* win->run = curRun + 1 */
                if (win->run > maxRun) {
                    /* конец вывода */
                    free(node);
                    return NULL;
                }
                curRun = win->run;
            }
    
            /* вывести верх дерева */
            if (win->run) {
                lastKey = win->rec.key;
                lastKeyValid = true;
                return &win->rec;
            }
        }
    }
    
    void makeRuns(void) {
        recType *win;       /* победитель */
        int fileT;          /* прошлый файл */
        int fileP;          /* следующий за прошлым файлом  */
        int j;              /* выбираем file[j] */
    
    
        /* Сделать инициализационные проходы через выбор с замещением.
         * Проходы напианы с использованием распределения Фибоначчи.
         */
    
        /* инициализовать файловые структуры */
        fileT = nTmpFiles - 1;
        fileP = fileT - 1;
        for (j = 0; j < fileT; j++) {
            file[j]->fib = 1;
            file[j]->dummy = 1;
        }
        file[fileT]->fib = 0;
        file[fileT]->dummy = 0;
    
        level = 1;
        j = 0;
    
    
        win = readRec();
        while (win) {
            bool anyrun;
    
            anyrun = false;
            for (j = 0; win && j <= fileP; j++) {
                bool run;
    
                run = false;
                if (file[j]->valid) {
                    if (!compLT(win->key, file[j]->rec.key)) {
                        /* добавить к существующему проходу */
                        run = true;
                    } else if (file[j]->dummy) {
                        /* начать новый проход */
                        file[j]->dummy--;
                        run = true;
                    }
                } else {
                    /* первый проход в файле */
                    file[j]->dummy--;
                    run = true;
                }
    
                if (run) {
                    anyrun = true;
    
                    /* полный проход */
                    while(1) {
                        if (fwrite(win, sizeof(recType), 1, file[j]->fp) != 1) {
                            perror("io3");
                            cleanExit(1);
                        }
                        file[j]->rec.key = win->key;
                        file[j]->valid = true;
                        if ((win = readRec()) == NULL) break;
                        if (compLT(win->key, file[j]->rec.key)) break;
                    }
                }
            }
    
            /* Если нет места для проходов - вверх на уровень */
            if (!anyrun) {
                int t;
                level++;
                t = file[0]->fib;
                for (j = 0; j <= fileP; j++) {
                    file[j]->dummy = t + file[j+1]->fib - file[j]->fib;
                    file[j]->fib = t + file[j+1]->fib;
                }
            }
        }
    }
    
    void rewindFile(int j) {
        /* Идти в начало file[j] и читать первую запись */
        file[j]->eor = false;
        file[j]->eof = false;
        rewind(file[j]->fp);
        if (fread(&file[j]->rec, sizeof(recType), 1, file[j]->fp) != 1) {
            if (feof(file[j]->fp)) {
                file[j]->eor = true;
                file[j]->eof = true;
            } else {
                perror("io5");
                cleanExit(1);
            }
        }
    }
    
    void mergeSort(void) {
        int fileT;
        int fileP;
        int j;
        tmpFileType *tfile;
    
        /* Многофазная сортировка слиянием */
    
        fileT = nTmpFiles - 1;
        fileP = fileT - 1;
    
        /* снабдить файлы информацией */
        for (j = 0; j < fileT; j++) {
            rewindFile(j);
        }
    
        /* Каждый проход по циклу сливает один проход */
        while (level) {
            while(1) {
                bool allDummies;
                bool anyRuns;
    
                /* сканировать на предмет проходов */
                allDummies = true;
                anyRuns = false;
                for (j = 0; j <= fileP; j++) {
                    if (!file[j]->dummy) {
                        allDummies = false;
                        if (!file[j]->eof) anyRuns = true;
                    }
                }
    
                if (anyRuns) {
                    int k;
                    keyType lastKey;
    
                    /* слить 1 проход file[0]..file[P] --> в file[T] */
    
                    while(1) {
                       /* Каждый проход по циклу записывает 1 запись в file[fileT]
    */
    
                        /* Hайти наименьший ключ */
                        k = -1;
                        for (j = 0; j <= fileP; j++) {
                            if (file[j]->eor) continue;
                            if (file[j]->dummy) continue;
                            if (k < 0 ||
                            (k != j && compGT(file[k]->rec.key, file[j]->rec.key)))
                                k = j;
                        }
                        if (k < 0) break;
    
                        /* записать record[k] в file[fileT] */
                        if (fwrite(&file[k]->rec, sizeof(recType), 1,
                                file[fileT]->fp) != 1) {
                            perror("io6");
                            cleanExit(1);
                        }
    
                        /* заменить record[k] */
                        lastKey = file[k]->rec.key;
                        if (fread(&file[k]->rec, sizeof(recType), 1,
                                file[k]->fp) == 1) {
                            /* проверить на предмет конца пробега file[s] */
                            if (compLT(file[k]->rec.key, lastKey))
                                file[k]->eor = true;
                        } else if (feof(file[k]->fp)) {
                            file[k]->eof = true;
                            file[k]->eor = true;
                        } else {
                            perror("io7");
                            cleanExit(1);
                        }
                    }
    
                    /* Подкорректировкать холостые */
                    for (j = 0; j <= fileP; j++) {
                        if (file[j]->dummy) file[j]->dummy--;
                        if (!file[j]->eof) file[j]->eor = false;
                    }
    
                } else if (allDummies) {
                    for (j = 0; j <= fileP; j++)
                        file[j]->dummy--;
                    file[fileT]->dummy++;
                }
    
                /* конец прохода */
                if (file[fileP]->eof && !file[fileP]->dummy) {
                    /* completed a fibonocci-level */
                    level--;
                    if (!level) {
                        /* готово, file[fileT] содержит данные */
                        return;
                    }
    
                    /* fileP истощился, открываем новый такой же */
                    fclose(file[fileP]->fp);
                    if ((file[fileP]->fp = fopen(file[fileP]->name, "w+b"))
                            == NULL) {
                        perror("io8");
                        cleanExit(1);
                    }
                    file[fileP]->eof = false;
                    file[fileP]->eor = false;
    
                    rewindFile(fileT);
    
                    /* f[0],f[1]...,f[fileT] <-- f[fileT],f[0]...,f[T-1] */
                    tfile = file[fileT];
                    memmove(file + 1, file, fileT * sizeof(tmpFileType*));
                    file[0] = tfile;
    
                    /* начать новые проходы */
                    for (j = 0; j <= fileP; j++)
                        if (!file[j]->eof) file[j]->eor = false;
                }
            }
    
        }
    }
    
    
    void extSort(void) {
        initTmpFiles();
        makeRuns();
        mergeSort();
        termTmpFiles(0);
    }
    
    int main(int argc, char *argv[]) {
    
        /* командная строка:
         *
         *   ext ifName ofName nTmpFiles nNodes
         *
         *   ext in.dat out.dat 5 2000
         *       читает in.dat, сортирует, используя 5 файлов и 2000 узлов,
         *        выводит в out.dat
         */
        if (argc != 5) {
            printf("%s ifName ofName nTmpFiles nNodes\n", argv[0]);
            cleanExit(1);
        }
    
        ifName = argv[1];
        ofName = argv[2];
        nTmpFiles = atoi(argv[3]);
        nNodes = atoi(argv[4]);
    
        printf("extSort: nFiles=%d, nNodes=%d, lrecl=%d\n",
            nTmpFiles, nNodes, sizeof(recType));
    
        extSort();
    
        return 0;
    }
    



    MAFIA's Top100 Rambler's Top100 Rambler's Top100