ЛЕКЦИЯ 3. СОРТИРОВКА 1. Модель внутренней сортировки 2. Простые схемы сортировки: Алгоритм «пузырьковой» сортировки Алгоритм выборочной сортировки Алгоритм сортировки посредством выбора Сортировкой, или упорядочиванием списка объектом называется расположение этих объектов по возрастанию или убыванию согласно определенному линейному отношению порядка, такому как отношение «<» для чисел. Это очень большая тема, поэтому она разбита на две части: внутреннюю и внешнюю сортировку. При внутренней сортировке все сортируемые данные помещаются в оперативную память компьютера, где можно получить доступ к данным в любом порядке (т.е. используется модель памяти с произвольным доступом), внешняя сортировка применяется тогда, когда объем упорядочиваемых данных слишком большой, чтобы все данные можно было поместить в оперативную память. Здесь узким мечом является механизм перемещения больших блоков данных oт устройств внешнего хранения данных к оперативной памяти компьютера и обратно. Тог факт, что физически непрерывные данные надо для удобного перемещения организовывать в блочную структуру, 1 заставляет нас применять разнообразные методы внешней сортировки. Эти методы будут рассмотрены позже. 1. Модель внутренней сортировки В этой главе мы представим основные принципиальные алгоритмы внутренней сортировки. Простейшие из лих алгоритмов затрачивают время порядка 0(n2) для упорядочивания п объектов и потому применимы только к небольшим множествам объектов. Один из наиболее популярных алгоритмов сортировки, так называемая быстрая сортировка, выполняется в среднем за время 0(пlogn). Быстрая сортировка хорошо работает в большинстве приложений, хотя в самом худшем случае она также имеет время выполнения 0(n2). Существуют другие методы сортировки, такие как пирамидальная сортировка или сортировка слиянием, которые в самом худшем случае да.n время порядка O(nlogn), но в среднем (в статистическом смысле) работают не лучше, чем быстрая сортировка. Отметим, что метод сортировки слиянием хорошо подходит для построения алгоритмов внешней сортировки. Мы также рассмотрим алгоритмы другого типа, имеющие общее название "карманной” сортировки. Эти алгоритмы работают только с данными определенного типа, например с целыми числами из ограниченного 2 интервала, но когда их можно применить, то они работают очень быстро, затрачивая время порядка 0(п) в самом худшем случае. На этой лекции мы будем предполагать, что сортируемые объекты являются записями, содержащими одно иди несколько полей. Одно из полей, называемое ключом, имеет такой тип данных, что на нем определено отношение линейного порядка “<=”. Целые и действительные числа, символьные массивы — вот общие примеры таких типов данных, но, конечно, мы можем использовать ключи других типов данных, лишь бы на них можно было определить отношение «меньше чем» или «меньше чем или равно». Задача сортировки состоят в упорядочивании последовательности записей таким образом, чтобы значения ключевого поля составляли неубывающую последовательность. Другими словами, записи r1 , r2 ,..., rп со значениями ключей k1,k2, … , kп надо расположить в порядке r1 , r2 ,..., rп , таком, что k1≤k2≤ … ≤ kп. Мы не требуем, чтобы все записи были различными, и если есть записи с одинаковыми значениями ключей, то в упорядоченной последовательности они располагаются рядом друг с другом в любом порядке. Мы будем использовать различные критерии оценки времени выполнения алгоритмов внутренней сортировки. Первой и наиболее обшей мерой времени выполнения является количество шагов алгоритма, 3 необходимых для упорядочивания п записей. Другой обшей мерой служит количество сравнений между значениями ключей, выполняемых при сортировке списка из п записей. Эта мера особенно информативна, когда ключи являются строками символов, и поэтому самым "трудоемким" оператором будет оператор сравнения ключей. Если размер записей большой, то следует также учитывать время, необходимое на перемещение записей. При создании конкретных приложений обычно ясно, по каким критериям нужно оценивать применяемый алгоритм сортировки. 2. Простые схемы сортировки По-видимому, самым простым методом сортировки является так называемый метод "пузырька". Чтобы описать основную идею этого метода, представим, что записи, подлежащие сортировке, хранятся в массиве, расположенном вертикально, Записи с малыми значениями ключевого поля более "легкие" и "всплывают" вверх наподобие пузырька. При первом проходе вдоль массива, начиная проход снизу, берется первая запись массива и ее ключ поочередно сравнивается с ключами последующих записей. Если встречается запись с более "тяжелым" ключом, то эти записи меняются местами. При встрече с записью с более "легким" ключом эта запись становится 4 "эталоном" для сравнения, и все последующие записи сравниваются с этим новым, более "легким" ключом. В результате запись с наименьшим значением ключа окажется в самом верху массива. Во время второго прохода вдоль массива находится запись со вторым по величине ключом, которая помешается под записью, найденной при первом проходе массива. т е. на вторую сверху позицию и т|.д. Отметим, что во время второго и последующих проходов вдоль массива нет необходимости просматривать записи, найденные за предыдущие проходы, гак как они имеют ключи, меньшие, чем у оставшихся записей. Другими словами, во время i-го прохода не проверяются записи, стоящие на позициях выше i. В листинге 1.1 приведен описываемый алгоритм, в котором через А обозначен массив из п записей (тип данных целые, вещественные или строковые). Здесь и далее в данной лекции предполагаем, что одно из полей записей называется key (ключ) и содержит значения ключей. 1 .Алгоритм «пузырька».т Массив с числами «5 1. Первый проход: 1 4 2 8». (5 1 4 2 8) (1 5 4 2 8), Здесь алгоритм сравнивает два первых элемента и меняет их местами. (1 5 4 2 8) (1 4 5 2 8), Меняет местами, 5 так как 5 >4 (1 4 5 2 8) (1 4 2 5 8), Меняет местами, так как 5 > 2 (1 4 2 5 8) (1 4 2 5 8), 8 > 5, не меняем 2. Второй проход: (1 4 2 5 (1 4 2 5 как 4 > 2 (1 2 4 5 (1 2 4 5 8) (1 4 2 5 8), 4 > 1, не меняем 8) (1 2 4 5 8), Меняет местами, так 8) (1 2 4 5 8), 5 > 4, не меняем 8) (1 2 4 5 8), 8 > 5, не меняем i=1,n,1 j = 1 , n-1 , 1 aj = aj+1 Вывод aj , j = 1,n 6 r = aj aj = aj+1 aj+1 = r Пример 2. В табл. приведен список названий которого необходимо упорядочить по алфавиту. Исходное 1-й 2-й 3-й 4-й 5-й проход проход проход проход Темур Андрей Андрей Андрей Андрей Андрей Эдуард Темур Валерий Валерий Валерий Валерий Кирилл Эдуард Темур Кирилл Кирилл Кирилл Андрей Кирилл Эдуард Темур Темур Темур Сергей Валерий Кирилл Эдуард Сергей Сергей Валерий Сергей Сергей Эдуард Эдуард состояние проход Сергей 2. Выборочная сортировка Bторой метод, который мы рассмотрим, называется выборочной сортировкой, так как на i-м этапе мы «вставляем» i-й элемент аi нужную позицию среди элементов массива, на первую позицию, которые уже упорядочены. После этой вставки первые i элементов будут упорядочены. Главным недостатком пузырьковой сортировки является количество перестановок элементов массива. В выборочной сортировке этот недостаток устранен. Здесь элемент сразу занимает свою конечную позицию. Как при пузырьковой сортировке, данный алгоритм будет просматривать подмассивы и отыскивать наименьший элемент в каждом из них. А далее последует единственный обмен в данном подмассиве. 7 Пример, упорядочить массив по возрастанию: 9 8 6 2 1 5 – исходное 1 8 6 2 9 5 – первый проход 1 2 6 8 9 5 – второй проход 1 2 5 8 9 6– третий проход 1 2 5 6 9 8– четвертый проход 1 2 5 6 8 9– пятый проход i = 1 , n-1 , 1 min = ai , k = i j=1,n,1 min = aj k=j min = aj r = ai , ai = ak , ak = r 8 3. Сортировка вставкой со сдвигом Пример, упорядочить массив по возрастанию: 12 3 19 8 2 1 15 1) 3 12 19 8 2 1 15 2) 12 19 8 2 1 3 15 3) 19 8 2 1 3 12 15 4) 8 2 1 3 12 15 19 5) 2 1 8 12 15 19 3 6) 1 2 3 8 12 15 19 --------------------------------------n-1раз! 9 4. Алгоритм сортировки вставкой со сдвигом i = 1 , n-1 , 1 R = a1 , kk=0 j = 2 , n-1 , 1 + aj < R< aj+1 k = j, kk=1 t = 1 , k-1 , 1 aj = aj+1 ak = R + kk = 0 & R > an k=n t = 1 , k-1 , 1 aj = aj+1 ak = R 10 11 12 for i:e 2 ю .1 do переместить А/i/ на позицию j £ i такую, что А/i/ < Л / А / для j & к < I и либо A|i| £ A/i - 1], либо j - 1 Чтобы сделать процесс перемещения элементе >ф] более простым, полезно ввести элемент Л|<)|. чье значение ключа будет меньше значения ключа любого х*емента AJ11 Д[л]. Мы можем постулировать существование константы - ©о типа keyiype. которая будет меньше значения ключа любой записи, встречающейся на практике. Если такую коне гангу нельзя применить, то при вставке/!/// в позицию Г I надо проверить, не будет ли - 1. если нет. тогда сравнивать элемент A[i] (который сейчас находится в позиции J) с элементом А[) - 1\ Описанный алгоритм показан в листинге Й.З. Листинг 8.3. Сортировка вставками Ш AfO] .Агву:» - < 2)for i: = 2 to П do beain \3) у.- i; (4)while Aljl < A{j - 1] do begin (G> swapiAlj], Alj - 1]); (в) j:a J • i end end Пример В табл. 8.3 показан начальный список из табл. S.I и последова- тельные этапы алгоритма вставкой для i - 2. 3 13 6. После каждого лапа алгоритма элементы, расположенные мыше линии, уже упорядочены, хотя между ними на последующих этапах мот бы!ь вставлены элементы, которые сейчас находятся ниже линии. D . *,4*1 • *.• к .;•> Таблица 8.3. Этапы сортировки вставками - во — — — •• — П и.1 II П Кр Аг Аг Д|> ОЙ т* •• ш Этий Э Пи Кр Kp Ве» Крйкйпу или Эт уш Пи уш IIU иг К|Ы *катиу Агумг чмл Ar ли Аг Акй1А> ЭТ ukuiayСи > *и иПил с» Блева С и Сk ли С* JIM Э¥ ЬН1иу Сш. ВлМ)*и yiif Н уш Вс ИЛ Ве . Елена Вс и Эти и. . tлеи» . Елемн ИЙ Елены1=6 И Ийчилки е 1> 1 |унии1= яний * = ауиий/ = JCHU м положение E »«ни 2 з 4 5 s Х.2. ПРОСТЫЕ СХЕМЫ СОИ И РОВ К И Идея сортировки посредством выбора также элементарна, как и го два метода сортировки, которые мы >же рассмотрели. На i-м этапе сортировки выбирается запись с наименьшим ключом среди записей уф], меняется местами с зали- сью /[1]. В результате после |-го этапа все таниси А[1], бул>т упорядочены. Сортировку посредством выбора можно описать следующих! образом: for 1:= 1 to л - 1 do выбрать среди A. i), А|л| элемент с наименьшим ключом и поменять ето местами с А(Ц; Более полный код, реализующий этот метод сортировки, приведен в листинге S.A. Листинг 8.4. С ортмровка посредством нмбора >аг lowkey: keytype; ' гекуциЯ каикскмшй кпоч, ил^дсшшЙ при 14 проходе по элементам А {i j . •. ., Л[л] ) lowindex: integer; 1 папицкя :>лсконтл с кюсгххл lowkey } begin (1| far i: = 1 to л - 1 do begin (21 lowindexm i; (3) lovArey:* A[i).koy; (4) for j:w i * 1 to л do { сравнение клочвЙ с токушо* ключом lowkey ) if A[j).k*y < lowkey tbrn begin (5) (by lowkey:e A[ji .key; (T| lovi/idexr* / end; (Si sv<jp(A(l), Allovindex] I end end; Пример S.3. В габл. 8.4 показаны этапы сортировки посредством выбора для списка из табл. 8.1. Например, на 1-м лапе значение hwindex равно 4, т.е. позиции Агунга, который меняется с Пили, элементом All], Линтти в табл. 8.4 показывают, что элементы, расположенные выше ее. имеют наименьшие значения ключей и уже упорядочены. После (и - 1)*го этапа элемент уЦл] также стоит на "правильном" месте, так как выше ето все записи имеют меньшие значения ключей. D Таблица 8.4. Сортировка посредством выбора Пили А Ат Аг Ат А Этт Э Ве Ве Ве В Краката гунг К уит Кр унг Кр унт Кр гуит К Агунг тна П зувийПи дувийПи зувийПи езувий П Св. С акатауСв. акатауСв. акатауСв. ракатау С у ракатау Везувий или В ли Эт ли Эт ли Эт или Э ЕлетиНачальн в. 1- Едена2-й Елена3- Елена4-й в. 5ез>1м« на на на тна Ь*еиа Елена ое положение й этап этап Л этап этап й этап й Временная сложность методов сортировки Методы "пузырька", вставками и посредством выбора имеют временную сложное п. 0(л2) и А(л2) на последовательностях из и элементов. Рассмотрим метод "пузырька* (листинг 8.1». Независимо от того, что подразумевается иод типом recordtype, ВЫП0ЛН6- ние процедуры swap требует фиксированною времени. Полому строки (3). (4) затрачивают Ciединиц времени: с\ — некоторая константа. Следовательно, для фиксированного значения i цикл строк (2) - (4) требует ие больше сЖп - О шагов; а ~ константа. Последний константа несколько больше константы с\9 L'C ,M учитывать операции с индексом у в строке (2). Полому вся программа требует шагов, тле слагаемое с3п учитывает операции с индексом i « строке (Ц Последнее выражение не превосходит (сг/2 + <*з)л* для п > I. поэтому алгоритм "пузырька" имеет временную сложность 0<п*). Нижняя временная гранима для алгоритма равна £}(л2). поскольку если даже не выполнять процедуру swap (например, если список уже отсортирован), ти все равно л(л — 1 )/2 раз выполняется проверка в строке <3). Далее рассмотрим сортировку вставками (листиг 8.3). Цикл while в строках (4> * (6) выполняется не более 0(0 раз. поскольку начальное S.2. ШЮСТЫЕ СХЕМЫ СОРТИРОВКИ значение у равно /. а затем / уменьшается на 1 при каждом выполнении лото цикла. Следовательно, цикл for строк <2> - <6> потребует не более шагов для некоторой константы с. Эта сум ма имеет порядок 0(л2). Читатель может проверить, что если список записей первоначально был отсортирован в обратном порядке, то пикл while в строках (41 - (6) выполняется ровно i - 1 раз. поэтому строка (4) выполняется Х ,2(* ~1)= "Г*1)/2 раз. Следовательно, сортировка вставками в самом худшем случае требует времени не менее 0(л2). Можно показать, что нижняя транипа в среднем будет такой же. Наконец, рассмотрим сортировку посредством выбора, показанную и листинге К.4. Легко проверить, что внутренний никл в строках (4) - (?) требует времени порядка 0(л - i>. поскольку j здесь изменяется от i *** I до и. Поэтому обшее время выполнения алгоритма составляет (п -») для некоторой константы с. Эта сумма, равная сл(л 1)/2, имеет порядок роста <XnJ). С другой стороны, нетрудно показать, что строка (4) выполняется не менее Y*У (1) = л(л-1)/2 реже зависимо от на•^1*1 мг*1*Т чального списка сортируемых элементов. Поэтому сортировка посредством выбора требует времени ие менее ft(n2) в худшем случае и в среднем. Подсчет перес тановок Если размер записей большой, тогда процедура swap хтя перестановки записей, которая присутствует во всех трех вышеописанных алгоритмах, занимает больше времени, чем все другие операции, выполняемые в программе (например, такие как сравнение ключей или вычисление индексов массива). Поэтому, хотя мы уже показали. что время работы всех трех алгоритмов пропорционально л2, необходимо более детально сравнить их с учетом использования процедуры swap. Сначала рассмотрим алгоритм *пу'зырька*. В лом алгоритме процедура лиар выполняется (см. листинг 8.1, строка (4)) не менее .(!£ л(я - 1)/2раз, т.е. почти Л2/2 раз. Но поскольку строка (4) выполняется после условного оператора в строке (3). то можно ожидать, что число перестановок значительно меньше, чем л2/2. В самом деле, в среднем перестановки выполняются только в половине из всех возможных случаев. Следовательно, ожидаемое число перестановок, если все воз- можиые исходные последовательности ключей равновероятны, составляет примерна л*/4. Для доказательства этого утверждения рассмотрим два списки ключей, которые обрати друг другу: -■ jkle А2, кл н L2 - кщ. п._« fej. Переставляются ключи и kf hi одного списка, если они расположены в "неправильном" порядке, т.е. нарушают отношение линейного порядка, сданною на множестве значений ключей. Такая перестановка для ключей kt и к) выполняется только один раз, так как они расположены неправильно или только н списке Llf или юлько в списке La, но не в обоих сразу. Поэтому общее число перестановок в алгоритме "пузырька-, примененномS.2.кШЮСТЫЕ спискам L\ и L2, равно числу пар элементов, i .e. числу сочетаний СХЕМЫ СОРТИРОВКИ из п по 2: С* - п(п -1)/2. Следовательно, среднее число перестановок для списков L\и L% равно л(л - \)/Л или примерно л1/4- Поскольку для любою упорядочивания ключей существует обратное к нему, как в случае списков L\ и го отсюда вытекает, чго среднее число перестановок для любою списка также равно примерно л2/4. Число перестановок для сортировки вставками в среднем точно такое же. как и для алгоритма "пузырька*. Для доказательства этого достаточно применить тот же самый аргумент: каждая пара элементов подвергается перестановке или в самом упорядочиваемом списке /. и в обратном к нему, но никогда в обоих. Если на выполнение процедуры swap необходимы большие вычислительные или временные ресурсы, то легко увидеть, что в этом случае сортировка посредством выбора более предпочтительна, нежели другие рассмотренные методы сортировки. В самом деле. в алгоритме этого метода (листинг 8.4> процедура swap выполняется вне внутреннего цикла, поэтому она выполняется точно п - I раз для массива длиной п. Другими словами, в этом алгоритме осуществляется 0(л) перестановок в отличие от двух других алгоритмов, тле количество таких перестановок имеет порядок 0(л2). Причина этого понятна: в метоле сортировки посредством выбор:» элементы "перескакивают" через большой ряд других элементов, вместо того чтобы последовательно меняться с ними местами, как это делается в алгоритме "пузырька* и алгоритме сортировки вставками. В общем случае, если необходимо упорядочить длинные записи (поэтому их перестановки занимают много времени), целесообразно работать не с массивом записей, а с массивом указателей на записи, 1Ю1ИЯ отдельных фупкиии И оиерлгороп !ф01р£ЧШ4. Прим. ред. используя дли сортировки любой подходящий алгоритм. В этом случае переставляются не сами записи, а только указатели на них. После упорядочивания указателей сами записи можно расположить в нужном порядке та время порядка 0(л). Ограниченность простых схем сортировки Еще раз напомним, что каждый и* рассмотренных в этом разделе алгоритмов выполняется ia время порядка 0(л2) как в среднем, гак и в самом худшем случае. Полому для больших и эти алгоритмы заведомо проигрывают алгоритмам с временем вы- полнения <Хл logn), которые будут описаны в следующем разделе. Значение л, начиная с которого быстрое алгоритмы сортировки становятся прелночттедьнее простых методов сортировки, зависит oi различных факторов, таких как качество объектного кода программы, генерируемого компилятором, компьютера, на кото|>ом выполняется про- грамма сортировки, или от размера записей. Определить такое критическое значение п для конкретных задач и вычислительного окружения (компилятор, компьютер и т.п.) можно после экспериментов с профайлером2 и на основании выдаваемых им результатов. Практический опыт y*nir. что при га. не превышающем 100. на время выполнения программы влияет множество факторов, которые с трудом поддаются регистрации и поэтому не учтены в анализе, проведенном в этом ратделе. Для небольших значений п рекомендуем применять простой в реализации алгоритм сортировки Шелла (Shell), который имеет временную сложность 0(п1Л). Этот алгоритм, описанный в упражнении 8.3. S.2. ШЮСТЫЕ СХЕМЫ СОРТИРОВКИ является обобщением алгоритма "пузырька". 2 Профайлер это подпрограмма протоколирования, помюлиишдн оценить иремя импал-