【逆序数】哎呀为什么会有人想用QuickSort求逆序数嘛!

本文探讨了如何利用快速排序算法高效地计算一个序列的逆序数,并对比了归并排序的方法,最终给出了一个简洁高效的实现方案。

(这篇文章底端的图为什么这么大……不管了)

[--大家好我们第一个团本CD就通了PT而且打掉了H老一呢,看不懂这行的请当它不存在--]

事情,大概是这样的—— (没错这又是一篇我被作业算法血虐的心路历程大水文)

哦对了,得先解释一下,逆序数这东西呢,可以理解为冒泡排序的过程中,bubble一次算一次逆序,全部排序完毕之后bubble了多少次,那就是逆序数是多少。

官方一点的解释呢就是:

“对于n个不同的元素,先规定各元素之间有一个标准次序(例如n个 不同的自然数,可规定从小到大为标准次序),于是在这n个元素的任一排列中,当某两个元素的先后次序与标准次序不同时,就说有1个逆序。一个排列中所有逆序总数叫做这个排列的逆序数。”

哎呀求逆序数,开心,惬意,归并走起,刷刷刷——

class SortAndCount_Merge():
    def __init__(self):
        self.inList = []
        
    def mergeAndCount(self, L, R):
        RC, i, j = 0, 0, 0
        ret = []
        for k in range(len(L) + len(R)):
            if i == len(L) or j == len(R):
                ret += L[i:] + R[j:]
                break
            elif L[i] > R[j]:
                ret.append(R[j])
                RC += len(L) - i
                # The Same as:
                # RC += (len(L) + len(R))/2 - i
                j += 1
            else :
                ret.append(L[i])
                i += 1
        return (RC, ret)

    def sortAndCount(self, A):
        if len(A) < 2: return (0, A)
        mid = len(A) / 2 
        L,R = A[:mid],A[mid:]
        RC_L, L = self.sortAndCount(L)
        RC_R, R = self.sortAndCount(R)
        # There can be a better method without recursive
        # Mark for advanced
        cnt, ret = self.mergeAndCount(L, R)
        cnt += RC_L + RC_R
        return (cnt, ret)

然后,悲伤如我,发现了题意是要求使用【快速排序】来求…… 唔,咱们用冒泡排序+插入排序+树状数组各求一次行不,快排这么伤脑子的事情能不能就不做了?QvQ

不能。

哦……

class SortAndCount_QSort():
    def __init__(self, inList):
        self.A = inList
        self.cnt = 0
    
    def swap(self, pos1, pos2):
        l,r = min(pos1, pos2), max(pos1, pos2)
        self.cnt += (r - l)
        
        tmp = self.A[l]
        self.A[l] = self.A[r]
        self.A[r] = tmp

    def sortAndCount(self, lef, rig):
        if lef >= rig: return 
        pivot = lef
		for pos in xrange(lef+1, rig+1):
            if self.A[pos] < self.A[lef]:
                pivot += 1
                self.swap(pivot, pos)
        self.swap(lef, pivot)
        self.sortAndCount(lef, pivot-1)
        self.sortAndCount(pivot+1, rig)
        return (self.cnt, self.A)

于是心思缜密的我去对照了一下两个算法的答案……

然后把这段注释掉了QvQ

""" There will be extra counts without modified-method. 
for pos in xrange(lef+1, rig+1):
	if self.A[pos] < self.A[lef]:
		pivot += 1
		self.swap(pivot, pos)
self.swap(lef, pivot)
""" #( counts QSORT:2502239417 > MERGE:2500572073 )


头疼,能不能分成以pivot为界线,分成 “左边的到了左边”,“左边的到了右边”,“右边的到了左边”和“右边的到了右边”来考虑呢,然后我就写了这么个可怕的东西——

然后……鄙人就是不服,可以的——

class SortAndCount_QSort():
    def __init__(self, inList):
        self.A = inList
        self.cnt = 0
    
    def swap(self, pos1, pos2):
        l,r = min(pos1, pos2), max(pos1, pos2)
        # self.cnt += (r - l)
        
        tmp = self.A[l]
        self.A[l] = self.A[r]
        self.A[r] = tmp
    
    def addPartCnt(self, pivot, dir, sig):  
        ins, insp, crs, crsp = 0, [], 0, [] # inside/cross part
        for idx in xrange( pivot + sig, dir + sig, sig ):
            if self.A[ins] < self.A[pivot]:
                insp.append(self.A[idx])
                ins += 1
            else:
                crsp.append(self.A[idx])
                crs += 1
                self.cnt += ins + 1 
        return insp, crsp, crs
        
    """ Simplified to addPartCnt() '''
    def addLefCnt(self, lef):
        lposl, L2L, lposr, L2R = 0, [], 0, []
        for idx in xrange(pivot-1, lef-1, -1):
            if self.A[lposl] < self.A[pivot]:
                L2L.append(self.A[idx])
                lposl += 1
            else:
                L2R.append(self.A[idx])
                lposr += 1
                self.cnt += lposl + 1 
        return L2L, L2R, lposr
    
    def addRigCnt(self, rig):
        rposr, R2R, rposl, R2L = 0, [], 0, []
        for idx in xrange(pivot+1, rig+1, +1):
            if self.A[rposr] > self.A[pivot]:
                R2R.append(self.A[idx])
                rposr += 1
            else:
                R2L.append(self.A[idx])
                rposl += 1
                self.cnt += rposr + 1 
        return R2R, R2L, rposl
    """
    
    def mergeAndCount(self, pivot, lef, rig):
        if not lef <= pivot <= rig: return
        ll, lr = [], []
        crsL2R, crsR2L = 0, 0
        if lef < pivot : ll, lr, crsL2R = self.addPartCnt(pivot, lef, -1) # addLefCnt()
        if rig > pivot : rr, rl, crsR2L = self.addPartCnt(pivot, rig, +1) # addRigCnt()
        ll.reverse()
        lr.reverse()
        self.cnt += crsL2R * crsR2L
        ret = ll + rl + [self.A[pivot]] + lr + rr
        if lef != 0: ret = self.A[:lef] + ret
        if rig != self.A.__len__()-1: ret = ret + self.A[rig+1:]
        self.A = ret
        
    def sortAndCount(self, lef, rig):
        if lef >= rig: return 
        pivot = lef
        self.mergeAndCount(pivot, lef, rig)
        self.sortAndCount(lef, pivot-1)
        self.sortAndCount(pivot+1, rig)
        return (self.cnt, self.A)

看呐我还发现了那两段是一样的,还合并成了一个函数是不是很聪明,是不是可以加分!

刚好 @ZoeCUR 来问我这道题,我三五句话给她讲懂之后,她瞬间表示了解,算法GET,三分钟后,“这个还是简单,不就几行的事情么?” WHAT?!

经夫人一番指点果然豁然开朗……

然后我发现了……原来这就是一个……线性的……写出来只要16行的……代码……

对不起是我的错,我想多了,对不起人民对不起国家:(没错前面都是废话都是废代码亲爱的读者你发现了吗?)


好的其实就是个线性的这么简单的东西QwQ—— (听说作业被抄袭也要算0分,这段先隐藏一下等作业提交截止了再发不好意思~)

class SortAndCount_QSort():
    def __init__(self):
        self.cnt = 0
    
    def swap(self, pos1, pos2):
        l,r = min(pos1, pos2), max(pos1, pos2)
        # self.cnt += (r - l)
        
        tmp = self.A[l]
        self.A[l] = self.A[r]
        self.A[r] = tmp
   
    def sortAndCount(self, inList):
        c, L, R = 0, [], []
        if inList.__len__() <= 1 : return c, inList

        for idx in xrange(1, inList.__len__()) :
            if(inList[idx] < inList[0]):
                c += idx - 1 - L.__len__()
                L.append(inList[idx])
            else: R.append(inList[idx])
        c += L.__len__()
        lcnt, L = self.sortAndCount(L)
        rcnt, R = self.sortAndCount(R)
        return lcnt + c + rcnt, L + [inList[0]] + R
        
        """ There will be extra counts without modified-method. 
        for pos in xrange(lef+1, rig+1):
            if self.A[pos] < self.A[lef]:
                pivot += 1
                self.swap(pivot, pos)
        self.swap(lef, pivot)
        """ #( counts QSORT:2502239417 > MERGE:2500572073 )


输出结果:

E:\UCAS\计算机算法设计与分析\Homework\091M4041H - Assignment1_DandC>python A08.py

[ANSWER]  Merge Version : 1.21399998665 sec.
the number of inversions in Q8.txt is:  2500572073
Check Completed: List Sorted.

[ANSWER]  Qsort Version : 0.953000068665 sec.
the number of inversions in Q8.txt is:  2500572073
Check Completed: List Sorted.

唔,为了伸手党们,感觉该有的还是得有……像什么Main函数啊,读入输出啥的也贴一下好啦~

def readFile(filename):
    with open(filename,'r') as f:
        inList = [int(x) for x in f.readlines()]
        return inList

def check(A):
    a, b = A, xrange(1,100001)
    for pos in b:
        if a[pos-1] != pos:
            return "Unmatch at", pos
    return "Check Completed: List Sorted."
    
def printAnswer(mode, t, filename, cnt, ret):
    print "\n[ANSWER] ", mode, "Version :", t, "sec."
    print "the number of inversions in", filename, "is: ", cnt
    print check(ret) #,"\nList:\n",ret
        
if __name__ == "__main__":
    filename = "Q8.txt"
    inList   = readFile(filename)
    capacity = len(inList)
    
    t, SacM  = time.time(), SortAndCount_Merge()
    cnt, ret = SacM.sortAndCount(inList)
    printAnswer("Merge", time.time() - t, filename, cnt, ret)
    
    t, SacQ  = time.time(), SortAndCount_QSort()
    cnt, ret = SacQ.sortAndCount(inList)
    printAnswer("Qsort", time.time() - t, filename, cnt, ret)




事情,大概是这样的——

哦对了,得先解释一下,逆序数这东西呢,可以理解为冒泡排序的过程中,bubble一次算一次逆序,全部排序完毕之后bubble了多少次,那就是逆序数是多少。

官方一点的解释呢就是:

“对于n个不同的元素,先规定各元素之间有一个标准次序(例如n个 不同的自然数,可规定从小到大为标准次序),于是在这n个元素的任一排列中,当某两个元素的先后次序与标准次序不同时,就说有1个逆序。一个排列中所有逆序总数叫做这个排列的逆序数。”


<think>好的,我需要解决用户关于Ultra-QuickSort算法的问题,包括如何实现和解释它。首先,我得先理解这个算法的本质。根据用户提供的引用,Ultra-QuickSort实际上是通过交换相邻元素来排序,而问题是要计算需要多少次这样的交换操作。听起来这类似于计算逆序对的数量,因为每次交换相邻元素只能消除一个逆序对。 接下来,我需要回忆一下相关的算法。通常,计算逆序对的高效方法有归并排序和树状数组。用户提到的引用[3]中提到了归并排序,这可能是一个常用的方法。归并排序在分治的过程中可以统计逆序对的数量,具体来说,在合并两个有序子数组时,如果左边的元素大于右边的元素,那么左边的剩余元素都会与这个右边的元素形成逆序对,这样就能统计出数量。 那么,如何将归并排序应用于这个问题呢?首先,归并排序会将数组分成两半,递归地对左右两部分排序,并统计各自的逆序对数目。然后在合并过程中,当左边的元素大于右边的元素时,就需要计算左边剩余元素的数量,并将这个数量加到总的逆序对数目中。这样,总的逆序对数目就是整个数组需要交换的次数,也就是Ultra-QuickSort所需的swap操作次数。 接下来,我需要考虑具体的实现步骤。例如,在归并排序的合并阶段,如何正确统计逆序对的数量。假设有一个数组arr,分成左半部分和右半部分,分别排序后合并。当左半部分的当前元素大于右半部分的当前元素时,说明存在逆序对,此时逆序对的数量应该是左半部分剩余元素的数量,因为左半部分已经是有序的,所有剩余元素都会比当前右半部分的元素大。 另外,需要考虑递归的终止条件,即当数组长度为1时,逆序对数量为0。然后,在递归返回时,将左右子数组的逆序对数目加上合并过程中产生的数目,得到总的数目。 测试一下这个思路是否正确。例如,对于输入序列9 1 0 5 4,正确的输出是交换次数为6。那么用归并排序计算逆序对是否得到6?让我手动计算一下。原始序列是9,1,0,5,4。分解后的逆序对可能有:9与1、0、5、4;1与0;5与4。总共有6个逆序对,确实如此。因此,这个方法是正确的。 接下来,我需要编写代码实现这个算法。用Python的话,可以定义一个递归函数,返回排序后的数组和逆序对的数量。在合并过程中,使用双指针遍历左右两个子数组,比较元素大小,并按顺序合并到临时数组中。每当左指针的元素大于右指针的元素时,就将右指针的元素放入临时数组,并增加逆序对的数量,增加的数量为左子数组剩余元素的个数。 需要注意的是,归并排序的实现中,临时数组的处理可能会影响空间复杂度,但这里问题规模应该可以接受。此外,Python中处理大数组时需要注意递归深度,不过题目中的例子应该不会有问题。 最后,验证代码的正确性。用给出的测试用例9 1 0 5 4,应该输出6。再比如,对于已经排序的数组,逆序对数目为0。另一个例子,比如3,2,1,应该有3个逆序对,交换次数为3。 总结一下,Ultra-QuickSort的swap次数等于数组中的逆序对数目,而归并排序是一个有效的方法来计算这个数目。因此,实现的关键在于正确修改归并排序以统计逆序对的数量。</think>### Ultra-QuickSort算法解析与实现 Ultra-QuickSort的核心是**通过交换相邻元素消除逆序对**,其最小交换次数等于数组中逆序对的总数。例如输入序列$9\ 1\ 0\ 5\ 4$需要6次交换[^1][^3]。 #### 算法原理 1. **逆序对定义**:若$i < j$且$a_i > a_j$,则$(a_i,a_j)$构成逆序对 2. **等价关系**:每次相邻交换最多消除一个逆序对 3. **最优性证明**:最小交换次数=逆序对总数 #### 实现方法(归并排序) ```python def count_inversions(arr): if len(arr) <= 1: return arr, 0 mid = len(arr) // 2 left, inv_left = count_inversions(arr[:mid]) right, inv_right = count_inversions(arr[mid:]) merged = [] inv_count = inv_left + inv_right i = j = 0 while i < len(left) and j < len(right): if left[i] <= right[j]: merged.append(left[i]) i += 1 else: merged.append(right[j]) j += 1 inv_count += len(left) - i merged += left[i:] merged += right[j:] return merged, inv_count # 示例用法 arr = [9, 1, 0, 5, 4] _, swaps = count_inversions(arr) print(f"需要最少交换次数:{swaps}") # 输出6 ``` #### 代码解析 1. **递归分治**:将数组不断二分至单个元素 2. **合并统计**:合并有序子数组时计算跨区间的逆序对 3. **时间复杂度**:$O(n \log n)$,空间复杂度$O(n)$
评论 22
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

糖果天王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值