如何迭代地编写归并排序?

7

我已经编写了一个递归版本的归并排序算法。它使用了一个单独的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:]))

为了节省堆栈空间(并且为了学习算法的乐趣),我正试图以迭代方式编写这个函数。然而,我发现这很困难,因为我不确定如何在最后组合不同的列表。
谢谢!

请考虑这里的答案:https://dev59.com/JUvSa4cB1Zd3GeqPgKzo - Marcin
4个回答

4
您需要一个 merge 函数(相同或几乎相同的 merge 函数),该函数将被重复调用。因此,您不需要更改 merge 函数。
这是一种多遍解决方案。从大小为2的块开始,并在每个遍历中将块大小加倍。
在每个遍历中,将列表分成大小为任意值的非重叠块。将每个块分成2个部分,然后调用 merge
这是一种自底向上的版本。

2

我根据Divya的描述进行了扩展(还添加了一个测试函数进行验证)。下面的代码可以通过消除子数组(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
这是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

递归更直观,因此我更喜欢它,除非在某些情况下我想避免显著的堆栈深度(例如,在消耗某些协程实现时)。然而,在归并排序的情况下,迭代版本实际上更容易理解(至少是伪代码)。

所需的全部内容只是一个嵌套循环,内部循环对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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接