Организация и функционирование компьютеров
bf1271d8

Сортировка массивов и файлов


Сортировкой базы данных называется переписывание записей базы данных в порядке, задаваемом при сортировке. В случае, когда наряду с базой данных поддерживается индекс базы данных, сортировка данных сводится к записи в выходной файл записей исходной базы данных в порядке, заданном индексом (смотри выше). Если нужный индекс не поддерживается, то можно его создать и далее поступить аналогичным образом. Однако зачастую это нужно сделать быстро, а сортировка на основе индексирования не оптимальна по времени. Существуют много других методов сортировки. Мы приведем несколько алгоритмов для того, чтобы проиллюстрировать  методы программирования вообще и задач информатики в частности.

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

Пусть задан массив a целых чисел длины n. Первый способ сортировки называется сортировкой обменом. Идея алгоритма достаточно проста. Найдем среди элементов массива минимальный и поменяем его с первым элементом, затем найдем минимальный среди оставшихся и поменяем его со вторым элементом и т.д. Фрагмент программы на Паскале выглядит так:

for

j:=1 to n-1 do  {Для всех элементов массива, кроме последнего, сделать}

    begin  m := j;     {Ищем минимальный элемент (вначале j-й) }

                for k:=j+1 to n do

                    if  a[k]<a[m]  then  m:=k;    {Текущий минимальный элемент - k-й}

                x          := a[j];     {Обменять минимальный элемент с j-ым}

                a[j]     := a[m];

                a[m]   := x

    end;

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



В конце цикла самый большой элемент автоматически оказывается в конце массива (“всплывает” в конец массива, отсюда название “пузырьковый”). Во втором цикле то же самое проделывается с элементами массива, исключая последний, и т.д. Фрагмент программы на Паскале выглядит так:

for

j:=n downto 2 do  {Для всех начальных частей массива сделать}

    for

k:=1 to j-1 do   {Сравнить все пары соседних элементов от первого до (j-1)-го}

        if  a[k]>a[k+1]   

            begin   x            := a[k];     {Обменять k-й элемент с (k+1)-ым}

                          a[k]      := a[k+1];

                          a[k+1]:= x

            end;

Третий способ сортировки называется сортировкой слиянием. Пусть массив a длины n и массив b

длины m уже отсортированы в возрастающем порядке, и пусть мы хоти молучить массив длины (n+m), состоящий из их объединения и также отсортированный. Это делается за один проход по массиву a и массиву b

следующим образом:

j:=1;   k:=1;   i:=1;  {Текущими являются первые элементы массивов a, b и c}

while (j<=n) or (k<=m) do   {Пока не кончатся оба массива}

    begin

        if  (j<=n) and ((k>m) or

(a[j]<=b[k])) then

                {Массив a не кончился, а массив b кончился, либо массив b не кончился и

                 текущий элемент массива a меньше или равен текущему элементу массива b}

            begin   c[i]:=a[j];  {Записываем в массив c элемент массива a}

                          j:=j+1;          {Сдвигаем текущий элемент массива a}

                          l:=i+1           {Сдвигаем текущий элемент массива c}

            end

        else if  (k<=m) and ((j>n) or

(a[j]>b[k])) then

                {Массив b не кончился, а массив a кончился, либо массив a не кончился и

                 текущий элемент массива a больше текущего элемента массива b}

            begin   c[i]:=b[k];  {Записываем в массив c элемент массива b}

                          k:=k+1;         {Сдвигаем текущий элемент массива b}



                          l:=i+1            {Сдвигаем текущий элемент массива a}

            end

    end;

Идея полного алгоритма сортировки массива слиянием заключается в следующем. Предположим для простоты, что длина массива равна степени двойки. Разобьем весь массив на пары соседних элементов и упорядочим каждку пару по возрастанию. Затем каждые две соседних пары сольем в одну упорядоченную четверку. В следующем цикле сольем каждые две соседних четверки в одну упорядоченную восьмерку и т.д. Для такого алгоритма необходима модификация описанной выше процедуры слияния, которая сливала бы два следующих друг за другом фрагмента одного массива и записывала результат в другой массив. Если длина массива не равна степени двойки, то сливаемые фрагменты массива могут иметь разную длину, но принципиально ничего не изменится.

Преимущество сортировки слиянием заключается в том, что один цикл слияния происходит за один проход всего массива и поэтому таким методом можно сливать не только массивы, но и большие файлы, списывая их фрагментами в массивы в оперативную память. Алгоритм при этом несколько усложняется, но принципиально остается тем же. Количество циклов при сортировке слиянием (число проходов по всему массиву или файлу) равно приблизительно log2n, где n - длина массива или файла.

Принципиально иной алгоритм сортировки основан на идее упорядочения массива по частям. Пусть m и M - минимальное и максимальное значения элементов массива. Положим b=(m+M)/2.  Переставим элементы массива так, чтобы в начале шли ( в произвольном порядке) элементы, меньшие или равные b, а затем элементы большие b. Для этого будем двигаться одновременно с начала массива до элемента, большего b, и с конца массива до элемента, меньшего b. Как только мы их найдем, переставим эти элементы местами и начнем двигаться дальше. Процесс заканчивается тогда, когда мы встретимся где-то в середине массива. Соответствующий фрагмент программы на Паскале записывается так:

j:=1;   k:=n;   {Текущими являются первый и последний элементы массива}



while  j<k  do {Пока мы не встретимся в середине массива}

    begin

        while  (j<k) and (a[j]<=b) do      j:=j+1;     { Дойти до номера j, для которого a[j]>b}

        while  (j<k) and (a[k]>b) do         k:=k-1;      {Дойти до номера k, для которого a[k]<=b}

        if  j<k  then  begin  x := a[j];                           {Поменять a[j] и a[k]}

                                           a[j] :=a[k];

                                           a[k]:=x

                               end

    end;

После описанной процедуры осталось отсортировать отдельно первую часть массива (до конечного значения переменных j и k) и вторую часть массива. Для этого аналогичную процедуру нужно повторить для первой части массива и значения b1=(b+m)/2  и второй части массива и значения b2=(b+M)/2. Для полученных двух кусков первой и второй частей повторить то же самое и т.д. Процесс прекращается тогда, когда при делении образуются фрагменты массива длины 1. Конечно, процедуру нужно модифицировать таким образом, чтобы она умела разделять произвольные фрагменты массива с произвольным пороговым значением b.

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

до n). Будем считать, что элемент a[k] подчиняет элементы a[2k] и a[2k+1]  (если 2k+1 или 2k больше n, соответствующие стрелки в дереве отсутствуют). корнем дерева является элемент a[1]. Постараемся перестановками элементов добиться того, чтобы каждый отец был не меньше своих сыновей. Назовем такое дерево регулярным. Очевидно, максимальным в регулярном дереве является корень a[1]. Поменяем теперь элементы a[1]

и a[n] и будем рассматривать оставшуюся часть массива длины n-1. Эта часть не будет регулярным деревом, так как оно испорчено: одна из вершин (с номером n) выкинута, а ее значение перенесено в корень. Однако испорчено оно несильно и его можно быстро исправить: корень дерева нужно поменять местами с максимальным из его сыновей, этого сына - с максимальным из его сыновей и т.д., пока очередная вершина не окажется больше своих сыновей либо их не будет вовсе.


После этого в корне снова окажется максимальный элемент из оставшихся (n-1)-го, и его следует поменять с элементом a[n-1]. Производя подобную процедуру со все меньшими фрагментами массива, мы добьемся того, что массив будет весь отсортирован.

Приведем вспомогательную процедуру восстановления регулярности дерева в корне (k - длина еще не отсортированной части массива):

j :=

1;    {дерево регулярно везде, кроме, возможно, вершины  с номером j}

while ((2*j+1<= k) and

(a[2*j+1] > a[j])) or ((2*j <= k) and (a[2*j] > a[j]))  do

    if (2*j+1 <= k) and (a[2*j+1]>=a[2*j])  {Больше элемент a[2j+1]}

        then begin   x:=a[2*j+1];  {обменять местами a[j] и a[2j+1] }

                               a[2*j+1]:=a[j];

                               a[j] := x;

                               j := 2*j+1   {текущей будет вершина с номером  2j+1}

                end

        else begin    x:=a[2*j];  {обменять местами a[j] и a[2j] }

                               a[2*j]:=a[j];

                               a[j] := x;

                               j := 2*j   {текущей будет вершина с номером  2j}

                end;

Возможно вместо указанного алгоритма использовать рекурсивную процедуру:

procedure

regul (j,k: integer);  {Восстановить регулярность поддерева с корнем j в массиве длины k}

var  i: integer;

begin

    if         2*j> k    then  i:=0         {Вершина j не имеет подчиненных}

    else

if  2*j= k    then  i:=2*j     {Вершина j имеет одну подчиненную вершину 2j}

    else                                                {Вершина j имеет две подчиненные вершины 2j и 2j+1}

        if  a[2*j+1]>=a[2*j]              {Выбрать максимальную из двух подчинееных вершин}

            then  i:=2*j+1

            else   i:= 2*j;

    if  (i>0) and  (a[j]<a[i])  then  {Вершина j не регулярная}

        begin   x      := a[i];  {обменять местами a[j] и a[i] }

                     a[i] := a[j];

                     a[j] := x;

                     regul (i,k)   {Рекурсивный вызов процедуры regul для того, чтобы восстановить

                                               регулярность поддерева с корнем в вершине с номером  i}

        end

end;

Аналогичная процедура используется для того, чтобы сделать весь массив регулярным на начальной стадии сортировки. Мы не будем ее здесь приводить.


Содержание раздела