Сортировка Слиянием (Merge-Sort)
- Подробности
- Категория: Сортировка и поиск
Сортировка слиянием (англ. merge sort) — алгоритм сортировки, который упорядочивает списки (или другие структуры данных, доступ к элементам которых можно получать только последовательно, например — потоки) в определённом порядке. Эта сортировка — хороший пример использования принципа «разделяй и властвуй». Сначала задача разбивается на несколько подзадач меньшего размера. Затем эти задачи решаются с помощью рекурсивного вызова или непосредственно, если их размер достаточно мал. Наконец, их решения комбинируются, и получается решение исходной задачи.
- массив рекурсивно разбивается пополам, и каждая из половин делиться до тех пор, пока размер очередного подмассива не станет равным единице;
- далее выполняется операция алгоритма, называемая слиянием. Два единичных массива сливаются в общий результирующий массив, при этом из каждого выбирается меньший элемент (сортировка по возрастанию) и записывается в свободную левую ячейку результирующего массива. После чего из двух результирующих массивов собирается третий общий отсортированный массив, и так далее. В случае если один из массивов закончиться, элементы другого дописываются в собираемый массив;
- в конце операции слияния, элементы перезаписываются из результирующего массива в исходный.
Худшее время |
O(n log n) |
---|---|
Лучшее время |
O(n log n) обычно, O(n) на упорядоченном массиве |
Среднее время |
O(n log n) |
Затраты памяти |
O(n) вспомогательных |
Реализация алгоритма на различных языках программирования:
C++
/** * @brief Сортировка элементов от l до r массива buf * @param[in/out] buf - сортируемый массив * @param[in] l - левая граница. При первой итерации l = 0 * @param[in] r - правая граница. При первой итерации r = buf.size() - 1 * * В результате сортируются все элементы массива buf от l до r включительно. */ template<typename Type> void MergeSort(vector<Type>& buf, size_t l, size_t r) { //! Условие выхода из рекурсии if(l >= r) return; size_t m = (l + r) / 2; //! Рекурсивная сортировка полученных массивов MergeSort(buf, l, m); MergeSort(buf, m+1, r); merge(buf, l, r, m); } /** * @brief Слияние элементов. * @param[in/out] buf - массив * @param[in] l - левая граница. При певой итерации l = 0 * @param[in] r - правая граница. При первой итерации r = buf.size() - 1 * @param[in] m - граница подмассивов. * * Массив buf содержит два отсортированных подмассива: * - [l; m] - первый отсортированный подмассив. * - [m+1; r] - второй отсортированный подмассив. * В результате получаем отсортированный массив, полученный из двух подмассивов, * который сохраняется в buf[l; r]. */ template<typename Type> static void merge(vector<Type>& buf, size_t l, size_t r, size_t m) { if (l >= r || m < l || m > r) return; if (r == l + 1 && buf[l] > buf[r]) { swap(buf[l], buf[r]); return; } vector<Type> tmp(&buf[l], &buf[l] + (r + 1)); for (size_t i = l, j = 0, k = m - l + 1; i <= r; ++i) { if (j > m - l) { buf[i] = tmp[k++]; } else if(k > r - l) { buf[i] = tmp[j++]; } else { buf[i] = (tmp[j] < tmp[k]) ? tmp[j++] : tmp[k++]; } } }
Существует также итеративный алгоритм сортировки, избавленный от рекурсивных вызовов. Такой алгоритм называют «Восходящей сортировкой слиянием».
// Слияние двух упорядоченных массивов // m1 - Первый массив // m2 - Второй массив // l1 - Длина первого массива // l2 - Длина второго массива // Возвращает объединённый массив template <class T> T* merge(T *m1, T* m2, int l1, int l2) { T* ret = new T[l1+l2]; int n = 0; // Сливаем массивы, пока один не закончится while (l1 && l2) { if (*m1 < *m2) { ret[n] = *m1; m1++; --l1; } else { ret[n] = *m2; ++m2; --l2; } ++n; } // Если закончился первый массив if (l1 == 0) { for (int i = 0; i < l2; ++i) { ret[n++] = *m2++; } } else { // Если закончился второй массив for (int i = 0; i < l1; ++i) { ret[n++] = *m1++; } } return ret; } // Функция восходящего слияния template <class T> void mergeSort(T * mas, int len) { int n = 1, l, ost; T * mas1; while (n < len) { l = 0; while (l < len) { if (l + n >= len) break; ost = (l + n * 2 > len) ? (len - (l + n)) : n; mas1 = merge(mas + l, mas + l + n, n, ost); for (int i = 0; i < n + ost; ++i) mas[l+i] = mas1[i];//тут нужно что-то поменять, потому что это лишнее копирование, и оно увеличивает время работы алгоритма в два раза delete [] mas1; l += n * 2; } n *= 2; } }
Пример: char a[] = "ASORTINGEXAMPLE"; mergeSort(a, 16); Альтернативная версия алгоритма Сортировки Слиянием.
template <typename Item> void Merge(Item Mas[], int left, int right, int medium) { int j = left; int k = medium + 1; int count = right - left + 1; if (count <= 1) return; Item *TmpMas = new Item[count]; for (int i = 0; i < count; ++i) { if (j <= medium && k <= right) { if (Mas[j] < Mas[k]) TmpMas[i] = Mas[j++]; else TmpMas[i] = Mas[k++]; } else { if (j <= medium) TmpMas[i] = Mas[j++]; else TmpMas[i] = Mas[k++]; } } j = 0; for (int i = left; i <= right; ++i) { Mas[i] = TmpMas[j++]; } delete[] TmpMas; } template <typename Item> void MergeSort(Item a[], int l, int r) { int m; // Условие выхода из рекурсии if(l >= r) return; m = (l + r) / 2; // Рекурсивная сортировка полученных массивов MergeSort(a, l, m); MergeSort(a, m + 1, r); Merge(a, l, r, m); }
C#
static Int32[] Merge_Sort(Int32[] massive) { if (massive.Length == 1) return massive; Int32 mid_point = massive.Length / 2; return Merge(Merge_Sort(massive.Take(mid_point).ToArray()), Merge_Sort(massive.Skip(mid_point).ToArray())); } static Int32[] Merge(Int32[] mass1, Int32[] mass2) { Int32 a = 0, b = 0; Int32[] merged = new int[mass1.Length + mass2.Length]; for (Int32 i = 0; i < mass1.Length + mass2.Length; i++) { if (b < mass2.Length && a < mass1.Length) if (mass1[a] > mass2[b]) merged[i] = mass2[b++]; else //if int go for merged[i] = mass1[a++]; else if (b < mass2.Length) merged[i] = mass2[b++]; else merged[i] = mass1[a++]; } return merged; } static void Main(string[] args) { Int32[] arr = new Int32[100]; //заполняем массив случайными числами Random rd = new Random(); for(Int32 i = 0; i < arr.Length; ++i) { arr[i] = rd.Next(1, 101); } System.Console.WriteLine("The array before sorting:"); foreach (Int32 x in arr) { System.Console.Write(x + " "); } //сортировка arr = Merge_Sort(arr); System.Console.WriteLine("\n\nThe array after sorting:"); foreach (Int32 x in arr) { System.Console.Write(x + " "); } System.Console.WriteLine("\n\nPress the <Enter> key"); System.Console.ReadLine(); }
C#
//предыдущий пример сортирует лишь целые числа. Следующий - работает со всеми типами данных. static IComparable[] Merge_Sort(IComparable[] massive) { if (massive.Length == 1) return massive; int mid_point = massive.Length / 2; return Merge(Merge_Sort(massive.Take(mid_point).ToArray()), Merge_Sort(massive.Skip(mid_point).ToArray())); } static IComparable[] Merge(IComparable[] mass1, IComparable[] mass2) { int a = 0, b = 0; IComparable[] merged = new IComparable[mass1.Length + mass2.Length]; for (int i = 0; i < mass1.Length + mass2.Length; i++) { if (b.CompareTo(mass2.Length) < 0 && a.CompareTo(mass1.Length) < 0) if (mass1[a].CompareTo(mass2[b]) > 0) merged[i] = mass2[b++]; else merged[i] = mass1[a++]; else if (b < mass2.Length) merged[i] = mass2[b++]; else merged[i] += mass1[a++]; } return merged; } static void Main(string[] args) { IComparable[] arr = new IComparable[100]; Random rd = new Random(); for (int i = 0; i < arr.Length; ++i) arr[i] = rd.Next(1, 101); Console.WriteLine("Массив перед сортировкой:"); foreach (int x in arr) System.Console.Write(x + " "); arr = Merge_Sort(arr); Console.WriteLine("\n\nМассив после сортировки:"); foreach (int x in arr) System.Console.Write(x + " "); Console.WriteLine("\n\nДля выхода нажмите <Enter>."); Console.ReadKey(); }
Perl
@out=(5,2,8,9,4,2,7,9,4,1,6,9,0); sub sortM{ my($array,$first,$last)=@_; if($last>$first){ my$middle=int(($last+$first)/2); sortM($array,$first,$middle); sortM($array,$middle+1,$last); my$j=0; $work[$j++]=$$array[$_]for($first..$last); $middle=int(($first+$last)/2)if($middle>$last); my$n=$middle-$first+1; for($i=$first,$j=0,$k=$n;$i<=$last;$i++){ if(($j<$n)&&(($k==(($last-$first)+1))||($work[$j]lt$work[$k]))){ $$array[$i]=$work[$j++] }else{ $$array[$i]=$work[$k++]; } } } } sortM(\@out,0,$#out+1);
Паскаль (сортировка текстовых файлов)
Сортировка простым слиянием
Procedure MergeSort(name: string; var f: text); Var a1,a2,s,i,j,kol,tmp: integer; f1,f2: text; b: boolean; Begin kol:=0; Assign(f,name); Reset(f); While not EOF(f) do begin read(f,a1); inc(kol); End; Close(f); Assign(f1,'{имя 1-го вспомогательного файла}'); Assign(f2,'{имя 2-го вспомогательного файла}'); s:=1; While (s<kol) do begin Reset(f); Rewrite(f1); Rewrite(f2); For i:=1 to kol div 2 do begin Read(f,a1); Write(f1,a1,' '); End; If (kol div 2) mod s<>0 then begin tmp:=kol div 2; While tmp mod s<>0 do begin Read(f,a1); Write(f1,a1,' '); inc(tmp); End; End; While not EOF(f) do begin Read(f,a2); Write(f2,a2,' '); End; Close(f); Close(f1); Close(f2); Rewrite(f); Reset(f1); Reset(f2); Read(f1,a1); Read(f2,a2); While (not EOF(f1)) and (not EOF(f2)) do begin i:=0; j:=0; b:=true; While (b) and (not EOF(f1)) and (not EOF(f2)) do begin If (a1<a2) then begin Write(f,a1,' '); Read(f1,a1); inc(i); End else begin Write(f,a2,' '); Read(f2,a2); inc(j); End; If (i=s) or (j=s) then b:=false; End; If not b then begin While (i<s) and (not EOF(f1)) do begin Write(f,a1,' '); Read(f1,a1); inc(i); End; While (j<s) and (not EOF(f2)) do begin Write(f,a2,' '); Read(f2,a2); inc(j); End; End; End; While not EOF(f1) do begin tmp:=a1; Read(f1,a1); If not EOF(f1) then Write(f,tmp,' ') else Write(f,tmp); End; While not EOF(f2) do begin tmp:=a2; Read(f2,a2); If not EOF(f2) then Write(f,tmp,' ') else Write(f,tmp); End; Close(f); Close(f1); Close(f2); s:=s*2; End; Erase(f1); Erase(f2); End;
Сортировка естественным слиянием
Procedure MergeSort(name: string; var f: text); Var s1,s2,a1,a2,where,tmp: integer; f1,f2: text; Begin s1:=5; s2:=5; {Можно задать любые числа, которые запустят цикл while} Assign(f,name); Assign(f1,'{имя 1-го вспомогательного файла}'); Assign(f2,'{имя 2-го вспомогательного файла}'); While (s1>1) and (s2>=1) do begin where:=1; s1:=0; s2:=0; Reset(f); Rewrite(f1); Rewrite(f2); Read(f,a1); Write(f1,a1,' '); While not EOF(f) do begin read(f,a2); If (a2<a1) then begin Case where of 1: begin where:=2; inc(s1); End; 2: begin where:=1; inc(s2); End; End; End; Case where of 1: write(f1,a2,' '); 2: write(f2,a2,' '); End; a1:=a2; End; If where=2 then inc(s2) else inc(s1); Close(f); Close(f1); Close(f2); Rewrite(f); Reset(f1); Reset(f2); Read(f1,a1); Read(f2,a2); While (not EOF(f1)) and (not EOF(f2)) do begin If (a1<=a2) then begin Write(f,a1,' '); Read(f1,a1); End else begin Write(f,a2,' '); Read(f2,a2); End; End; While not EOF(f1) do begin tmp:=a1; Read(f1,a1); If not EOF(f1) then Write(f,tmp,' ') else Write(f,tmp); End; While not EOF(f2) do begin tmp:=a2; Read(f2,a2); If not EOF(f2) then Write(f,tmp,' ') else Write(f,tmp); End; Close(f); Close(f1); Close(f2); End; Erase(f1); Erase(f2); End;
Delphi (сортировка произвольных типов данных - простое слияние)
unit uMergeSort; interface type TItem = Integer; //Здесь можно написать Ваш произвольный тип TArray = array of TItem; procedure MergeSort(var Arr: TArray); implementation function IsBigger(d1, d2 : TItem) : Boolean; begin Result := (d1 > d2); //Сравниваем d1 и d2. Не обязательно так. Зависит от Вашего типа. //Сюда можно добавить счетчик сравнений end; //Процедура сортировки слияниями procedure MergeSort(var Arr: TArray); var tmp : TArray; //Временный буфер //Слияние procedure merge(L, Spl, R : Integer); var i, j, k : Integer; begin i := L; j := Spl + 1; k := 0; //Выбираем меньший из первых и добавляем в tmp while (i <= Spl) and (j <=R) do begin if IsBigger(Arr[i], Arr[j]) then begin tmp[k] := Arr[j]; Inc(j); end else begin tmp[k] := Arr[i]; Inc(i); end; Inc(k); end; //Просто дописываем в tmp оставшиеся эл-ты if i <= Spl then //Если первая часть не пуста for j := i to Spl do begin tmp[k] := Arr[j]; Inc(k); end else //Если вторая часть не пуста for i := j to R do begin tmp[k] := Arr[i]; Inc(k); end; //Перемещаем из tmp в arr Move(tmp[0], Arr[L], k*SizeOf(TItem)); end; //Сортировка procedure sort(L, R : Integer); var splitter : Integer; begin //Массив из 1-го эл-та упорядочен по определению if L >= R then Exit; splitter := (L + R) div 2; //Делим массив пополам sort(L, splitter); //Сортируем каждую sort(splitter + 1, R); //часть по отдельности merge(L, splitter, R); //Производим слияние end; //Основная часть процедуры сортировки begin SetLength(tmp, Length(Arr)); sort(0, Length(Arr) - 1); SetLength(tmp, 0); end; end.
D
void mergeSort(int[] array) { static void merge(int[] array, int q) { int[] leftArray = array[0..q].dup ~ int.max; int[] rightArray = array[q..$].dup ~ int.max; int i = 0; int j = 0; int length = array.length; for (int k = 0; k < length; ++k) { array[k] = (leftArray[i] <= rightArray[j]) ? leftArray[i++] : rightArray[j++]; } } if (array.length > 1) { int q = array.length / 2; mergeSort(array[0..q]); mergeSort(array[q..$]); merge(array, q); } }
Python 2.7 (функциональная реализация)
def merge(right, left, result): result.append((left if left[0] < right[0] else right).pop(0)) return merge(right=right, left=left, result=result) if left and right else result+left+right merge_sort = (lambda arr: arr if len(arr) == 1 else merge(merge_sort(arr[len(arr)/2:]), merge_sort(arr[:len(arr)/2]), []))
Недостатком сортировки слиянием является использование дополнительной памяти. Но когда работать приходиться с файлами или списками, доступ к которым осуществляется только последовательно, то очень удобно применять именно этот метод. Также, к достоинствам алгоритма стоит отнести его устойчивость и неплохую скорость работы O(n*logn).
При написании статьи были использованы открытые источники сети интернет :