Pull to refresh
0
Edison
Изобретаем успех: софт и стартапы

Сбалансированное слияние сверху-вниз и снизу-вверх

Reading time4 min
Views7.8K

В прошлой статье мы ознакомились с реликтовыми сортировками слияния (вызывающих прежде всего исторический интерес). А что в тренде сегодня?

Для ознакомления с концепцией упорядочивания через слияния обычно используются алгоритмы сбалансированного слияния. Термин «сбалансированность» означает, что алгоритм рекурсивно разбивает массив и его подмассивы на примерно равные части. Сегодня мы рассмотрим как это выглядит на практике.

Пара функций одинакова для обоих методов. Да и вообще, что «вниз-вверх», что «вверх-вниз» — это практически один и тот же алгоритм, просто показанный с разных ракурсов.

Нам, понадобится, собственно, слияние двух половинок отрезка в один подмассив. Половинки одновременно перебираются в одном массиве, сравниваются текущие элементы в обоих переборах и меньший элемент уходит во второй массив:

//Левая половина источника A[iBegin: iMiddle - 1]
//Правая половина источника A[iMiddle: iEnd - 1]
//Результат: слияние в B[iBegin: iEnd - 1]
void Merge(A[], iBegin, iMiddle, iEnd, B[]) {
  i = iBegin, j = iMiddle;
  //Пока есть элементы в левом или правом прогоне...
  for (k = iBegin; k < iEnd; k++) {
  //Если левый указатель ещё существует 
  //и он <= существующего правого указателя
    if (i < iMiddle && (j >= iEnd || A[i] <= A[j])) {
      B[k] = A[i];
      i++;
    } else {
      B[k] = A[j];
      j++;
    }
  }
}

Копирование отрезка из одного массива в другой. Обе реализации оперируют двумя массивами, данные приходится постоянно гонять из основного во вспомогательный и обратно:

//Копируем из основного массива A[]
//во вспомогательный массив B[]
void CopyArray(A[], iBegin, iEnd, B[]) {
    for(k = iBegin; k < iEnd; k++) B[k] = A[k];
}

Нисходящее сбалансированное слияние




Сначала берётся весь массив целиком, после чего начинается рекурсивный спуск. Массив дихотомируется до тех пор, пока не дойдём до подмассивов из одного элемента (которые являются отсортированными сами по себе). Затем рекурсия начинает обратный подъём, производя по пути слияние подмассивов (размер которых на каждом уровне удваивается).

//Основной массив A[] содержит элементы для сортировки
//Массив B[] является вспомогательным
void TopDownMergeSort(A[], B[], n) {
  CopyArray(A, 0, n, B); //Дублируем A[] в B[]
  TopDownSplitMerge(B, 0, n, A);//Сортируем B[] и возвращаем в A[]
}

//Сортировка заданного прогона для A[], используя B[] в качестве источника
//Границы диапазона: iBegin включительно; iEnd не включая
void TopDownSplitMerge(B[], iBegin, iEnd, A[]) {
  //Если size == 1, то такой подмассив отсортирован
  if(iEnd - iBegin < 2)  return;
  //Если size > 1, такие массивы делим пополам
  iMiddle = (iEnd + iBegin) / 2;//iMiddle - середина отрезка
  //Рекурсивно сортируем обе половины из A[] в B[]
  TopDownSplitMerge(A, iBegin,  iMiddle, B);//Сортируем левую половину
  TopDownSplitMerge(A, iMiddle,    iEnd, B);//Сортируем правую половину
  //Объединяем полученные прогоны из B[] в A[]
  Merge(B, iBegin, iMiddle, iEnd, A);
}


Восходящее сбалансированное слияние




Здесь происходит итерация по массиву, по пути сначала берём соседние минимальные массивы (из одного элемента) и попарно производим их слияние. Получая на каждом шаге удвоенные отсортированные подмассивы, снова производим слияние соседей и так продолжаем до тех пор, пока на выходе не получим весь массив, уже в отсортированном виде.

//Основной массив A[] содержит элементы для сортировки
//Массив B[] является вспомогательным
void BottomUpMergeSort(A[], B[], n) {
  //Каждый подмассив из одного элемента в A[] уже «отсортирован».
  //Последовательные отсортированные прогоны удваивающейся длины:
  //× 2, 4, 8, 16, ... - до тех пор, пока не будет отсортирован весь массив
  for (width = 1; width < n; width = 2 * width) {
    //Массив А заполнен прогонами длиной width
    for (i = 0; i < n; i = i + 2 * width) {
      // Объединение двух прогонов: 
      //A [i: i + width - 1] и A [i + width: i + 2 * width - 1] в B[]
      //или копирование A[i: n - 1] в B[] (если i + width > = n)
      Merge(A, i, min(i + width, n), min(i + 2 * width, n), B);
    }
    //Теперь вспомогательный массив B[] заполнен прогонами длиной 2 * width
    //Копируем массив B[] в массив A[] для следующей итерации
    //Более эффективная реализация меняет роли A[] и B[]
    CopyArray(B, 0, n, A); //Возвращаем B[] в A[]
    //Теперь массив А заполнен прогонами длиной 2 * width
  }
}

В целом предпочтительнее нисходящая реализация, так как в ней эффективнее используются два массива, которые просто постоянно меняются ролями «основной/вспомогательный». В восходящем варианте массив A всегда основной, а массив B всегда вспомогательный. В результате, после каждой итерации данные из B нужно в полном составе возвращать в A, что не способствует улучшению алгоритмической сложности. С другой стороны, реализация восходящей проще, в ней даже нет рекурсии.

Несбалансированное слияние


От самого слова «сбалансированность» веет какой-то надёжностью, устойчивостью. Может даже создаться впечатление, что хороший алгоритм должен быть непременно сбалансированным. А «несбалансированность» ассоциируется с какой-то расшатанностью и перекосами. Ну действительно, разве сбалансированное не должно быть во всех отношениях лучше чем несбалансированное?

На самом деле, бывает что и хуже. Само собой, разделение подмассивов на равные половины (что и подразумеваются под сбалансированностью для сортировок слиянием) гораздо проще реализовать. Дели себе массив напополам и к каждой половине применяй рекурсию. Собственно, в этой лёгкости и заключается основное достоинство сбалансированного слияния перед несбалансированным.

В следующих публикациях мы ознакомимся с несбалансированными способами. Они заметно сложнее в понимании и реализации. Данные для последующего слияния будут распределяться по вспомогательным массивам не равномерно да поровну, а в соответствии с рядом обобщённых чисел Фибоначчи. И это позволит достигать мощных результатов, которые недостижимы для упрощённых сбалансированных методов.

Ссылки


Merge (Google-translate), Слияние

Статьи серии:



Очередные слиятельные сортировки теперь доступны в приложении AlgoLab (кто изучает алгоритмы с помощью этого Excel-приложения — обновите файл).

Временно на массивы поставлено ограничение — их размер должен быть степенью двойки (связано с некоторыми сложностями, возникшими при программировании визуализации). Чуть позже можно будет сортировать любые массивы.

EDISON Software - web-development
Статья написана при поддержке компании EDISON Software, которая с помощью облачных сервисов создаёт встроенное программное обеспечение и разрабатывает мобильные приложения на JAVA.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 15: ↑15 and ↓0+15
Comments0

Articles

Information

Website
www.edsd.ru
Registered
Founded
Employees
31–50 employees
Location
Россия