浙江选考技术周五干货浅谈冒泡排序中的“交换次数”

Posted 浙江选考技术

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浙江选考技术周五干货浅谈冒泡排序中的“交换次数”相关的知识,希望对你有一定的参考价值。

最近出了道这样的选择题,拿来和大家分享,题意如下:

采用冒泡排序对数据序列16,89,60,34,12,27,73完成升序排序,则排序过程中总的交换次数是( ),其中数据34 参与交换的次数是( )。 

结合上面两个问题,我就冒泡排序中的“交换次数”(以升序为例)和大家做一个简要探讨。

曾记得有人提过:“冒泡排序中的交换次数等于逆序对的个数”。这里,允许我先补充说明一下,上面排序处理,之所以我指定为升序排序,同样也是为便于下面我们探讨“逆序对”。

01

交换次数与逆序对

® 逆序对的定义 

设有一个序列{a1,a2,a3,……,an-1an},对于序列中任意两个元素aiaji<j,ai>aj则说明aiaj是一对“逆序对”。 

问题1:

给定一组无重复的数据,如“73,28,54,29,78,19,35”,按冒泡升序处理,统计排序中的交换次数(我指定数据无重复原因,是避免你一定要说相同的元素也可能做交换)。 

很明显,当冒泡升序处理完成后,升序列{a1 a2a3≤ ⋯⋯ ≤an-1≤ an},因此,在该序列中“逆序对”的数量为

接下来,我将简要证明:冒泡排序过程中次交换一定等同于“逆序对”减少

证明:

设冒泡排序过程中,当前数据状态如下:

a1、a2a3、……、ai-1{ai,ai+1}ai+2……、an-1,an 

上面数据序列中的逆序对总数,由下面三个部分组成: 

{A}{ai,ai+1}{B}

由于冒泡排序是相邻元素进行比较,若aiai+1i<i+1,因此{ai,ai+1}构成逆序对。


1. 两数交换前的逆序对总数: 

{A、B或A与B逆序对}+{A与ai或A与ai+1逆序对}+{B与ai或B与ai+1逆序对}+{ai+ai+1}逆序对 

2. aiai+1交换后,逆序对总数: 

{A、B或A与B逆序对}+{A与ai或 A与ai+1逆序对}+{B与ai或B与ai+1逆序对} 


由上可知,次冒泡交换完成后,等同该序列中“逆序对”总次数减1。 

又因为冒泡升序完成后“逆序对”的数量为0。因此,对冒泡升序而言,统计“逆序对”的总数量, 实质上等同于统计冒泡排序过程中“数据交换”的总次数。 

根据上面分析及证明,要找出冒泡升序过程中发生的总交换次数,我们可以不需要进行冒泡模拟,实 际上只需要统计所有数据构成“逆序对”的总数即可。 

问题 2:

给定一组无重复的数据,如“73,28,54,29,78,19,35”,按冒泡升序排序,统计在排序过程中,与数据“54”发生的交换次数。

证明:

首先,我们假设从当前数据序列中拿掉数据“54”,当冒泡升序处理完成后,得到升序序列{a1a2a3……an-2an-1},设该序列在冒泡升序过程中交换(或逆序对)次数为s 。 

接下来,我们将数据“54”放回原位置,设数据“54”与序列中其余数据构成逆序对的数量为。 


设冒泡排序过程中,当前数据状态如下:

a1、a2a3、……、ai-1、{ai54}ai+2、an-1an

将上面序列分组处理,则上面数据序列中的逆序对总数,由下面三部分组成:{A}{ai54}{B}


1.根据前面假设,拿掉数据“54”后,即:

a1、a2a3、……、ai-1、{ai}ai+2……、an-1、an

即为: {A}{ai}{B} ,此时序列中逆序对的数量为s 。 


2. 由于冒泡排序是相邻元素进行比较,若ai≥ 54 i<i+1,因此{ai54}构成逆序对。下面,我们探讨一下,冒泡升序处理中,该序列“逆序对”的总数: 

{A、B、A与B逆序对}+{A与ai或A与54逆序对}+{B与ai或B与54逆序对}+{ai54}逆序对 

等同于:

{A、B或A与B逆序对}+{A与ai}+{B与ai}+{序列中其余数据与数据54构成的逆序对} 

即:{S+K} 

由于,K={序列中其余数据与数据54构成的逆序对},所以,冒泡升序中计算数据“54”发生交换 的次数等同于数据54”在该序列中构成逆序对的数量。 

根据上面分析及证明,要找出与数据“54”发生的交换次数,我们可以不需要进行冒泡模拟,实际上 只需要统计与数据“54”构成逆序对的总数即可。 

02

统计“逆序对”


前面,我假设冒泡处理的数据序列中的元素均不重复,接下来,我们来探讨一下如何统计“逆序对”。 

1

直接计算


★ 通过冒泡升序统计交换次数,完成“逆序对”的计算,代码如下: 

a=[16,89,60,34,12,27,73

n=len(a); num=

for in range(0,n-1): #[0..n-2] 

for in range(0n-i-1): #i:0,j:[0..n-2] 

if a[j]>a[j+1]: 

a[j],a[j+1]=a[j+1],a[j

num+=

print(num


★ 直接对“逆序对”进行计算,代码如下: 

a=[16,89,60,34,12,27,73

n=len(a); num=

for in range(0,n-1): #[0..n-2]

 for in range(i+1n): #j:[i+1..n-1]; i<j 且 a[i]>a[j] 

if a[i]>a[j]: 

num+=

print(num

上面两段“逆序对”处理代码,时间复杂度均为:0(n2

2

归并及“逆序对”处理


若两个升序序列相邻,我们可以将两个序列归并成一个有序序列,归并同时快速计算出“逆序对”的 个数。“逆序对”的统计方法如下: 

{ai ≤ ai+1 ≤ ai+2 ≤ ⋯⋯ ≤ an−1 ≤ an} {aj ≤ aj+1 ≤ aj+2 ≤ ⋯⋯ ≤ am−1 ≤ am

归并时,每次只讨论两个序列中最前面的元素:ai , a

1、ai ≤ aj 将数据ai进行归入,很明显数据ai比当前两个序列中任何一个元素均小,因此不可能与 序列中其他数据构成“逆序对”。 

2、ai >aj 将数据aj进行归入,很明显数据aj比前面序列中任何一个元素均小,所以可以与前面序列中所有数据构成“逆序对”,“逆序对”的个数为:n-i+1。 

下面代码中,我借助了二路归并思想,利用有序段特征在数据归并同时完成“逆序对”的统计,算法时间复杂度均为:0(n*logn)

★非递归方式,由下到上归并处理

a=list(map(int,input().split()))#a=[16,89,60,34,12,27,73] 

n=len(a

b=[0]*

num=

def merge(i0,j0,ln): 

global num

i1=i0+lnj1=j0+lni=i0j=j0k=i0 

if i1>=n说明后面有序段不存在,数组a[i0..n-1]有序,没有逆序对,因此不需要处理

return 

if j1>=n后面有序段元素个数不足ln,因此设置为实际个数:n 

j1=


while i<i1 or j<j1

# 1. 后面有序段数据取完:j>=j1 2. 后面有序段数据没有取完且前面数据存在,且前面数据更小 

if j>=j1 or a[i]<=a[jand i<i1

b[k]=a[i]; i+=1k+=1

else#2. a[j]与前面有序段中所有数据构成逆序对(a[i..i0+ln-1], a[j])i0+ln-1 -i +1= i0+ln-i

b[k]=a[j]; j+=1k+=1

num+=i0+ln-

for i in range(i0,k): 

a[i]=b[i

ln=1

while ln<n

for in range(0,n,2*ln): 

merge(i,i+ln,ln

ln<<=

print(num)


★递归处理(分治思想),由下到上归

a=list(map(int,input().split()))#a=[16,89,60,34,12,27,73] 

n=len(a

b=[0]*

num=

def merge(L,mid,R): #将两个相邻的有序段区间进行归并:[L mid) [mid,R) 

global num 

i1=midj1=Ri=Lj=midk=

if i1>=n

return 

if j1>n

j1=

while i<i1 or j<j1

# 1.后面有序段取完:j>=j1 2.后面有序段数据没有取完且前面数据存在,且前面数据更小 

if j>=j1 or a[i]<=a[jand i<i1

b[k]=a[i]; i+=1k+=

else#2. 统计逆序对个数:[i..mid-1] [j], mid-1 -i +1= mid-i 

b[k]=a[j]; j+=1k+=

num+=mid-

for in range(L,R): 

a[i]=b[i


def sort(L,R): 

if L>=R-1

return 

mid=(L+R)//

sort(L,mid

sort(mid,R

merge(L,mid,R


def main(): 

sort(0,n

print(num


if __name__ == '__main__'

main()


03

树状数组处理“逆序对”

a=list(map(int,input().split()))#a=[16,89,60,34,12,27,73] 

#树状数组:利用前缀和思想快速对“逆序对”进行统计(区间中各元素值均小于Maxn10000) 

n=len(a); Maxn=10010 

bit=[0]*Maxn 数组bit[]为统计区间前缀和提供便利 

num=


统计的区间是[0..x],在数据x前面且x的元素个数,可看成是x的“顺序对”个数。

此时a[i],x并未输入,我们统计的是前面输入的数据当中<a[i]的数据个数:ans 

def sum(x): 

ans=

while x>0

ans+=bit[x

x-=& -

return ans 


数据a[i]对区间后面的数据而言将产生影响,因为数据a[i]出现在后面输入数据的前面,区间[a[i]..Maxn] 

def add(i,x): 

while i<Maxn

bit[i]+=

i+=i& -


def main(): 

res=

for in range(n): 

# sum(a[i])统计的是区间[0..i],<a[i]的“顺序对”个数,a[i]的逆序对=总数据对-顺序对:i+1-1 -sum(a[i]) 

res+=(i-sum(a[i])) 

add(a[i],1#单点更新,a[i]出现对于后面输入x>a[i]数据而言,统计前缀和时,其对应个数将增


print("%d" %res

if __name__ == '__main__'

main() 

树状数组在建树同时完成了各个数据点“逆序对”的统计。由于数状数组单点更新、求区间前缀和算法效率极高,时间复杂度≤ ܱ(݈݋݃݊),实际上与当前处理数据二进制数据位中的个数相关,上面代码法时间复杂度≤0(n*logn),上限为:0(n*logn) 











- 朱一帆老师免费视频课程 -


1、Flash按钮元件题:

2、约瑟夫环问题:

3、VB随机数和逻辑推断专题:

4、电路分析(圈圈图):

5、三视图补线题(第三种方法):

6、控制方式(开闭环):

7、尺寸标注(漏标):

8、受力形式分析:

9、密码锁电路中电容充放电过程分析:

10、宁波十校部分试题讲解:

11、温州二模部分试题讲解:



QQ学习群:573423309

朱老师微信:zhuyifan014



 别忘了点 “在看” 哦 

以上是关于浙江选考技术周五干货浅谈冒泡排序中的“交换次数”的主要内容,如果未能解决你的问题,请参考以下文章

关于浙江信息技术选考中二分查找的变式

冒泡排序的交换次数

冒泡排序的交换次数 (树状数组)

1,2,3,4,5,6,7最小比较和交换次数的排序次序为?

冒泡排序

冒泡排序算法导学案