排列组合中确定某个组合在组合遍历集中的序号
排列组合中,我们常计算组合数。有时也遍历组合的集合,即将所有的可能组合都列举出来。
另外的一些时候呢,我们会用采样的方法来采样一些组合,那么采样样本的代表性如何呢?我们可以用样本的均匀性来考察,
在直方图上就是各个方柱具有相近的高度。
那么怎样来生成这样一个直方图呢?类似于一般的0-1均匀分布的采样我们用样本的值来生成直方图,那么均匀分布的组合呢,则需要用组合的序号来生成直方图。
这个序号可以用一个简单的遍历算法,即将通过组合集的遍历找到该组合从而确定其序号。但显然这种方式是比较慢的,所以我们简单的写了一个算法,相比简单的遍历确定方式要快的多。
因为是着急用所以就直接写了,没有去搜索现有的其它的可能算法。所以这里只做分享,不做比较。
问题提出
假设我们有[1,2,3,4,5,6,7,8,9,10]这样一个序列,需要从中任选三个构成一个组合,那么这个组合数是binom(10,3)=120。
如果做一个遍历,那么可以获得所有的组合。
比如:
datastt=1
dataend=10
comblst=[]
for i1 in range(datastt,dataend+1):
for i2 in range(i1+1,dataend+1):
for i3 in range(i2+1,dataend+1):
comblst.append([i1,i2,i3])
或者:
for comb in itertools.combinations(range(datastt,dataend+1),m):
comblst.append(list(comb))
那么在随机采样的时候,我们任意的采样了一个组合[3,5,8],那么这个组合在遍历集中是第几个呢?
第一种方法通过组合遍历的方式,我们在组合遍历过程中找到这个组合,记录下来的遍历过的组合数就是这个组合的序号。
这种方式在数据量较少的情况下是可以的,但是在一个更大的序列和更大的组合中,由于需要遍历的组合实在太多,从计算量上看并不合适。
所以我们希望建立一个更快一点的算法。
基于组合计算的算法
因为遍历是有规律的,所以我这里基于遍历中的组合计算规律提出了基于组合计算的方法。
观察遍历的过程可以去分析每个循环的过程中的出现的组合数的规律。
如果将要确定的组合按从小到大排序后,那么这种规律就会更明显。
对于一个[stt,…,end]的序列求组合[c1,…,cm]的序号。
观察遍历的过程,从1开始到找到目标组合[c1,…,cm]中的第一个数c1,我们选定一个i
,那么所有与目标组合无关的组合的数量列表是:
[\bionm(end-i,m-1) for i in stt,...,c1-1]
当找到c1后,我们可以类似的分析得到在c1+1与c2之间的与目标组合无关的组合的数量列表:
[\bionm(end-i,m-2) for i in c1+1,...,c2-1]
直到找到最后一个数cm前,类似的无关组合的数量列表为包括:
[\bionm(end-i,0) for i in c(m-1)+1,...,cm]
因此将上述无关组合的数量加起来就能得到目标组合序号,注意序号从1开始计数。
算法实现为:
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
id=1
for k in range(m):
for i in range(comblst[k]+1,comblst[k+1]):
id+=Cmn(dataend-i,m-1-k)
return id
其中Cmn为计算组合数的函数,类似于mathematica中Binomial[n,m]
算法验证
为了验证算法的正确性,我们也采用遍历方法来确定目标组合的序号。
算法为:
def getcombIDnumen(cds=[1,2,3],datastt=1,dataend=10):
cdssorted=sorted(cds)
i=0
for i1 in range(datastt,dataend+1):
for i2 in range(i1+1,dataend+1):
for i3 in range(i2+1,dataend+1):
for i4 in range(i3+1,dataend+1):
for i5 in range(i4+1,dataend+1):
i+=1
if i1==cdssorted[0] and i2==cdssorted[1] and i3==cdssorted[2] and i4==cdssorted[3] and i5==cdssorted[4]:
id=i
return id
我们验证一下结果:
def testCombinationIdB():
datastt=1
dataend=52
comb=[1,2,3,4,5]
print('id=',getCombinationId(comb,datastt,dataend))
comb=[48,49,50,51,52]
print('id=',getCombinationId(comb,datastt,dataend))
cdslst=[]
for i in range(100):
cds=[]
while len(cds)<5:
cd=random.randint(1,52)
if cd not in cds:
cds.append(cd)
cdssorted=sorted(cds)
cdslst.append(cdssorted)
print('cds=',cdssorted,' id=',getcombIDnumen(cdssorted,1,52),getCombinationId(cdssorted,1,52))
#两种方法的计时:
print('stated method 1')
t1=time.process_time()
for cds in cdslst:
id=getcombIDnumen(cds,1,52)
t2=time.process_time()
print('time elapsed',t2-t1) #time elapsed 23.203125
print('stated method 2')
t1=time.process_time()
for cds in cdslst:
id=getCombinationId(cdssorted,1,52)
t2=time.process_time()
print('time elapsed',t2-t1) #time elapsed 0.078125
return None
显然两者是结果是相同的,而且基于组合计算的方法速度明显要快得多。
分析随机采样的均匀性
我们来看一下的从1-10中选4个数作为一个组合的情况,这种情况遍历的话有210种组合,我们随机采样210*sig个组合,观察其分布的均匀性并与遍历的方式得到的210个组合进行比较。
取sig=1时,有:
取sig=10时,有:
显然随着采样数的增加,采样的样本均匀性会有所提升。
测试代码为:
def testcombuniform():
datastt=1
dataend=10
m=4
nc=int(Cmn(dataend-datastt+1,m))
print('nc=',nc)
sig=10
n=nc*sig
cdlist=range(datastt,dataend+1)
cdsflg1=np.zeros(nc)
cdslst1=[]
cdsids1=[]
for i in range(n):
cds=random.sample(cdlist,m)
cdslst1.append(cds)
id=int(getCombinationId(sorted(cds),datastt,dataend))
cdsflg1[id-1]+=1
cdsids1.append(id)
cdsflg2=np.zeros(nc)
cdslst2=[]
cdsids2=[]
id=1
for i1 in range(datastt,dataend+1):
for i2 in range(i1+1,dataend+1):
for i3 in range(i2+1,dataend+1):
for i4 in range(i3+1,dataend+1):
cds=[i1,i2,i3,i4]
cdslst2.append(cds)
cdsflg2[id-1]+=1
cdsids2.append(id)
id+=1
plt.figure()
plt.plot(range(nc),cdsflg1/sig,label='samples')
plt.plot(range(nc),cdsflg2,label='enumerations')
plt.legend()
plt.figure()
plt.hist(cdsids1,bins=30,density=True,cumulative=False,rwidth=0.9,histtype='bar',label='samples')
plt.hist(cdsids2,bins=30,density=True,cumulative=False,rwidth=0.5,histtype='bar',label='enumerations')
plt.legend()
plt.show()
return None
小结
本文我们提供了一种从组合遍历集中快速获取目标组合的序号的算法。