Цель: ознакомиться с различными сортировками массивов и оценить их сложность. Студенты должны: Знать: что такое сортировка и методы сортировки. Уметь: Писать простейшие алгоритмы на языке C++ или C#. 1 Краткая теория 1.1 Сортировки массивов Алгоритмы сортировок очень широко применяются в программировании, но иногда приходиться оценивать алгоритмы и выбирать какой алгоритм работает лучше всех (под понятием «лучше всех» имеется ввиду сочетание быстродействия и сложности как написания, так и выполнения). Selection sort (сортировка выбором) – суть алгоритма заключается в проходе по массиву от начала до конца в поиске минимального элемента массива и перемещении его в начало. Bubble sort (сортировка пузырьком) – данный алгоритм меняет местами два соседних элемента, если первый элемент массива больше второго. Так происходит до тех пор, пока алгоритм не обменяет местами все неотсортированные элементы. Insertion sort (сортировка вставками) – алгоритм сортирует массив по мере прохождения по его элементам. На каждой итерации берется элемент и сравнивается с каждым элементом в уже отсортированной части массива, таким образом находя «свое место», после чего элемент вставляется на свою позицию. Так происходит до тех пор, пока алгоритм не пройдет по всему массиву. На выходе получим отсортированный массив. Quick sort (быстрая сортировка) – суть алгоритма заключается в разделении массива на два под-массива, средней линией считается элемент, который находится в самом центре массива. В ходе работы алгоритма элементы, меньшие чем средний будут перемещены в лево, а большие в право. Такое же действие будет происходить рекурсивно и с под-массива, они будут разделяться на еще два под-массива до тех пор, пока не будет чего разделать (останется один элемент). На выходе получим отсортированный массив. Merge sort (сортировка слиянием) – Основная идея состоит в том, что на каждом шагу мы разбиваем массив на 2 равные части, сортируем их, а потом сливаем два отсортированных куска. То есть получается рекурсивная сортировка, т.к. каждую из этих 2 частей мы будем сортировать аналогично. Выход из рекурсии будет происходить тогда, когда у нас остается меньше 3 элементов. Если их остается всего 2, то меняем их между собой по мере надобности. Если остается только 1 элемент, то оставляем его в покое. Shaker sort (шейкерная сортировка) Также известна как сортировка перемешиванием и коктейльная сортировка. Заметим, что сортировка пузырьком работает медленно на тестах, в которых маленькие элементы стоят в конце (их еще называют «черепахами»). Такой элемент на каждом шаге алгоритма будет сдвигаться всего на одну позицию влево. Поэтому будем идти не только слева направо, но и справа налево. Будем поддерживать два указателя begin и end, обозначающих, какой отрезок массива еще не отсортирован. На очередной итерации при достижении end вычитаем из него единицу и движемся справа налево, аналогично, при достижении begin прибавляем единицу и двигаемся слева направо. 1.2 Оценка сложности алгоритма Практически всегда существует несколько способов решения той или иной задачи: одни предполагают затратить много времени, другие ресурсов, а третьи помогают лишь приближённо найти решение. Всегда следует искать оптимум в соответствии с поставленной задачей, в частности, при разработке алгоритмов решения класса задач. Важно также оценивать, как будет вести себя алгоритм при начальных значениях разного объёма и количества, какие ресурсы ему потребуются и сколько времени уйдёт на вывод конечного результата. Основным показателем сложности алгоритма является время, необходимое для решения задачи и объём требуемой памяти. Также при анализе сложности для класса задач определяется некоторое число, характеризующее некоторый объём данных – размер входа. Итак, можем сделать вывод, что сложность алгоритма – функция размера входа. Сложность алгоритма может быть различной при одном и том же размере входа, но различных входных данных. Существуют понятия сложности в худшем, среднем или лучшем случае. Обычно, оценивают сложность в худшем случае. Временная сложность в худшем случае – функция размера входа, равная максимальному количеству операций, выполненных в ходе работы алгоритма при решении задачи данного размера. Ёмкостная сложность в худшем случае – функция размера входа, равная максимальному количеству ячеек памяти, к которым было обращение при решении задач данного размера. Порядок роста сложности алгоритмов Порядок роста сложности (или аксиоматическая сложность) описывает приблизительное поведение функции сложности алгоритма при большом размере входа. Из этого следует, что при оценке временной сложности нет необходимости рассматривать элементарные операции, достаточно рассматривать шаги алгоритма. Шаг алгоритма – совокупность последовательно-расположенных элементарных операций, время выполнения которых не зависит от размера входа, то есть ограничена сверху некоторой константой. Виды асимптотических оценок O – оценка для худшего случая Рассмотрим сложность f(n) > 0, функцию того же порядка g(n) > 0, размер входа n > 0. Если f(n) = O(g(n)) и существуют константы c > 0, n0 > 0, то 0 < f(n) < c*g(n), для n > n0. Функция g(n) в данном случае асимптотически-точная оценка f(n). Если f(n) – функция сложности алгоритма, то порядок сложности определяется как f(n) – O(g(n)). Данное выражение определяет класс функций, которые растут не быстрее, чем g(n) с точностью до константного множителя. Примеры асимптотических функций f(n) g(n) 2n2 + 7n — 3 n2 98n*ln(n) n*ln(n) 5n + 2 n 8 1 Ω – оценка для лучшего случая Определение схоже с определением оценки для худшего случая, однако f(n) = Ω(g(n)), если 0 < c*g(n) < f(n) Ω(g(n)) определяет класс функций, которые растут не медленнее, чем функция g(n) с точностью до константного множителя. Θ – оценка для среднего случая В данном случае функция f(n) при n > n0 всюду находится между c1*g(n) и c2*g(n), где c – константный множитель. Например, при f(n) = n2 + n; g(n) = n2. Критерии оценки сложности алгоритмов Равномерный весовой критерий (РВК) предполагает, что каждый шаг алгоритма выполняется за одну единицу времени, а ячейка памяти за одну единицу объёма (с точностью до константы). Логарифмический весовой критерий (ЛВК) учитывает размер операнда, который обрабатывается той или иной операцией и значения, хранимого в ячейке памяти. Временная сложность при ЛВК определяется значением l(Op), где Op – величина операнда. Ёмкостная сложность при ЛВК определяется значением l(M), где M – величина ячейки памяти. Пример оценки сложности при вычислении факториала Необходимо проанализировать сложность алгоритма вычисление факториала. Для этого напишем на псевдокоде языка С данную задачу: Временная сложность при равномерном весовом критерии Достаточно просто определить, что размер входа данной задачи – n. Количество шагов – (n — 1). Таким образом, временная сложность при РВК равна O(n). Временная сложность при логарифмическом весовом критерии В данном пункте следует выделить операции, которые необходимо оценить. Вопервых, это операции сравнения. Во-вторых, операции изменения переменных (сложение, умножение). Операции присваивания не учитываются, так как предполагается, что она происходят мгновенно. Итак, в данной задаче выделяется три операции: 1) i <= n На i-м шаге получится log(n). Так как шагов (n-1), сложность данной операции составит (n-1)*log(n) . 2) i = i + 1 На i-м шаге получится log(i). Таким образом, получается сумма: 3) result = result * i На i-м шаге получится log((i-1)!). Таким образом, получается сумма: Если сложить все получившиеся значения и отбросить слагаемые, которые заведомо растут медленнее с увеличением n, получим конечное выражение: Ёмкостная сложность при равномерном весовом критерии Необходимо подсчитать количество переменных. Если в задаче используются массивы, за переменную считается каждая ячейка массива. Так как количество переменных не зависит от размера входа, сложность будет равна O(1). Ёмкостная сложность при логарифмическом весовом критерии В данном случае следует учитывать максимальное значение, которое может находиться в ячейке памяти. Если значение не определено (например, при операнде i > 10), то считается, что существует какое-то предельное значение Vmax. В данной задаче существует переменная, значение которой не превосходит n(i), и переменная, значение которой не превышает n! (result). Таким образом, оценка равна O(log(n!)) 2 Контрольные вопросы 1. Объясните алгоритм реализации «пузырьковой сортировки» и оцените его сложность. 2. Объясните алгоритм реализации «сортировки вставкой» и оцените его сложность. 3. Объясните алгоритм реализации «сортировки выбором» и оцените его сложность. 4. Объясните алгоритм реализации «быстрой сортировки» и оцените его сложность. 5. Объясните алгоритм реализации «сортировки слияниме» и оцените его сложность. 6. Объясните алгоритм реализации «шейкерной сортировки» и оцените его сложность. 3 Задание 1. Изучите теоретический материал, представленный в лабораторной работе. 2. Ответьте на теоретические вопросы. 3. Написать программу в соответствии с заданием на языке программирования С++ из пункта 5. 4. Проведите эксперимент, занесите результаты эксперимента в таблицу. 5. Оцените сложность каждого алгоритма сортировки. 6. Сделайте вывод какой алгоритм сортировки лучше использовать в зависимости от объема данных и почему. 7. Оформить отчет по итогам эксперимента и сдать его преподавателю. Каждую сортировку реализовать отдельной функцией. Все функции, используемые в программах, должны иметь независимый интерфейс. Все функции, используемые в проекте должны содержаться в отдельном модуле. 4 Задание 1. Напишите программу, которая выполняет следующие функции: • - генерацию элементов массива с заданной размерностью; • - сортировку массива каждым из 6 способов (пузырьковая сортировка, сортировка вставкой, сортировка выбором, быстрая сортировка, сортировка слиянием, шейкерная сортировка); • - осуществляет подсчет времени сортировки и количества перестановок. 2. Провести эксперимент сортировки массива со следующим количеством элементов: 1000, 10000 и 100000. Для проведения эксперимента необходимо произвести по 5 запусков каждого алгоритма и выбрать наилучшее время. Выбранный наилучший результат занести в таблицу. Сортировку осуществлять с одним и тем же набором данных. Вид 1000 10000 100000 сортировки время кол-во время кол-во время кол-во перестановок перестановок перестановок Bubble sor Selection sort Insertion sort Quick sort Merge sort Shaker sor 3. Оцените сложность каждого алгоритма и сделайте вывод о наилучшем способе сортировки массива по каждой размерности массива. Свой ответ поясните. 4. Оформите отчет который должен содержать: описание алгоритмов сортировки в виде блок-схемы и ее описанием; реализацию этих алгоритмов на языке программирования C++; описание компьютера на котором проводился эксперимент (вид процессора, тактовая частота, количество ядер, количество оперативной памяти и т.д.); результаты эксперимента в виде таблицы; -вывод по каждой размерности массива с указанием лучшей сортировки и пояснениями. 5. Отчет выполнить в текстовом редакторе Word.