Как написать итеративную сортировку слиянием? - PullRequest
6 голосов
/ 11 января 2012

Я написал рекурсивную версию сортировки слиянием.В нем используется отдельная подпрограмма merge:

def merge(lst1, lst2):
    i = j = 0
    merged = []
    while i < len(lst1) and j < len(lst2):
        if lst1[i] <= lst2[j]:
            merged.append(lst1[i])
            i += 1
        else:
            merged.append(lst2[j])
            j += 1
    merged.extend(lst1[i:])
    merged.extend(lst2[j:])
    return merged

def merge_sort(lst):
    if len(lst) < 2:
        return lst
    else:
        middle = len(lst) / 2
        return merge(merge_sort(lst[:middle]), merge_sort(lst[middle:]))

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

Спасибо!

Ответы [ 4 ]

4 голосов
/ 11 января 2012

Вам потребуется функция merge (та же или почти та же самая функция merge), которая будет вызываться повторно. Таким образом, вам не нужно менять функцию merge.

Это многоходовое решение. Начните с размера куска 2 и удваивайте размер куска в каждом проходе.

При каждом проходе разбивайте список на непересекающиеся куски любого размера. Разделите каждый кусок на 2 и назовите merge на 2 части.

Это версия снизу вверх.

2 голосов
/ 09 июля 2014

Я расширился на основе описания Дивьи (также добавлена ​​тестовая функция для проверки). Приведенный ниже код может быть оптимизирован путем исключения подмассивов (data_1 и data_2) и сортировки на месте.

def merge_sort_iterative(data):
  """ gets the data using merge sort and returns sorted."""

  for j in range(1, len(data)):
    j *= 2
    for i in range(0,len(data),j):
      data_1 = data[i:i+(j/2)]
      data_2 = data[i+(j/2):j-i]
      l = m = 0
      while l < len(data_1) and m < len(data_2):
        if data_1[l] < data_2[m]:
          m += 1
        elif data_1[l] > data_2[m]:
          data_1[l], data_2[m] = data_2[m], data_1[l]
          l += 1
      data[i:i+(j/2)], data[i+(j/2):j-i] = data_1, data_2

  return data

def test_merge_sort():
  """test function for verifying algorithm correctness"""

  import random
  import time

  sample_size = 5000
  sample_data = random.sample(range(sample_size*5), sample_size)
  print 'Sample size: ', sample_size

  begin = time.time()
  sample_data = [5,4,3,2,1]
  result = merge_sort_iterative(sample_data)
  end = time.time()
  expected = sorted(sample_data)
  print 'Sorting time: %f \'secs'%(end-begin)

  assert result == expected, 'Algorithm failed'
  print 'Algorithm correct'

if __name__ == '__main__':
  test_merge_sort()
1 голос
/ 03 февраля 2016

Вот реализация Java

public static <T extends Comparable<? super T>> void iterativeMergeSort(T[] seed) {

    for (int i = 1; i <seed.length; i=i+i)
    {
        for (int j = 0; j < seed.length - i; j = j + i+i)
        {
            inPlaceMerge(seed, j, j + i-1, Math.min(j+i + i -1, seed.length -1));
        }
    }       
}

public static <T extends Comparable<? super T>>  void inPlaceMerge(T[] collection, int low, int mid, int high) {
    int left = low;
    int right = mid + 1;

    if(collection[mid].equals(collection[right])) {
        return ;//Skip the merge if required
    }
    while (left <= mid && right <= high) {          
        // Select from left:  no change, just advance left
        if (collection[left].compareTo(collection[right]) <= 0) {
            left ++;
        } else { // Select from right:  rotate [left..right] and correct
            T tmp = collection[right]; // Will move to [left]
            rotateRight(collection, left, right - left);
            collection[left] = tmp;
            // EVERYTHING has moved up by one
            left ++; right ++; mid ++;
        }
    }       
}

Вот модульный тест

private Integer[] seed;

@Before
public void doBeforeEachTestCase() {
    this.seed = new Integer[]{4,2,3,1,5,8,7,6};
}
@Test
public void iterativeMergeSortFirstTest() {
    ArrayUtils.<Integer>iterativeMergeSort(seed);
    Integer[] result = new Integer[]{1,2,3,4,5,6,7,8};
    assertThat(seed, equalTo(result));  
}
0 голосов
/ 04 января 2017

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

Все, что нужно, - это вложенный цикл, в котором внутренний цикл выполняет слияние пар из 2 ^ k элементов, а внешний цикл отвечает за увеличение k.

Требуется дополнительный шаг - объединить любую непарную группу с предыдущей объединенной группой. Непарная группа будет встречаться, если число элементов не является степенью 2. Непарная группа всегда будет в конце итерации.

например. [5, 7, 3, 4, 1, 9] -> [5, 7] [3, 4] [1, 9] -> [3, 4, 5, 7] [1, 9] -> [1 , 3, 4, 5, 7, 9]

В приведенном выше примере [1, 9] - это группа, в которой не было другой группы, с которой нужно было изначально объединиться. Таким образом, он был объединен с предыдущей группой (которая уже была объединена и отсортирована)

Вот реализация Python:

from MergeSort import merge

def sort(arr):
    n = len(arr) - 1
    c = 1
    start = 0
    mid = 0
    end = 0
    while c <= n:
        while end < n:
            mid = start + c//2
            end = start + c
            if (start < n) and (end <= n):
                merge(arr, start, mid, end)
                start = end + 1
            else:
                merge(arr, start - c - 1, start-1, n)
        c = 2*c + 1
        start = 0
        mid = 0
        end = 0

Я использовал функцию слияния из обычной (рекурсивной) версии. Хотя приведенный выше код не самый элегантный, но он работает и имеет ту же сложность, что и рекурсивная версия. (Я не проверил подробно, но мне так кажется с первого взгляда)

Вот модульный тест:

def test_merge_sort_iterative(self):
    for i in range(1, 100):
        length = randint(10, 5000)
        data = [randint(1, 10000) for x in range(1, length)]
        IterativeMergeSort.sort(data)
        issorted = True
        i = 0
        while (i < len(data) - 1) & issorted:
            if data[i] > data[i + 1]:
                issorted = False
            i += 1
    self.assertTrue(issorted, data)
    return
...