Сортировка слиянием — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
Строка 1: Строка 1:
 
==Описание==
 
==Описание==
 
[[Файл:Merge-sort1.gif|right|380px|thumb|Действие алгоритма.]]
 
[[Файл:Merge-sort1.gif|right|380px|thumb|Действие алгоритма.]]
'''Сортировка слиянием''' — алгоритм сортировки, хороший пример использования принципа «разделяй и властвуй». Он был пред­ло­жен Джо­ном фон Ней­ма­ном в 1945 го­ду.
+
'''Сортировка слиянием''' — алгоритм сортировки. Он был пред­ло­жен Джо­ном фон Ней­ма­ном в 1945 го­ду.
  
Это ста­биль­ный ал­го­ритм сор­ти­ров­ки, использующий <tex>O(n)</tex> дополнительной памяти и <tex>O(n</tex> <tex>\log(n))</tex> времени.
+
Это ста­биль­ный ал­го­ритм, использующий <tex>O(n)</tex> дополнительной памяти и <tex>O(n</tex> <tex>\log(n))</tex> времени.
  
 
==Принцип работы==
 
==Принцип работы==
Принцип «разделяй и властвуй» — сначала задача разбивается на несколько подзадач меньшего размера. Затем эти задачи решаются с помощью рекурсивного вызова или непосредственно, если их размер достаточно мал. Наконец, их решения комбинируются, и получается решение исходной задачи.
+
Этот алгоритм хороший пример использования принципа «разделяй и властвуй» — сначала задача разбивается на несколько подзадач меньшего размера. Затем эти задачи решаются с помощью рекурсивного вызова или непосредственно, если их размер достаточно мал. Наконец, их решения комбинируются, и получается решение исходной задачи.
  
Про­це­ду­ра слия­ния тре­бу­ет два от­сор­ти­ро­ван­ных мас­си­ва. За­ме­тив, что мас­сив из од­но­го эле­мен­та по опре­де­ле­нию яв­ля­ет­ся от­сор­ти­ро­ван­ным, мы мо­жем осу­ще­ствить сор­ти­ров­ку сле­дую­щим об­ра­зом:
+
Для процедуры слияния требуется два отсортированных массива. Зная, что массив из одного элемента по определению отсортирован, мы можем разработать такой алгоритм:
  
# Раз­бить имею­щие­ся эле­мен­ты мас­си­ва на па­ры и осу­ще­ствить слия­ние эле­мен­тов каж­дой па­ры, по­лу­чив от­сор­ти­ро­ван­ные це­поч­ки дли­ны 2 (кро­ме, быть мо­жет, од­но­го эле­мен­та, для ко­то­ро­го не на­шлось па­ры).
+
# Массив разбивается на половинки до тех пор, пока размер "половинки" не станет равным единице.
# Раз­бить имею­щие­ся от­сор­ти­ро­ван­ные це­поч­ки на па­ры, и осу­ще­ствить слия­ние це­по­чек каж­дой па­ры.
+
# Каждая из получившихся частей сортируется отдельно. Или же это просто одиночный элемент.
# Ес­ли чис­ло от­сор­ти­ро­ван­ных це­по­чек боль­ше еди­ни­цы, пе­рей­ти к ша­гу 2.
+
# "Сливаем" два упорядоченных массива в один.
  
 
===Слияние двух массивов===
 
===Слияние двух массивов===
Допустим, у нас есть два отсортированных массива А и B размерами <tex>N_a </tex> и <tex>N_b </tex>  со­ответственно, и мы хотим объединить их элементы в один большой отсортирован­ный массив C размером <tex>N_a + N_b </tex> . Для этого можно применить процедуру слия­ния, суть которой заключается в повторяющемся «отделении» элемента, наи­меньшего из двух имеющихся в началах исходных массивов, и присоединении это­го элемента к концу результирующего массива. Элементы мы переносим до тех пор, пока один из исходных массивов не закончится. После этого оставшийся «хвост» одного из входных массивов дописывается в конец результирующего мас­сива. Пример работы процедуры показан на рисунке:
+
Допустим, у нас есть два отсортированных массива А и B размерами <tex>N_a </tex> и <tex>N_b </tex>  со­ответственно, и мы хотим объединить их элементы в один большой отсортирован­ный массив C размером <tex>N_a + N_b </tex> . Для этого можно применить процедуру слияния. Эта процедура заключается в том, что мы сравниваем элементы массивов (начиная с начала) и меньший из них записываем в финальный. И затем, в массиве у которого оказался меньший элемент, переходим к следующему элементу и сравниваем теперь его. В конце, если один из массивов закончился, мы просто дописываем в финальный другой массив. После мы наш финальный массив записываем заместо двух исходных и получаем отсортированный участок.
 
[[Файл:Mergearr.png|right|300px|thumb|Пример работы процедуры слияния.]]
 
[[Файл:Mergearr.png|right|300px|thumb|Пример работы процедуры слияния.]]
<br>
+
 
 
Алгоритм слияния формально можно записать следующим образом:
 
Алгоритм слияния формально можно записать следующим образом:
  
Строка 38: Строка 38:
 
==Рекурсивный алгоритм==
 
==Рекурсивный алгоритм==
 
[[Файл:Merge sort1.png|300px|right|thumb|Пример работы рекурсивного алгоритма сортировки слиянием]]
 
[[Файл:Merge sort1.png|300px|right|thumb|Пример работы рекурсивного алгоритма сортировки слиянием]]
Проще всего формализовать этот алгоритм рекурсивным способом. Функция сортирует участок массива от элемента с номером left до элемен­та с номером right:
+
Функция сортирует участок массива от элемента с номером left до элемен­та с номером right:
 +
 
 +
right и left — правая и левая граница массива, middle — середина.
  
 +
Условие выхода — если массив стал состоять из 1 элемента.
 
<pre>
 
<pre>
sort(array a, int left, int right) // right и left — правая и левая граница массива, middle — середина
+
sort(array a, int left, int right)
 
   middle = right / 2   
 
   middle = right / 2   
   if middle == right    // условие выхода — если массив стал состоять из 1 элемента
+
   if middle == right     
 
     return
 
     return
 
   sort(a, left, middle)  
 
   sort(a, left, middle)  
Строка 51: Строка 54:
  
 
Пример работы алгоритма показан на рисунке:
 
Пример работы алгоритма показан на рисунке:
 +
 +
==Восходящая сортировка слиянием==
 +
[[Файл:mergenonrec.png|300px|right|thumb|Пример работы восходящей сортировки слиянием]]
 +
Помимо рекурсивного алгоритма существует и альтернативный.
 +
Пример работы алгоритма показан на рисунке:
 +
 +
# Выделим память размером с занимаемой памяти исходного массива.
 +
# Попарно сравним элементы, записывая во временную память.
 +
# Поменяем указатели временного и исходного массива.
 +
# Выполним слияние "кусочков" размером два.
 +
# Повторяем до тех пор, пока не сделаем единый кусок.
 +
# Удаляем временный массив.
 +
 +
Процедуру слияния надо будет изменить, так, что-бы она записывала результат в результирующий массив (mas1)
 +
<pre>sort(array * mas, int elementsAmount)
 +
  array * mas1 =  new array[elementsAmount];
 +
  for(int size = 1; size < elementsAmount; size *= 2) {
 +
    int start = 0;
 +
    for(; (start + size) < elementsAmount; start += size * 2)
 +
      merge(mas + start, mas + start,
 +
          mas + start + size,
 +
          mas + start + size + min(size,elementsAmount - start - size));
 +
    for(; start < elementsAmount; ++start)
 +
      mas1[start] = mas[start];
 +
    array * temp = mas1;
 +
    mas1 = mas;
 +
    mas = temp;
 +
  delete[] mas1;
 +
</pre>
  
 
==Время работы==
 
==Время работы==
Строка 63: Строка 95:
 
==Ссылки==
 
==Ссылки==
 
*[http://ru.wikipedia.org/wiki/Mergesort Википедия — сортировка слиянием]
 
*[http://ru.wikipedia.org/wiki/Mergesort Википедия — сортировка слиянием]
*[http://iproc.ru/parallel-programming/lection-6/ Сортировка слиянием]
 
 
*[http://www.sorting-algorithms.com/merge-sort Сортировка слиянием, анимация и свойства (англ.)]
 
*[http://www.sorting-algorithms.com/merge-sort Сортировка слиянием, анимация и свойства (англ.)]
 
*[http://ru.wikibooks.org/wiki/%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%80%D1%8B_%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8_%D1%81%D0%BB%D0%B8%D1%8F%D0%BD%D0%B8%D0%B5%D0%BC Примеры реализации на различных языках (Википедия)]
 
*[http://ru.wikibooks.org/wiki/%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%80%D1%8B_%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8_%D1%81%D0%BB%D0%B8%D1%8F%D0%BD%D0%B8%D0%B5%D0%BC Примеры реализации на различных языках (Википедия)]

Версия 23:18, 20 мая 2012

Описание

Действие алгоритма.

Сортировка слиянием — алгоритм сортировки. Он был пред­ло­жен Джо­ном фон Ней­ма­ном в 1945 го­ду.

Это ста­биль­ный ал­го­ритм, использующий [math]O(n)[/math] дополнительной памяти и [math]O(n[/math] [math]\log(n))[/math] времени.

Принцип работы

Этот алгоритм хороший пример использования принципа «разделяй и властвуй» — сначала задача разбивается на несколько подзадач меньшего размера. Затем эти задачи решаются с помощью рекурсивного вызова или непосредственно, если их размер достаточно мал. Наконец, их решения комбинируются, и получается решение исходной задачи.

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

  1. Массив разбивается на половинки до тех пор, пока размер "половинки" не станет равным единице.
  2. Каждая из получившихся частей сортируется отдельно. Или же это просто одиночный элемент.
  3. "Сливаем" два упорядоченных массива в один.

Слияние двух массивов

Допустим, у нас есть два отсортированных массива А и B размерами [math]N_a [/math] и [math]N_b [/math] со­ответственно, и мы хотим объединить их элементы в один большой отсортирован­ный массив C размером [math]N_a + N_b [/math] . Для этого можно применить процедуру слияния. Эта процедура заключается в том, что мы сравниваем элементы массивов (начиная с начала) и меньший из них записываем в финальный. И затем, в массиве у которого оказался меньший элемент, переходим к следующему элементу и сравниваем теперь его. В конце, если один из массивов закончился, мы просто дописываем в финальный другой массив. После мы наш финальный массив записываем заместо двух исходных и получаем отсортированный участок.

Пример работы процедуры слияния.

Алгоритм слияния формально можно записать следующим образом:

// слияние двух массивов с помощью временного
merge(array a, int left, int middle, int right) // left - левая граница, right - правая, middle - середина
  array b = a[middle + 1, right];
  i = left, j = middle + 1, k = 0;
  array temp;
  while i <= middle and j <= right
    temp[k++] = (a[j] < b[i]) ? a[j++] : b[i++];
  while i <= middle
    temp[k++] = b[i++];
  while j <= right
    temp[k++] = a[j++];
  for (int t = 0; t != k; t++)
    a[t] = temp[t]
// в конце a[1..k] это будет отсортированный массив

Рекурсивный алгоритм

Пример работы рекурсивного алгоритма сортировки слиянием

Функция сортирует участок массива от элемента с номером left до элемен­та с номером right:

right и left — правая и левая граница массива, middle — середина.

Условие выхода — если массив стал состоять из 1 элемента.

sort(array a, int left, int right)
  middle = right / 2  
  if middle == right    
    return
  sort(a, left, middle) 
  sort (a, middle + 1, right)
  merge(array a, left, middle, right)

Пример работы алгоритма показан на рисунке:

Восходящая сортировка слиянием

Пример работы восходящей сортировки слиянием

Помимо рекурсивного алгоритма существует и альтернативный. Пример работы алгоритма показан на рисунке:

  1. Выделим память размером с занимаемой памяти исходного массива.
  2. Попарно сравним элементы, записывая во временную память.
  3. Поменяем указатели временного и исходного массива.
  4. Выполним слияние "кусочков" размером два.
  5. Повторяем до тех пор, пока не сделаем единый кусок.
  6. Удаляем временный массив.

Процедуру слияния надо будет изменить, так, что-бы она записывала результат в результирующий массив (mas1)

sort(array * mas, int elementsAmount)
  array * mas1 =  new array[elementsAmount];
  for(int size = 1; size < elementsAmount; size *= 2) {
    int start = 0; 
    for(; (start + size) < elementsAmount; start += size * 2) 
      merge(mas + start, mas + start,
          mas + start + size, 
          mas + start + size + min(size,elementsAmount - start - size));
    for(; start < elementsAmount; ++start)
      mas1[start] = mas[start];
    array * temp = mas1;
    mas1 = mas;
    mas = temp; 
  delete[] mas1;

Время работы

Чтобы оценить время работы этого алгоритма, составим рекуррентное соотношение. Пускай [math]T(n)[/math] — время сортировки массива длины n, тогда для сортировки слиянием справедливо [math]T(n)=2T(n/2)+O(n)[/math]
([math]O(n)[/math] — это время, необходимое на то, чтобы слить два массива). Распишем это соотношение:

[math]T(n)[/math] [math]=[/math] [math]2T(n/2)[/math] [math]+[/math] [math]O(n)[/math] [math]=[/math] [math]4T(n/4)[/math] [math]+[/math] [math]2*O(n)[/math] [math]=[/math] [math]...[/math] [math]=[/math] [math]2^kT(1)[/math] [math]+[/math] [math]kO(n).[/math]

Осталось оценить [math]k[/math]. Мы знаем, что [math]2^k=n[/math], а значит [math]k=\log(n)[/math]. Уравнение примет вид [math]T(n)=nT(1)+ \log(n)O(n)[/math]. Так как [math]T(1)[/math] — константа, то [math]T(n)=O(n)+\log(n)O(n)=O(n\log(n))[/math].


Ссылки