python语言求排列的逆序数,分支算法实现,复杂度O(nlogn),附完整代码可运行

问题:

考虑1,2,3...n(n<=100000)的排列,比如263451中含有8个逆序(2,1),(3,1),(4,1),(5,1),(6,3),(6,4),(6,5),(6,1),即大的数在小的数的左边,就构成一个逆序对。

现给定1,2,...n的一个排列,求它的逆序数。

解题策略:

  1. 笨方法O(n^2)
  2. 分治O(nlogn)

分治的实现方法:

  1. 数组分为两半,分别求出左半边和右半边的逆序数
  2. 再算有多少逆序是由左半边取一个数和右半边取一个数构成(要求O(n)实现)。关键:左右半边都是从大到小排好序的,i,j分别指向左右半边的开始,只需扫一遍就可以找出由两边各取一个数构成的逆序数。

总结:由归并排序改进得到,加上计算逆序的步骤。MergeSortAndCount:归并排序并计算逆序数。

在做之前,先来回顾一下分治的经典应用——归并排序的实现:

def merge_sort(l,start,end):
    if start>=end:
        return
    mid=start+(end-start)//2
    merge_sort(l,start,mid)
    merge_sort(l,mid+1,end)
    merge(l,start,mid,end)
def merge(l,start,mid,end):
    temp_l=[]
    i,j=start,mid+1
    while i<=mid and j<=end:
        if l[i]<l[j]:
            temp_l.append(l[i])
            i+=1
        else:
            temp_l.append(l[j])
            j+=1
    temp_l+=l[i:mid+1]
    temp_l+=l[j:end+1]
    l[start:end+1]=temp_l

求解代码:

import random
import time
n=20000
l=list(range(1,n+1))
random.shuffle(l)
def mergeSortAndCount(l,start,end):
    if start>=end:
        return 0
    mid=(start+end)//2
    a=mergeSortAndCount(l,start,mid)
    b=mergeSortAndCount(l,mid+1,end)
    i,j=start,mid+1
    c=0
    # 3 4 5 | 1 2 6
    while i<=mid and j<=end:
        while j<=end and l[i]>l[j]:
            c+=mid-i+1
            j+=1
        if j>end:
            break
        i+=1
    i,j=start,mid+1
    temp_l=[]
    while i<=mid and j<=end:
        if l[i]<l[j]:
            temp_l.append(l[i])
            i+=1
        else:
            temp_l.append(l[j])
            j+=1
    temp_l+=(l[i:mid+1])
    temp_l+=(l[j:end+1])
    l[start:end+1]=temp_l
    return a+b+c
def count_reversed(l):
    cnt=0
    for i in range(len(l)-1):
        for j in range(i+1,len(l)):
            if l[i]>l[j]:
                cnt+=1
    return cnt
a1=time.perf_counter()
a=count_reversed(l)
a2=time.perf_counter()
print(a,'tradition ->',a2-a1)
b1=time.perf_counter()
b=mergeSortAndCount(l,0,len(l)-1)
b2=time.perf_counter()
print(b,'merge ->',b2-b1)

输出:

99780046 tradition -> 23.969701731
99780046 merge -> 0.15094447199999905
[Finished in 24.7s]

解题感想:

1. 之前搞不清楚一点的就是假如在分治中把数组排序的话会不会对后面的结果有影响。实际选一组数据手动算一下执行过程,就会发现是不影响的。相对于归并排序,只是多了计算了函数返回值,并且在merge排序两部分之前,先在同样O(n)的时间内算出来两部分组成的逆序对的数量。也就是说,假如把数组l分为l1和l2两部分,尽管回溯到l时l1和l2是排好序的,但是l1和l2分别的逆序数已经求出来了,那么根据排好序的l1和l2求从这两个中各取一个组成的逆序数,只需要O(n)的时间,然后三部分相加,l的逆序数就有了。再计算l和另一个同级的数组组成的更大的两倍数组的总逆序数...
2. 观察结果可以发现,分治法真的比O(n^2)的传统两层循环高效太多,如果达到题中给的n=100000,那么传统方法根本算不出来,而merge对于100000的数据也只需要0.85秒。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值