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


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

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

      给你一个集合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;
    }
    

     

     

  • 相关阅读:
    fscanf_s与scanf_s的宽度参数与缓冲区参数分析
    C语言小程序——推箱子(窄字符和宽字符)
    setlocale()函数详解——C语言
    在源文件(.c)和头文件(.h)中声明和定义的区别——C语言
    枚举类型enum详解——C语言
    宽字符————_T、_TEXT、L、TEXT之间的区别
    宽字符wchar_t和窄字符char——putwchar、wprintf
    extern的使用详解(多文件编程)——C语言
    编程的时候命名词穷了怎么办
    [C#/UI] 使用 await 实现业务对 UI 的控制反转
  • 原文地址:https://www.cnblogs.com/wu8685/p/1924280.html
Copyright © 2020-2023  润新知