浙江选考技术周五干货浅谈冒泡排序中的“交换次数”
Posted 浙江选考技术
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浙江选考技术周五干货浅谈冒泡排序中的“交换次数”相关的知识,希望对你有一定的参考价值。
最近出了道这样的选择题,拿来和大家分享,题意如下:
采用冒泡排序对数据序列“16,89,60,34,12,27,73”完成升序排序,则排序过程中总的交换次数是( ),其中数据34 参与交换的次数是( )。
结合上面两个问题,我就冒泡排序中的“交换次数”(以升序为例)和大家做一个简要探讨。
曾记得有人提过:“冒泡排序中的交换次数等于逆序对的个数”。这里,允许我先补充说明一下,上面排序处理,之所以我指定为升序排序,同样也是为便于下面我们探讨“逆序对”。
® 逆序对的定义
设有一个序列{a1,a2,a3,……,an-1,an},对于序列中任意两个元素ai,aj,若i<j,且ai>aj,则说明ai和aj是一对“逆序对”。
给定一组无重复的数据,如“73,28,54,29,78,19,35”,按冒泡升序处理,统计排序中的交换次数(我指定数据无重复原因,是避免你一定要说相同的元素也可能做交换)。
很明显,当冒泡升序处理完成后,升序列{a1 ≤a2≤a3≤ ⋯⋯ ≤an-1≤ an},因此,在该序列中“逆序对”的数量为0 对。
接下来,我将简要证明:冒泡排序过程中每1 次交换一定等同于“逆序对”减少1 对。
证明:
设冒泡排序过程中,当前数据状态如下:
a1、a2、a3、……、ai-1、{ai,ai+1}、ai+2……、an-1,an
上面数据序列中的逆序对总数,由下面三个部分组成:
{A}、{ai,ai+1}、{B}
由于冒泡排序是相邻元素进行比较,若ai≥ai+1且i<i+1,因此{ai,ai+1}构成逆序对。
1. 两数交换前的逆序对总数:
{A、B或A与B逆序对}+{A与ai或A与ai+1逆序对}+{B与ai或B与ai+1逆序对}+{ai+ai+1}逆序对
2. 当ai与ai+1交换后,逆序对总数:
{A、B或A与B逆序对}+{A与ai或 A与ai+1逆序对}+{B与ai或B与ai+1逆序对}
由上可知,1 次冒泡交换完成后,等同该序列中“逆序对”总次数减1。
又因为冒泡升序完成后“逆序对”的数量为0。因此,对冒泡升序而言,统计“逆序对”的总数量, 实质上等同于统计冒泡排序过程中“数据交换”的总次数。
根据上面分析及证明,要找出冒泡升序过程中发生的总交换次数,我们可以不需要进行冒泡模拟,实 际上只需要统计所有数据构成“逆序对”的总数即可。
给定一组无重复的数据,如“73,28,54,29,78,19,35”,按冒泡升序排序,统计在排序过程中,与数据“54”发生的交换次数。
证明:
首先,我们假设从当前数据序列中拿掉数据“54”,当冒泡升序处理完成后,得到升序序列{a1≤a2≤a3≤……≤an-2≤an-1},设该序列在冒泡升序过程中交换(或逆序对)次数为s 对。
接下来,我们将数据“54”放回原位置,设数据“54”与序列中其余数据构成逆序对的数量为k 对。
设冒泡排序过程中,当前数据状态如下:
a1、a2、a3、……、ai-1、{ai、54}、ai+2、an-1、an
将上面序列分组处理,则上面数据序列中的逆序对总数,由下面三部分组成:{A}、{ai、54}、{B}
1.根据前面假设,拿掉数据“54”后,即:
a1、a2、a3、……、ai-1、{ai}、ai+2……、an-1、an
即为: {A}、{ai}、{B} ,此时序列中逆序对的数量为s对 。
2. 由于冒泡排序是相邻元素进行比较,若ai≥ 54 且i<i+1,因此{ai、54}构成逆序对。下面,我们探讨一下,冒泡升序处理中,该序列“逆序对”的总数:
{A、B、A与B逆序对}+{A与ai或A与54逆序对}+{B与ai或B与54逆序对}+{ai、54}逆序对
等同于:
{A、B或A与B逆序对}+{A与ai}+{B与ai}+{序列中其余数据与数据54构成的逆序对}
即:{S+K}
由于,K={序列中其余数据与数据54构成的逆序对},所以,冒泡升序中计算数据“54”发生交换 的次数等同于数据“54”在该序列中构成逆序对的数量。
根据上面分析及证明,要找出与数据“54”发生的交换次数,我们可以不需要进行冒泡模拟,实际上 只需要统计与数据“54”构成逆序对的总数即可。
前面,我假设冒泡处理的数据序列中的元素均不重复,接下来,我们来探讨一下如何统计“逆序对”。
直接计算
★ 通过冒泡升序统计交换次数,完成“逆序对”的计算,代码如下:
a=[16,89,60,34,12,27,73]
n=len(a); num=0
for i in range(0,n-1): #[0..n-2]
for j in range(0, n-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+=1
print(num)
★ 直接对“逆序对”进行计算,代码如下:
a=[16,89,60,34,12,27,73]
n=len(a); num=0
for i in range(0,n-1): #[0..n-2]
for j in range(i+1, n): #j:[i+1..n-1]; i<j 且 a[i]>a[j]
if a[i]>a[j]:
num+=1
print(num)
上面两段“逆序对”处理代码,时间复杂度均为:0(n2)
归并及“逆序对”处理
若两个升序序列相邻,我们可以将两个序列归并成一个有序序列,归并同时快速计算出“逆序对”的 个数。“逆序对”的统计方法如下:
{ai ≤ ai+1 ≤ ai+2 ≤ ⋯⋯ ≤ an−1 ≤ an} {aj ≤ aj+1 ≤ aj+2 ≤ ⋯⋯ ≤ am−1 ≤ am}
归并时,每次只讨论两个序列中最前面的元素:ai , aj
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]*n
num=0
def merge(i0,j0,ln):
global num
i1=i0+ln; j1=j0+ln; i=i0; j=j0; k=i0
if i1>=n: # 说明后面有序段不存在,数组a[i0..n-1]有序,没有逆序对,因此不需要处理
return
if j1>=n: # 后面有序段元素个数不足ln个,因此设置为实际个数:n
j1=n
while i<i1 or j<j1:
# 1. 后面有序段数据取完:j>=j1 2. 后面有序段数据没有取完且前面数据存在,且前面数据更小
if j>=j1 or a[i]<=a[j] and i<i1:
b[k]=a[i]; i+=1; k+=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+=1; k+=1
num+=i0+ln-i
for i in range(i0,k):
a[i]=b[i]
ln=1
while ln<n:
for i in range(0,n,2*ln):
merge(i,i+ln,ln)
ln<<=1
print(num)
★递归处理(分治思想),由下到上归并
a=list(map(int,input().split()))#a=[16,89,60,34,12,27,73]
n=len(a)
b=[0]*n
num=0
def merge(L,mid,R): #将两个相邻的有序段区间进行归并:[L mid) 、[mid,R)
global num
i1=mid; j1=R; i=L; j=mid; k=L
if i1>=n:
return
if j1>n:
j1=n
while i<i1 or j<j1:
# 1.后面有序段取完:j>=j1 2.后面有序段数据没有取完且前面数据存在,且前面数据更小
if j>=j1 or a[i]<=a[j] and i<i1:
b[k]=a[i]; i+=1; k+=1
else: #2. 统计逆序对个数:[i..mid-1] [j], mid-1 -i +1= mid-i
b[k]=a[j]; j+=1; k+=1
num+=mid-i
for i in range(L,R):
a[i]=b[i]
def sort(L,R):
if L>=R-1:
return
mid=(L+R)//2
sort(L,mid)
sort(mid,R)
merge(L,mid,R)
def main():
sort(0,n)
print(num)
if __name__ == '__main__':
main()
a=list(map(int,input().split()))#a=[16,89,60,34,12,27,73]
#树状数组:利用前缀和思想快速对“逆序对”进行统计(区间中各元素值均小于Maxn:10000)
n=len(a); Maxn=10010
bit=[0]*Maxn # 数组bit[]为统计区间前缀和提供便利
num=0
# 统计的区间是[0..x],在数据x前面且≤x的元素个数,可看成是x的“顺序对”个数。
# 此时a[i],即x并未输入,我们统计的是前面输入的数据当中<a[i]的数据个数:ans
def sum(x):
ans=0
while x>0:
ans+=bit[x]
x-=x & -x
return ans
# 数据a[i]对区间后面的数据而言将产生影响,因为数据a[i]出现在后面输入数据的前面,区间[a[i]..Maxn]
def add(i,x):
while i<Maxn:
bit[i]+=x
i+=i& -i
def main():
res=0
for i 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]数据而言,统计前缀和时,其对应个数将增1
print("%d" %res)
if __name__ == '__main__':
main()
树状数组在建树同时完成了各个数据点“逆序对”的统计。由于数状数组单点更新、求区间前缀和算法效率极高,时间复杂度≤ ܱ(݈݃݊),实际上与当前处理数据二进制数据位中1 的个数相关,上面代码法时间复杂度≤0(n*logn),上限为:0(n*logn)
- 朱一帆老师免费视频课程 -
1、Flash按钮元件题:
2、约瑟夫环问题:
3、VB随机数和逻辑推断专题:
4、电路分析(圈圈图):
5、三视图补线题(第三种方法):
6、控制方式(开闭环):
7、尺寸标注(漏标):
8、受力形式分析:
9、密码锁电路中电容充放电过程分析:
10、宁波十校部分试题讲解:
11、温州二模部分试题讲解:
QQ学习群:573423309
朱老师微信:zhuyifan014
别忘了点 “在看” 哦 以上是关于浙江选考技术周五干货浅谈冒泡排序中的“交换次数”的主要内容,如果未能解决你的问题,请参考以下文章