问题:
考虑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的一个排列,求它的逆序数。
解题策略:
- 笨方法O(n^2)
- 分治O(nlogn)
分治的实现方法:
- 数组分为两半,分别求出左半边和右半边的逆序数
- 再算有多少逆序是由左半边取一个数和右半边取一个数构成(要求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秒。