美文网首页
Merge Sort

Merge Sort

作者: ArthurIsUsed | 来源:发表于2020-06-09 09:37 被阅读0次

废话不多说,先上伪代码

Merge(A, p, q, r)
    n1 = q - p + 1
    n2 = r -q
    Let L[1..n1+1] and R[1..n2 +1] be new arrays
    for i = 1 to n1
        L[i] = A[p+i-1]
    for j = 1 to n2
        L[j] = A[q+j]
    L[n1+1] = ∝
    R[n2+1] = ∝
    i = 1
    j = 1
    for k = p to r
        if L[i] ≤ R[j]
             A[k] = L[i]
             i = i + 1
        else
            A[k] = R[j]
            j = j + 1


Merge_sort(A, p, r)
    if p < r
        q = ⌊(p+r)/2⌋
    Merge_sort(A, p, q)
    Merge_sort(A, q+1, r)
    Merge(A, p, q, r)

Merge sort 采用分治思想来排序,一组数据对半分成两组, L1与L2,分别对L1与L2再分成两组。递归调用merge_sort函数,直到每一组只剩一个元素。一个元素,自然是有序的。递归回调时,把这些元素一个一个组装起来即可。组装的过程就是调用merge函数,重点是for k = p to r这一段。


对于分治思想,《Introduction To Algorithms》书上原文如下:

The merge sort algorithm closely follows the divide-and-conquer paradigm, Intuitively, it operates as follows
Divide: Divide the n-element sequence to be sorted into wto subsequences of n/2 elements each.
Conquer: Sort the two subsequences recusrively using merge sort.
Combine: Merge the two sorted subsequences to produce the sorted answer.

Merge_sort函数相当于divide, Merge_sort(A, p, q)是对左边递归分组, Merge_sort(A,q+1, r)是对右边递归分组。一直递归到每组只剩一个元素(有序,conquer)就开始合并(combine)。即分完之后是治,治后是合。合并的函数是Merge(A, p, q, r)
用扑克牌来作个形象的说明。8张扑克牌,黑桃2~9,打乱顺序,合并放桌上。第一次是一堆,开始divide,分成2堆,2堆分4堆,4堆分8堆。治的第一轮,A[1]与A[2]、A[3]与A[4]、A[5]与A[6]、A[7]与A[8]分别比较合并。之后A[1, 2]、A[3, 4]、A[5, 6]、A[7, 8]都是有序的。Merge_sort后再一次回调合并。A[1, 2, 3, 4]与A[5, 6, 7, 8]都是有序,最后一次合并A[1~8]都有序。
列表图说明如下:

  • 黑线上方是分的过程,下方是合的过程。
  • 横向是治,比较完大小后转入上一层递归回调。

在Merge过程中有一个conquer,便是治的问题,可以这样理解。当牌分成8堆,拿第1、2堆两张比较,小的放左边,两两比较合并成4堆。对于4堆2张牌,第1堆拿最左边那张,在第1、2堆左边选出最小的,交替拿完3张。至此,4堆变2堆,重复上一个过程,2堆变1堆。这个拿牌合并的过程,相当于伪代码中for k = p to r的这套循环。

Python代码如下:

def merge(ml, p, q, r):
    L = ml[p:q]      # 对原始数据,左右两边各复制一份,保留。用于之后比较与下方的for循环,类似于交换数据。
    R = ml[q:r]
    L.append(float('inf'))       # 设置哨兵值,其值是正无穷
    R.append(float('inf'))
    i = j = 0
    for n in  range(p, r):       # 对于分好堆的牌,开始抓牌合并
        if L[i] <= R[j]:         
            ml[n] = L[i]
            i = i + 1
        else:
            ml[n] = R[j]
            j = j + 1
    return ml


def merge_sort(ml, p, r):
    if p+1 < r:
        q = (p + r) // 2
        merge_sort(ml, p, q)
        merge_sort(ml, q, r)
        merge(ml, p, q, r)

    return ml

slist = [5, 2, 4, 7, 8, 3, 9, 6]
mslist = merge_sort(slist, 0, len(slist))
print(mslist)

初次调用merger sort 排序显示的结果不正常,只有[2, 4, 5,]这几个数字,关于merge函数,最开始的代码如下:

def merge(ml, p, q, r):
    nleft = q - p
    nright = r - q
    L = ml[:nleft]
    R = ml[nright:r]

若是如此,最终替换的都是前三个数。merge sort的主要思想是分组、比较、合并。最终是分成8堆,即,要保存L=[i](i = 2k,k=0,1, 2)、R=[j](j=2k+1, k=0,1,2),比较后分别替换mlx这8个数。分析明白后,把这4行代码替换成L=ml[p:q]R=[q:r]即可。

现说明为何需要添加哨兵值,以及merge函数中for n in range(p, r):的思想。当merge_sort递归到最深的一层是,L与R都只有两个元素,另inf=float('inf'),表示正无穷,L=[5, inf], R=[2, inf]。此时p=0,r=2,即n=[0, 1], 第一次比较合并是合并列表前两个元素。i = j = n = 0时,5<2, 不成立,即ml[0]=2, j=1。进入下一层for循环,此时R[1]=inf,把5添加到ml[1]的位置。

这个过程相当于桌面两张牌,选出最小的那张放左手最左边,然后再从另外一堆抓一张放左手。若堆里有2张及以上的牌,从L与R中选一张最小的放左手,设L中取最小,第二张牌就是从R中取,第三张又是L中取。如此,交替,取完两堆的牌。

第一次取最小,是对应L[i] <= R[j],表明升序。第一次取最大,是对应L[i] >= R[j],表明降序。

相关文章

网友评论

      本文标题:Merge Sort

      本文链接:https://www.haomeiwen.com/subject/zddvzhtx.html