确定某个分两段的组合在遍历集中的序号
前面介绍过 某个组合在组合遍历集中的序号 的求法。
其问题是求一个序列中某个组合的在遍历集中的序号。
比如在1-10的序列中,[2,4,5,6]组合在所有组合(这是一个从[1,2,3,4]到…到[7,8,9,10]的集合,集合大小为binom(10,4)=210)中序号。
显然这种组合是一次性的(一个阶段),若组合是分阶段的,比如是分两段的[(2,4),(5,6)],那么总的组合数会变为原来的6倍,即binom(10,4)*binom(10,4)=1260
。
那么这个分两段的组合[(2,4),(5,6)]在1260个全部组合的集合中的序号是多少呢?
这篇短文来介绍其解法
1. 方法
其实序号的计算与遍历的方式是有关的,所以我们要确定组合的序号,需要考虑组合遍历的方式。
在一个阶段的组合中,我们用的是字典序,即从组合的最后一个数开始变化(后一个变化完了再变化前一个),比如1-10序列中4个数的组合,是从[1,2,3,4],[1,2,3,5],[1,2,3,6],这样的顺序开始的,直到最后的[7,8,9,10]。
在两个阶段的组合中,我们分两个阶段遍历,两个阶段都采用字典序,即前一个阶段的组合在全部的序列中以字典序排序,后一个阶段序列则在全部序列除去前一阶段的组合中数的序列中以字典项排序。
比如1-10序列中4个数的分前后各两个数阶段的组合,那么顺序应该是这样的:
i= 1 [(1, 2), (3, 4)] id= 1.0
i= 2 [(1, 2), (3, 5)] id= 2.0
i= 3 [(1, 2), (3, 6)] id= 3.0
i= 4 [(1, 2), (3, 7)] id= 4.0
i= 5 [(1, 2), (3, 8)] id= 5.0
i= 6 [(1, 2), (3, 9)] id= 6.0
i= 7 [(1, 2), (3, 10)] id= 7.0
i= 8 [(1, 2), (4, 5)] id= 8.0
i= 9 [(1, 2), (4, 6)] id= 9.0
i= 10 [(1, 2), (4, 7)] id= 10.0
i= 11 [(1, 2), (4, 8)] id= 11.0
i= 12 [(1, 2), (4, 9)] id= 12.0
i= 13 [(1, 2), (4, 10)] id= 13.0
i= 14 [(1, 2), (5, 6)] id= 14.0
i= 15 [(1, 2), (5, 7)] id= 15.0
i= 16 [(1, 2), (5, 8)] id= 16.0
i= 17 [(1, 2), (5, 9)] id= 17.0
i= 18 [(1, 2), (5, 10)] id= 18.0
i= 19 [(1, 2), (6, 7)] id= 19.0
i= 20 [(1, 2), (6, 8)] id= 20.0
i= 21 [(1, 2), (6, 9)] id= 21.0
i= 22 [(1, 2), (6, 10)] id= 22.0
i= 23 [(1, 2), (7, 8)] id= 23.0
i= 24 [(1, 2), (7, 9)] id= 24.0
i= 25 [(1, 2), (7, 10)] id= 25.0
i= 26 [(1, 2), (8, 9)] id= 26.0
i= 27 [(1, 2), (8, 10)] id= 27.0
i= 28 [(1, 2), (9, 10)] id= 28.0
i= 29 [(1, 3), (2, 4)] id= 29.0
i= 30 [(1, 3), (2, 5)] id= 30.0
i= 31 [(1, 3), (2, 6)] id= 31.0
i= 32 [(1, 3), (2, 7)] id= 32.0
i= 33 [(1, 3), (2, 8)] id= 33.0
i= 34 [(1, 3), (2, 9)] id= 34.0
i= 35 [(1, 3), (2, 10)] id= 35.0
....
....
注意到:组合[(1, 2), (3, 4)],[(1, 3), (2, 4)],[(1, 4), (2, 3)],
[(2, 3), (1, 4)],[(2, 4), (1, 3)],[(3, 4), (1, 1)]。这6中两阶段的组合是不同的,其若是一个阶段的组合则是相同的。
因为是两个阶段都用字典序,所以我们可以用两个阶段的方式计算,每个阶段的计算都采用前面介绍的单阶段组合的序号确定方法。
2. 实现
具体来说,我们可以用一个函数组合两个阶段的序号查询,比如:
#获取分两段的组合在所有组合遍历中的id,id从1开始计数
#第一个参数是组合,第二个参数是组合的第一段的长度,第三和四参数为序列中的起点和终点
def getPieceCombId(comb1=[4,7,6,8],flen=2,datastt=1,dataend=10):
fstcds=comb1[:flen]
ifst=getCombinationId(comb1[:flen],datastt,dataend)
nsec=Cmn(dataend-datastt+1-flen,len(comb1)-flen)
#得到的第一个分段当前组合前的所有组合的对应的所有两段组合数量
idbase=nsec*(ifst-1)
#根据第二个分段的内容确定其当前第一分段下在第二个分段所有中的位置
seccomb=comb1[flen:]
restcds=[x for x in range(datastt,dataend+1) if x not in fstcds]
comb2=[restcds.index(x) for x in seccomb]
idsec=getCombinationId(comb2,0,len(restcds)-1)
id=idbase+idsec
#print('idbase=',idbase,'idsec=',idsec)
return id
即:先用一个单阶段的序号查询确定当前两阶段组合第一个分段组合前的所有两阶段组合数,然后确定第二个分段在当前两阶段组合中序号,从而获得完整的序号。
3. 测试
测试代码如下(注意测试用一个遍历的函数实现,是固定的。但求序号的函数是根据输入参数确定的):
#!/usr/bin/env python3
#_*_coding: utf-8 _*_
#遍历分两段的组合
#第一个参数是组合的总长度,第二个参数是组合的第一段的长度
def enumPieceComb(tlen=4,flen=2,datastt=1,dataend=10):
i=0
for i1 in range(datastt,dataend+1):
for i2 in range(i1+1,dataend+1):
fstcds=[i1,i2]
restcds=[x for x in range(datastt,dataend+1) if x not in fstcds]
for i3 in range(dataend-datastt+1-flen):
for i4 in range(i3+1,dataend-datastt+1-flen):
print('')
i+=1
print('i=',i,[(i1,i2),(restcds[i3],restcds[i4])],' id=',getPieceCombId([i1,i2,restcds[i3],restcds[i4]],flen,datastt,dataend))
#print('input anykey to continue')
#anykey=input()
return None
def enumPieceComb1(tlen=3,flen=2,datastt=1,dataend=10):
i=0
for i1 in range(datastt,dataend+1):
for i2 in range(i1+1,dataend+1):
fstcds=[i1,i2]
restcds=[x for x in range(datastt,dataend+1) if x not in fstcds]
for i3 in range(dataend-datastt+1-flen):
print('')
i+=1
print('i=',i,[(i1,i2),(restcds[i3])],' id=',getPieceCombId([i1,i2,restcds[i3]],flen,datastt,dataend))
#print('input anykey to continue')
#anykey=input()
return None
def enumPieceComb2(tlen=5,flen=3,datastt=1,dataend=10):
i=0
for i1 in range(datastt,dataend+1):
for i2 in range(i1+1,dataend+1):
for i5 in range(i2+1,dataend+1):
fstcds=[i1,i2,i5]
restcds=[x for x in range(datastt,dataend+1) if x not in fstcds]
for i3 in range(dataend-datastt+1-flen):
for i4 in range(i3+1,dataend-datastt+1-flen):
print('')
i+=1
print('i=',i,[(i1,i2,i5),(restcds[i3],restcds[i4])],' id=',getPieceCombId([i1,i2,i5,restcds[i3],restcds[i4]],flen,datastt,dataend))
#print('input anykey to continue')
#anykey=input()
return None
#Cmn,求组合数
def Cmn(n,m):
if m==0 : return 1
if n==0 : return 1
numer=1 #numerator
denom=1 #denominator
if m<n/2:
for i in range(n-m+1,n+1):
numer*=i
for i in range(1,m+1):
denom*=i
else:
for i in range(m+1,n+1):
numer*=i
for i in range(1,n-m+1):
denom*=i
return (numer/denom)
def getCombinationId(comb1=[4,7,8], datastt=1,dataend=10):
comb=sorted(comb1)
for i in comb:
if i not in range(datastt,dataend+1):
return -1
m=len(comb)
comblst=[datastt-1]+comb #+[dataend]
#print('comblst',comblst)
id=1
for k in range(m):
for i in range(comblst[k]+1,comblst[k+1]):
#print('k=',k,' i=',i)
id+=Cmn(dataend-i,m-1-k)
#print('id=',id)
return id
#获取分两段的组合在所有组合遍历中的id,id从1开始计数
#第一个参数是组合,第二个参数是组合的第一段的长度,第三和四参数为序列中的起点和终点
def getPieceCombId(comb1=[4,7,6,8],flen=2,datastt=1,dataend=10):
fstcds=comb1[:flen]
ifst=getCombinationId(comb1[:flen],datastt,dataend)
nsec=Cmn(dataend-datastt+1-flen,len(comb1)-flen)
#得到的第一个分段当前组合前的所有组合的对应的所有两段组合数量
idbase=nsec*(ifst-1)
#根据第二个分段的内容确定其当前第一分段下在第二个分段所有中的位置
seccomb=comb1[flen:]
restcds=[x for x in range(datastt,dataend+1) if x not in fstcds]
comb2=[restcds.index(x) for x in seccomb]
idsec=getCombinationId(comb2,0,len(restcds)-1)
id=idbase+idsec
#print('idbase=',idbase,'idsec=',idsec)
return id
if __name__ == "__main__":
enumPieceComb()
enumPieceComb1()
enumPieceComb2()
4. 小结
本文给出了一个两个阶段的组合的在序列的全部组合中的序号的求法,利用了原来的一个阶段的组合的序号求法,可以避免遍历求解,从而提高计算效率。