Вставкой
Пузырьком
Шейкер
Выбором
Подсчетом
Хоара — быстрая
Все кроме последней элементарно объясняются в википедии и в интернете вообще, а последняя через попу. Ща попытаюсь объяснить понятно. Скажете, что неясно.
Краткое описание
Суть этой сортировки заключается в том, что после каждого прохода все элементы разделяются на две группы: слева находятся меньшие, чем какой-либо элемент, а справа — большие. После каждого прохода берется каждая из двух частей и сортируется тем же способом, то есть задача сводится к предыдущей. Так происходит пока все подчасти не будут состоять из одного элемента. Таким образом, количество элементов в сортируемой части уменьшается при каждом следующем вызове функции из себя же.
Еще раз: после первого прохода список разделяется на две части. Затем, берется первая, и для нее вызывается та же сортировка, в результате которой этот подсписок (первая часть) тоже разбивается на 2, и т. д., пока в принимаемом списке не останется один элемент. То же повторяется и для второй части. Следовательно, удобно использовать рекурсию для написании этой сортировки.
Подробное описание. Пример
Для того, чтобы разбить список на 2 части относительного некоторого элемента, нужно выбрать этот элемент. Можно выбирать случайно, то есть любой. Я рассмотрю случай, когда элемент выбирается как серединный: (левая граница списка + правая граница) разделить на 2 = (l + r) / 2 = (0 + 7) / 2 = 3.
22 7 10 0 6 6 1
0 1 2 3 4 5 6
0 1 2 3 4 5 6
l r
Серединный — a[3] = 0.
Итак, находим значение серединного элемента, затем, чтобы он не мешался, обмениваем его с нулевым.
0 7 10 22 6 6 1
Далее находим границы списка, который мы будем анализировать. Левая граница (назовем ее l) будет находиться на первой позиции (если считать с нуля), так как на нулевой лежит избранный элемент. А правая (назовем ее r) будет находиться на конце списка и будет равна его размерности. Итак, диапазон анализируемых значений будет [l ; r), не включая правую границу.
0 7 10 22 6 6 1
l r
Давайте анализировать. Элемент а[l] > 0, значит, меняем его с a[r - 1]. И так как справа у нас находятся большие элементы, то правая граница анализируемой части подвигается влево на один.
0 1 10 22 6 6 7
l r
Далее, a[l] > 0, обмениваем его с a[r - 1]. Сдвигаем правую границу.
0 6 10 22 6 1 7
l r
И так далее, пока r и l не станут равными: r = l.
0 6 10 22 6 1 7
l r
0 22 10 6 6 1 7
l r
0 10 22 6 6 1 7
l r
0 10 22 6 6 1 7
l r
Далее необходимо базовый элемент положить как раз на границу двух списков: чтобы справа от него были те, что меньше него, а слева — те, что больше него. Очевидно эта граница находится на позиции l - 1.
Поэтому в данном случае меньше нуля элементов нет и первый подсписок состоит из нуля элементов, а следовательно он отсортирован и сортировку нужно производить с оставшимся подсписком
10 22 6 6 1 7
Выбираем базовый элемент — a[3]. Срединный — «6», меняем его с нулевым.
10 22 6 6 1 7
10 22 6 6 1 7
6 22 6 10 1 7
Расставляем границы.
6 22 6 10 1 7
l r
Сравниваем поочередно элементы a[l] с базовым «6». a[l] > 6, поэтому
6 7 6 10 1 22
l r
a[l] снова больше чем 6.
6 1 6 10 7 22
l r
А вот сейчас a[l] < 6, поэтому мы не производим обмен, а сдвигаем левую границу вправо на один элемент.
6 1 6 10 7 22
l r
6 <= 6, левая граница подвигается
6 1 6 10 7 22
lr
Сейчас r = l, вставляем базовый элемент в l - 1
6 1 6 10 7 22
lr
Получилось, что мы разбили подсписок на 2 части (с нулевого по l-ый — третий и с l-ого по r-ый — с третьего по пятый, не включая правые границы, как всегда):
6 1 и 10 7 22
№ 0 1 2 3 4
№ 0 1 2 3 4
Рассмотрим первую часть:
6 1
Выберем базовый элемент (0 + 2)/2 = 1, переместим на место первого, ставим границы
1 6
l r
Проводим те же операции, в результате получим
1 6
lr
и разделим на два подсписка: пустой и «6», которые уже отсортированы.
Далее, обработаем вторую часть исходного списка:
10 7 22
7 10 22
l r
7 22 10
l r
7 10 22
lr
Делим на два списка:
пустой и «10 22»
Обработаем второй (0 + 2) / 2 = 1:
10 22
22 10
l r
22 10
lr
Возвращаем базовый элемент на нужное место (l - 1):
10 22
lr
Разбиваем на два списка: «10» и пустой, которые уже отсортированы. Соединим все полученные подсписки в один
0 1 6 6 7 10 22.
Сложность
Сложность данного алгоритма в худшем случае (когда после разделения на элементы, большие базового и меньшие базового, получается два списка: пустой и из оставшихся элементов) O(n^2), что не отличает данную сортировку от всех вышеперечисленных «небыстрых». Но в среднем случае, когда базовый элемент разбивает при каждом проходе список примерно на два подсписка равной длины, мы получаем сложность n * log_2(n). Почему:
За один проход мы перебираем n элементов — n операций. Но каждый следующий проход делит список на два (при этом количество перебираемых (сравниваемых) элементов не уменьшается, так как мы сортируем обе половины), в итоге количество проходов равно двоичному логарифму от n — log_2(n).
Для тех, кто не помнит, что такое логарифм: сколько раз нужно умножить 2 само на себя, чтобы получилось n? log_2(n). То есть количество делений нашего списка на два, пока не останется список из одного (или нуля) элементов.
За один проход мы перебираем n элементов — n операций. Но каждый следующий проход делит список на два (при этом количество перебираемых (сравниваемых) элементов не уменьшается, так как мы сортируем обе половины), в итоге количество проходов равно двоичному логарифму от n — log_2(n).
Для тех, кто не помнит, что такое логарифм: сколько раз нужно умножить 2 само на себя, чтобы получилось n? log_2(n). То есть количество делений нашего списка на два, пока не останется список из одного (или нуля) элементов.
Доброго вечера, Елена! Если не возражаете у меня есть пара вопросов по Вашему объяснению сортировки Хоара.
ОтветитьУдалить1. Почему в этих 2х строчках:
0 10 22 6 6 1 7
l r
0 22 10 6 6 1 7
l r
Вы 22 и 10 меняете местами ведь 0 < 10 и 10 мы меняем с числом на позиции (r-1), т.е. получается с самим собой?
2. Для выбора базового элемента на одном из этапов Вы используете выражение (0 + 2)/2 = 1. Почему его и откуда Вы получили эти слагаемые?
Буду признательна за ответы.
С уважением, Марина.
1. Спасибо, это моя ошибка. Сейчас исправлю. Все правильно, 10 остается на месте (это значение меняется с самим собой местами).
ОтветитьУдалить2. Это выражение считает номер базового элемента. Здесь я беру элемент посередине. Имеется в виду номер начального элемента + количество элементов разделить на два.
Спасибо за замечания, еще одну опечатку сама заметила. Сейчас все уточню и исправлю.
Добавила про сложность ;)
ОтветитьУдалитьмда...
ОтветитьУдалитьИтак, находим значение серединного элемента, затем, чтобы он не мешался, обмениваем его с нулевым.
из каких соображений?)
Чтобы не мешался, написано ж.
УдалитьДалее необходимо базовый элемент положить как раз на границу двух списков: чтобы справа от него были те, что меньше него, а слева — те, что больше него.
ОтветитьУдалитьА не наоборот право-лево?
Очень тяжело читать, когда не знаешь, опечатка это или ты туговат... :)
ОтветитьУдалить