merge sort归并排序,数组中的逆序对,python实现

 刷到算法导论思考题

2-4 d

给出一个确定在n个元素的任何排列中逆序对数量的算法(提示:修改归并排序)

觉得有意思,而leetcode上又是困难题,所以记录下。因为用归并排序的思路做,不用暴力求解的方法,所以先从归并排序讲起。

一.归并排序

def merge_sort(nums, l, r):
    if l>=r:
        return
    m = l + (r - l)//2               #数组取中点,没有(l+r)/2是为了防止两大数相加溢出
    merge_sort(nums,l,m)              # divide 分
    merge_sort(nums, m +1, r)          # divide 分
    merge(nums,l,m,r)                    #conquer 治理

def merge(nums,l,m,r):
    temp = [0 for i in range(0,r-l+1)]     #初始化临时数组
    temp[l:r+1] = nums[l:r+1]               #复制原数组至临时数组
    i= l            
    j= m + 1                                    #双指针
    for k in range(l,r + 1):
        if i == m + 1:
            nums[k] = temp[j]
            j += 1
        elif (j == r + 1) or temp[i] <= temp[j]:   #可以分开为两个elif
            nums[k] = temp[i]
            i += 1
        else:
            nums[k] = temp[j]
            j += 1

标准的分治divide and conquer归并排序。

下面还提供一个放了哨兵的归并排序。好处在于不需要考虑这么多case,更容易想到,理解。

import math
def merge_sort(nums, l, r):
    if l>=r:
        return
    m = l + (r - l)//2
    merge_sort(nums,l,m)
    merge_sort(nums, m +1, r)
    merge_sentinal(nums,l,m,r)

def merge_sentinal(nums,l,m,r):
    left = nums[l:m+1]
    right = nums[m+1:r+1]
    left.append(math.inf)   #哨兵
    right.append(math.inf)
    i,j=0,0
    for k in range(l,r+1):
        if left[i] <= right[j]:
            nums[k] = left[i]
            i += 1
        else:
            nums[k] = right[j]
            j += 1

二.数组中的逆序对

为什么用归并排序?

逆序对就是相对顺序逆的两个元素。对归并排序熟悉后会发现他是几个高级排序里,在排序时,对各元素相对位置要求最高且能清晰看见的那个。   归并排序会把数组对半分开,左右两边各自从头比较大小,然后依次排序放回数组。按照数组里相对元素位置,分开后的右数组元素理应比左边大,但实际上归并排序的“治”的时候,有些右边元素是小于左边的。这时,一个逆序对就被发现了。

下面上码,用的leetcode答案格式,即包了个class。

class Solution:
    def reversePairs(self, nums):
        tmp = [0 for i in range(0,len(nums))]
        return self.mergePairs(nums, 0, len(nums) - 1, tmp)

    def mergePairs(self, nums, l, r,tmp):
        if l >= r:
            return 0
        m = l + (r - l) // 2
        left = self.mergePairs(nums, l, m, tmp)
        right = self.mergePairs(nums, m + 1, r,tmp)
        middle = self.merge(nums, l, m, r, tmp)
        inversion = right + middle + left
        return inversion

    def merge(self, nums, l, m, r, tmp):
        tmp[l:r+1] = nums[l:r+1]
        i, j = l, m+1
        count = 0
        for k in range(l, r + 1):
            if (i == m + 1):
                nums[k] = tmp[j]
                j += 1
            elif (j == r + 1) or (tmp[i] <= tmp[j]):
                nums[k] = tmp[i]
                i += 1
            else:                                #右子数组比左子数组小,即逆序
                nums[k] = tmp[j]
                j += 1
                count += m - i + 1               #累加逆序对的数量

        return count

题目先call reversePairs, 多亏这看似多余的部分,能建造一个大的临时数组,这样递归的时候不用重复造浪费时间空间。

下面的mergepairs()是标准的归并排序起手。区别在于递归base case为0而不是null,并需统计求和各递归分支的逆序对数量作为返回值

conquer“治”部分merge()里,变化最大的是多了个逆序对计数 count += m - i + 1 我个人感觉是比较难想的一点。但如果用简单例子过一遍流程,并画图的话会较为直观的算出等式。

口头来讲就是:此时m为左数组的最后一个元素,m+1为右数组的元素。其中 i 这个计数器记录的是左数组i个已经排序并放回原数组。

进入最后一个else的条件是右数组未排序的第一个bj  小与 左数组未排序的第一个ai。根据递归,我们可以信任左右两个子数组(也就是子问题)已经排序完成,那么ai到左数组末尾元素am未止的所有元素均>bj,且这m-i+1个元素排序故递增,即都是右一bj的逆序对。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值