现在的位置: 首页 > 综合 > 正文

组合算法:产生基本组合对象(Generating Elementary Combinatorial Objcts)

2013年05月23日 ⁄ 综合 ⁄ 共 1945字 ⁄ 字号 评论关闭

  组合对象,算法课刚学的时候我也挺小看他的。之后在写算法,特别是蛮力法,的时候才发现他的重要性。

  先说说他的用处。在算法问题中常遇到一些组合问题,如:

  给你一个集合S = {2, 7, 36, 40, 53, 59, 62, 69, 77, 80, 87, 89, 95, 98, 100, 102, 103, 106, 112, 115},要你从S中找出他的子集T,使得T中所有元素的和为220。

  当然,这个问题不光可以用蛮力来解决。但如果用蛮力法的话,我们有没有最简便的方法,使得算法效率最高呢。这就要用到组合对象了。

  组合对象X = {xi},我的理解,它是一个与集合S = {si}有相同元素个数的集合,并且X与S中的元素一一对应,X中的每个元素都只有两个值,a和b,且:xi = a 时,si被选中;xi = b 时,si没被选中。(大部分情况下我们默认a = 1,b = 0)

  下图为组合对象的一个例子,其中T是全集S={1,2,3}的一个子集合,他是根据组合对象的取值决定的。

           

  可以看到,如果我们能有个方法能够迭代的依次生成出所有的组合对象,那么对于全集S的不同子集合的选取将会方便很多。而且现在已经有了不少牛人的研究成果,用他们的算法绝对比我们自己做要省事,关键是效率很高。

  以下介绍Lexicographic ordering和Gray codes,具体事例都以S为全集,T为子集,且|S| = |T| = 3。元素索引从左向右递增,首位索引为0。

  Lexicographic ordering

  {0,0,1}
  {0,1,0}
  {0,1,1}
  {1,0,0}
  {1,0,1}
  {1,1,0}
  {1,1,1}
  {0,0,0}

  算法思想:指针每次从右侧开始向左扫描:  //以{0,0,1}为例

  (1)每遇到1,则将其改为0,并继续往左扫描下一位,即指针指向下一位;  //组合对象改为{0,0,0},指针指向红色元素

   (2)若指针索引为-1,即遇到{1,1,1}这种情况,则算法结束;  //若以{1,1,1}为例,此时组合对象为{0,0,0},算法结束

  (3)否则,将指针所指位改为1,并结束扫描;  //接(1)中例子:此时指针所指元素改为1,即{0,1,0},此为下一个组合对象

  此时得到下一种组合对象

  以下为代码,供参考:

public boolean[] next() {
	if(!hasNext) return null;    //组合对象生成完毕
	int index = n - 1;
	while(index >= 0 && set[index]){
		set[index] = false;
		index--;
	}
	if(index == -1){
		hasNext = false;
	}else{
		set[index] = true;
	}
	return set;
}

 

  Gray codes

  {0,0,0}
  {0,0,1}
  {0,1,1}
  {0,1,0}
  {1,1,0}
  {1,1,1}
  {1,0,1}
  {1,0,0}

      

  算法思想:如上图所示,组合对象的产生是遵循固定规律的,即沿着图中所绘图形的边框呈“之”字形变化:

  (1)当沿着直线向上或向下时,仅将最右边一位做取反操作,即得下一个组合对象;

  (2)当沿着直线向右时(向左可以类推):

    a.当在(1)中取反操作是0置1时,此时只要将右边第二位置反,即得下一个组合对象;

    b.否则,从右向左扫描找到第一个1的索引 i ,若 i = 0,即 i 为最左边的索引,则不再有下一个组合对象,算法终止,否则将第 i - 1位置反,即得下一个组合对象;

  代码如下,仅供参考:

//数组索引从0到n
public boolean[] next(){
	if(begin){	//如果是第一次调用begin=true,则返回{0,0,0,...,0}
		begin = false;
		return set;
	}
	if(!hasNext) return null;	//如果没有下一个组合对象 返回空
	if(turnFlag){	//为向下或向上移动
		if(!set[n]){
			set[n] = !set[n];	//0变1
			index = n - 1;
		}else{
			set[n] = !set[n];	//1变0
			index = n - 1;
			while(!set[index]) index--;	//如果当前位为0继续向左移
			if(index == 0){
				hasNext = false;	//此时为100...的情况 说明已经到最后了 没有next了
			}else{
				index--;	//此时为...100...情况 还有next
			}
		}
		turnFlag = false;
	}else{	//为向右移动
		set[index] = !set[index];
		turnFlag = true;
	}
	return set;
}

 

 

抱歉!评论已关闭.