Синдром восьмиклассника
- 1 year ago
- 0
- 0
Timsort — гибридный алгоритм сортировки . Гибридный потому, что сочетает сортировку вставками и сортировку слиянием . Опубликован в 2002 году Тимом Петерсом . В настоящее время является стандартным алгоритмом сортировки в Python , OpenJDK 7 , Apple Swift Standard Library 5 и реализован в Android JDK 1.5 . Основная идея алгоритма заключается в использовании наблюдения, согласно которому на практике сортируемые (упорядочиваемые) массивы данных часто содержат отсортированные (упорядоченные) подмассивы. На таких данных алгоритм timsort сравнительно быстрее некоторых алгоритмов сортировки .
Работу алгоритма можно разделить на следующие шаги:
Особенности алгоритма заключаются в особенностях алгоритма деления массива на подмассивы и в особенностях изменённого алгоритма сортировки слиянием.
N — размер входного массива .
run — отсортированный (упорядоченный) подмассив входного массива. — i-й элемент упорядоченного подмассива. Упорядочение возможно двумя способами:
minrun — минимальный размер подмассива входного массива, минимальный размер упорядоченной последовательности. Рассчитывается по определённой логике из числа N на первом шаге алгоритма и используется на втором шаге алгоритма при делении входного массива на подмассивы.
Число minrun (минимальный размер упорядоченного подмассива) определяется на основе числа N, исходя из следующих принципов.
(1) Подмассив размера minrun будет сортироваться с использованием алгоритма сортировки вставками . Алгоритм сортировки вставками эффективен только для сравнительно небольших массивов. То есть, число minrun ограничено сверху, должно быть таким, чтобы алгоритм сортировки вставками был эффективным.
(2) Два подмассива размера minrun будут объединяться с использованием алгоритма сортировки слиянием . Для алгоритма сортировки слиянием верно, что, чем меньше сортируемый массив (то есть, чем меньше число minrun), тем больше итераций слияния подмассивов придётся выполнить на последнем шаге алгоритма. То есть, число minrun ограничено снизу, должно быть таким, чтобы алгоритм сортировки слиянием был эффективным. Алгоритм сортировки слиянием подмассивов эффективнее работает с подмассивами примерно равного размера. Поэтому оптимальная величина для N / minrun равна либо степени числа 2, либо близка к ней.
Эксперименты, проведённые автором алгоритма timsort, показали следующее:
В данный момент алгоритм расчёта minrun следующий:
Реализуем на языке Java метод «getMinrun()» для расчёта числа minrun.
public static int getMinrun(int n) {
// n = N, размер входного массива.
int r = 0;
// 2^6 = 64.
while (n >= 64) {
r |= (n & 1);
// Если среди младших битов n имеется хотя бы один ненулевой бит, переменная r станет равна 1.
n >>= 1;
}
// Теперь переменная n содержит старшие 6 бит N.
return n + r; // minrun
}
Для реализации шагов 2 и 3 выполняются следующие действия.
Если входной массив содержал случайные данные, размер упорядоченных подмассивов будет близок к minrun. Если входной масив содержал упорядоченные подмассивы, упорядоченные подмассивы будут иметь размер, больший minrun. Чтобы получить упорядоченный массив (результирующий массив), требуется объединить упомядоченные подмассивы. Чтобы объединение выполнялось эффективно, должно быть верно следующее:
Алгоритм:
Z > Y + X; Y > X;
Цель этой процедуры — сохранение баланса. Изменения будут выглядеть так, как показано на картинке. А значит размеры хранимых в стеке подмассивов таковы, что алгоритм сортировки слиянием будет эффективен. В идеальном случае размеры хранимых в стеке подмассивов будут равны 128, 64, 32, 16, 8, 4, 2, 2 соответственно. Тогда объединения подмассивов не будут выполняться до тех пор, пока не встретятся два последних подмассива (подмассив размером 2 и подмассив размером 2); после чего будут выполнены 7 идеально сбалансированных объединений:
От расположения подмассивов зависит то, как процедура объединения (соединения, слияния) будет выполнять перебор (обход) и сравнение элементов подмассива. Для реализации алгоритма необходимы следующие процедуры:
На практике обычно реализуют процедуру перебора элементов массива слева направо и при выборе подмассива на роль левого массива не учитывают размер подмассива, так как это почти не влияет на время сортировки.
Перечислим шаги алгоритма.
Пусть
A = {1, 2, ..., 10_000}, B = {20_000, 20_001, ..., 30_000}.
Рассмотрим объединение массивов A и B. Массив A содержит 10_000 элементов. Массив B содержит 10_001 элемент. На четвёртом шаге выполняется одно сравнение (элементов двух массивов) и одно копирование. 4-й шаг будет выполнен 10_000 раз. То есть, будет выполнено 10_000 сравнений и 10_000 копирований. Алгоритм timsort предлагает изменение, называемое словом «галоп». Опишем шаги алгоритма.
Рассмотрим работу алгоритма c переходом в режима «галопа» на примере объединения массивов A и B. Первые 7 итераций сравниваются элементы 1, 2, 3, 4, 5, 6 и 7 массива A и элемент 20_000 массива B. Так как элемент 20_000 массива B больше первых семи элементов массива A, элементы массива A копируются в результирующий массив. В данном примере считается, что для перехода в режим «галопа» требуется подряд взять из одного из массивов 7 элементов. Так как из массива A было взято 7 элементов подряд, начиная со следующей итерации, алгоритм переходит в режим «галопа». Элемент 20_000 массива B последовательно сравнивается с элементами 8, 10, 14, 22, 38, n+2^i, …, 10_000 массива A. Всего выполняется примерно log_2(N) сравнений. После того, как будет достигнут конец массива A, станет известно, что любые элементы массива A меньше любых элементов массива B. Нужные элементы из массива A копируются в результирующий массив.
Реализация на языке C++.
const int RUN = 32;
void insertionSort(int arr[], int left, int right)
{
for (int i = left + 1; i <= right; i++) {
int temp = arr[i];
int j = i - 1;
while (j >= left && arr[j] > temp) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = temp;
}
}
void merge(int arr[], int l, int m, int r)
{
int len1 = m - l + 1, len2 = r - m;
int left[len1], right[len2];
for (int i = 0; i < len1; i++)
left[i] = arr[l + i];
for (int i = 0; i < len2; i++)
right[i] = arr[m + 1 + i];
int i = 0;
int j = 0;
int k = l;
while (i < len1 && j < len2) {
if (left[i] <= right[j]) {
arr[k] = left[i];
i++;
}
else {
arr[k] = right[j];
j++;
}
k++;
}
while (i < len1) {
arr[k] = left[i];
k++;
i++;
}
while (j < len2) {
arr[k] = right[j];
k++;
j++;
}
}
void timSort(int arr[], int n)
{
for (int i = 0; i < n; i += RUN)
insertionSort(arr, i, std::min((i + RUN - 1), (n - 1)));
for (int size = RUN; size < n; size = 2 * size)
{
for (int left = 0; left < n; left += 2 * size)
{
int mid = left + size - 1;
int right = std::min((left + 2 * size - 1), (n - 1));
if (mid < right)
merge(arr, left, mid, right);
}
}
}
Для улучшения этой статьи
желательно
:
|