探索并优化组合算法的中的01移位转换法(非递归)
2017-05-24 23:17
447 查看
//今天来讲讲如何把逐步优化串行(单机处理)的组合算法--01移位法 //以下所有方法只针对数组下标处理,与数组具体有什么元素没有任何关 系,所有方法均返回相同的组合数结果集(不输出) //可能有些同学不知道什么是01移位法,以下作简单的说明: 首先我们建立一个长度的5的数组来映射它的位置,并用0或1表示该位 置上的元素是否被选到,比如[1,1,1,0,0]这个序列表示[0,1,2], [0,1,1,1,0]则表示[1,2,3]; //下一个序列,总有当前的序列产生,它的产生规律如下:初始化第 从左向右找到第一对'10',交换他们的值,即'10'=>'01',然后将它们 前面的'1'全部移动到序列的开头,就产生了一个新的序列,新序列依照 前面的变化规律产生下一个新的序列,如5选3,变化规律如下 /** *1 4000 1 1 0 0 //1,2,3 * 1 1 0 1 0 //1,2,4 * 1 0 1 1 0 //1,3,4 * 0 1 1 1 0 //2,3,4 * 1 1 0 0 1 //1,2,5 * 1 0 1 0 1 //1,3,5 * 0 1 1 0 1 //2,3,5 * 1 0 0 1 1 //1,4,5 * 0 1 0 1 1 //2,4,5 * 0 0 1 1 1 //3,4,5 * * 然后我们来实现第一个版本的01移位法 * @param array $count [数组长度] * @param int $selectMany [选择多少个] * @return [mixed] [成功则返回形 * 如[1,1,1,0,0]的组合数结果集(二维数组)] */ public function combinationsByTransform1(int $count,int$selectMany){ if($selectMany<2 || $count<$selectMany) return false; $combination = []; //each combination for ($i=0; $i < $count; ++$i) { $combination[] = 0; } //init the first combination 初始化第一个序列 for ($i=0; $i < $selectMany ; ++$i) { $combination[$i] = 1; } $transform = [$combination]; //store combination 保存第一个序列 $end = $count-1; //如果i移动到序列末端,未找到'10'则已经产生了所有序列 for ($i=0,$one=-1;$i<$end; ++$i) { if($combination[$i]) ++$one; //统计直到当前位置,'1'的个数 //找到第一对'10'并交换 if($combination[$i]==1 && $combination[$i+1]==0){ $combination[$i] =0; $combination[$i+1] = 1; //以下两个for是把第一对'10'前的'1'全部移动到序列开头 for ($k=0; $k < $one; ++$k) { $combination[$k] = 1; } for ($k=$one; $k < $i; ++$k) { $combination[$k] = 0; } //保存序列 $transform[] = $combination; //查找下一个序列 $i=-1; $one = -1; } } //返回组合数形如[1,1,1,0,1]结果集 return $transform; } /**combinationsByTransform1 返回的形式如[1,1,1,0,1] 要没被选择到的位置,而且版本1还浪费空间,故而时间上也会很有影响 所以我们将版本1改了一下,让其返回形如[0,1,2,3]组合数,这正是 我们需要的结果 * [combinationsByTransform2 01转换法的改进版,输出组合 * 数形如[0,1,2,3]的结果集] * @param int $count [同上] * @param int $selectMany [同上] * @return [mixed] [返回组合数结果集] */ public function combinationsByTransform2(int $count,int$selectMany){ if($selectMany<2 || $count<$selectMany) return false; $combination = []; //each combination for ($i=0; $i < $count; ++$i) { $combination[] = 0; } //init the first combination for ($i=0,$transform=[]; $i < $selectMany ; ++$i) { $combination[$i] = 1; $transform [] = $i; } $combinations = [$transform]; $end = $count-1; for ($i=0,$one=-1;$i<$end; ++$i) { if($combination[$i]) ++$one; if($combination[$i]==1 && $combination[$i+1]==0){ $combination[$i] =0; $combination[$i+1] = 1; for ($k=0; $k < $one; ++$k) { $combination[$k] = 1; } for ($k=$one; $k < $i; ++$k) { $combination[$k] = 0; } //与版本1不一样的地方,这里将序列转换成了形如[1,2,3]的组合数 for ($k=0,$transform = []; $k < $count; ++$k) { if($combination[$k]) $transform[] = $k; } $combinations[] = $transform; $i=-1; $one = -1; } } return $combinations; } //版本2虽然返回了我们需要的结果,并且大大降低了空间的使用,但是 转换过程又遍历了一次,也就是说又花了一次遍历的时间 如果序列长度很大,就相当影响性能了,所以需要优化 /**版本3:在版本2的基础上,优化转换过程,使用索引映射和反索引映射, 可去掉版本2中用于转换的遍历过程 * [combinationsByTransform3 based on combinationsByTransform2 * ,deleting the transform process by using an index(report all location is the No.x '1') ] * @param int $count [description] * @param int $selectMany [description] * @return [type] [description] */ public function combinationsByTransform3(int $count,int$selectMany){ if($selectMany<2 || $count<$selectMany) return false; $combination = []; //each combination for ($i=0; $i < $count; ++$i) { $combination[] = 0; } //init the first combination for ($i=0,$index=[]; $i < $selectMany ; ++$i) { $combination[$i] = 1; $index[] = $i; //初始态时每个'1'所在的位置,即第几个再原数组的第几个位置上 } $reverse = $index; //初始态时原数组中某个'1'是第几个'1', //与$index互相映射$reverse[$j] = $i,则$index[$i] = $j, //$j表示原数组的$j下标,$i表示第$i个1; $combinations = [$index]; $end = $count-1; for ($i=0,$one=-1;$i<$end; ++$i) { if($combination[$i]) ++$one; if($combination[$i]==1 && $combination[$i+1]==0){ $combination[$i] =0; $combination[$i+1] = 1; $index[$reverse[$i]] = $i+1; //更新第$i个1的位置信息 $reverse[$i+1] = $reverse[$i]; //更新原数组第$i+1的位置上的对应的第$i个1的位置 unset($reverse[$i]); //以下两个for将第一对10前的1全部移动到序列开头,并更新相应的$index和$reverse for ($k=$one; $k < $i; ++$k) { $combination[$k] = 0; if(isset($reverse[$k])) unset($reverse[$k]); } for ($k=0; $k < $one; ++$k) { $combination[$k] = 1; $reverse[$k] = $k; $index[$k] = $k; } $combinations[] = $index; //保存该组合数 $index表示[1,2,3]这样的一维数组 $i=-1; $one = -1; } } return $combinations; } /**虽然版本3比版本2少作了以此转换过程的遍历,性能也提高了不少, 但是它在每次产生新序列的过程中都遍历了长度为$count的序列,如果, 这个长度非常非常长的花,将严重影响产生新序列的效率 我们来重新理解01移位法的规则:"从前向后找到第一对'10',交换它们", 这句话如果换种理解就是,从前向后遍历所有的1,一旦遍历到的1,如果它 的后一位是0的话,则交换它们,这样我们每产生一个新的序列,最多需要 遍历$selectMany次,如果$count>>$selectMany ,这一步优化的影 响尤为客观以下是实现这一优化的版本4 * [combinationsByTransform4 based on combinationsByTransform3 , * change the max searching lenght of find first '10' from $count * to $selectMany] * @param int $count [description] * @param int $selectMany [description] * @return [mixed] [description] */ public function combinationsByTransform4(int $count,int$selectMany){ if($selectMany<2 || $count<$selectMany) return false; $combination = []; //each combination for ($i=0; $i < $count; ++$i) { $combination[] = 0; } //init the first combination for ($i=0,$index=[]; $i < $selectMany ; ++$i) { $combination[$i] = 1; $index[] = $i; } $reverse = $index; $combinations = [$index]; //与版本3不一样的地方只有这里改成了对所有的1遍历,省去了对0的遍历 for ($i=0,$one=-1;$i<$selectMany; ++$i) { $j = $index[$i]; //$j表示每个1在原序列中的位置 ++$one; //计算1的遍历次数,表示到目前为止已经出现这么多个1 //以下同版本3 if($combination[$j]==1 && $j+1<$count &&$combination[$j+1]==0){ $combination[$j] =0; $combination[$j+1] = 1; $index[$reverse[$j]] = $j+1; $reverse[$j+1] = $reverse[$j]; unset($reverse[$j]); for ($k=$one; $k < $j; ++$k) { $combination[$k] = 0; if(isset($reverse[$k])) unset($reverse[$k]); } for ($k=0; $k < $one; ++$k) { $combination[$k] = 1; $reverse[$k] = $k; $index[$k] = $k; } $combinations[] = $index; $i=-1; $one = -1; } } return $combinations; } /**乍看一下,版本4似乎已经逆天了,其每产生一个新的组合数最多只需要O(m) 的时间复杂度,m表示选多少个元素,但是它还不能得瑟得太早,版本5表示我要和 版本4谈人生在实现版本5之前,我们再来看01移位法的规则:"从前向后找到第一 对'10',交换它们",我们把这句话先多读几遍,有木有明白它的本质? 木有的话,也没关系,我来解释给你听:它想说的是,定位到从前向后数的第一对'10' ,然后交换它们。没错,就是定位,精确的定位,无须任何的遍历!一针见血! 直接找到第一对'10'。 既然要做到定位,那我们总得知道第一对'10'的位置在哪吧,也就是说,我们需要 一个栈去记录它们的位置信息, 以下的版本5使得每一个组合数的生成只需要O(1)的时间复杂度。 * [combinationsByTransform5 based on combinationsByTransform4 , * plus accurate locating method which can quickly get the index * of '10' without traversal] * @param int $count [description] * @param int $selectMany [description] * @return [type] [description] */ public function combinationsByTransform5(int $count,int$selectMany){ if($selectMany<2 || $count<$selectMany) return false; $combination = []; //each combination for ($i=0; $i < $count; ++$i) { $combination[] = 0; } //init the first combination for ($i=0,$index=[]; $i < $selectMany ; ++$i) { $combination[$i] = 1; $index[] = $i; } $stackLenght = 0; //栈的初始长度 $stack = [$selectMany-1]; //第一对'10'中'1'的的位置 $reverse = $index; $combinations = [$index]; while ($stackLenght>=0) { //出栈:该序列从前往后数的第一对'10'中'1'的位置 $curIndex = $stack[$stackLenght]; unset($stack[$stackLenght]); --$stackLenght; //栈的长度减1 $combination[$curIndex] = 0; $combination[$curIndex+1] = 1; if($curIndex+2<$count && $combination[$curIndex+2]==0) //进栈:下一对'10'中'1'的位置 $stack[++$stackLenght] = $curIndex+1; $index[$reverse[$curIndex]] = $curIndex+1; $reverse[$curIndex+1] = $reverse[$curIndex]; $one = $reverse[$curIndex]; unset($reverse[$curIndex]); //以下两个for是把当前位置前面的1移动到开头 for ($k=$one; $k < $curIndex; ++$k) { $combination[$k] = 0; if(isset($reverse[$k])) unset($reverse[$k]); } for ($k=0; $k < $one; ++$k) { $combination[$k] = 1; $reverse[$k] = $k; $index[$k] = $k; } if($combination[$one]==0 && $one-1>=0) //进栈:下一对'10'中'1'的位置 $stack[++$stackLenght] = $one-1; $combinations[] = $index; } return $combinations; } /**写完了版本5,测试时发现,版本5在n比较大的情况下, 依然效率低下,原因在于移位过程中,将1转化为0时,遍历 不少不需要转换的位置,为此,我在版本5的基础上做了一个 优化的版本6,这个版本的优化的特点在于:把第一对10前所 有的1移到序列开头的同时,并把原来的位置上的置设为0,删 除对应的反索引 * [combinationsByTransform6 based on combinationsByTransform5] * @param int $count [description] * @param int $selectMany [description] * @return [mixed] [description] */ public function combinationsByTransform6(int $count,int$selectMany){ if($selectMany<2 || $count<$selectMany) return false; $combination = []; //each combination for ($i=0; $i < $count; ++$i) { $combination[] = 0; } //init the first combination for ($i=0,$index=[]; $i < $selectMany ; ++$i) { $combination[$i] = 1; $index[] = $i; } $stackLenght = 0; //栈的初始长度 $stack = [$selectMany-1]; //第一对'10'中'1'的的位置 $reverse = $index; $combinations = [$index]; while ($stackLenght>=0) { //出栈:该序列从前往后数的第一对'10'中'1'的位置 $curIndex = $stack[$stackLenght]; unset($stack[$stackLenght]); --$stackLenght; //栈的长度减1 $combination[$curIndex] = 0; $combination[$curIndex+1] = 1; if($curIndex+2<$count && $combination[$curIndex+2]==0) //进栈:下一对'10'中'1'的位置 $stack[++$stackLenght] = $curIndex+1; $index[$reverse[$curIndex]] = $curIndex+1; $reverse[$curIndex+1] = $reverse[$curIndex]; $one = $reverse[$curIndex]; unset($reverse[$curIndex]); //以下是把当前位置前面的1移动到开头 for ($k=0; $k < $one; ++$k) { $combination[$index[$k]] = 0; if(isset($reverse[$index[$k]])) unset($reverse[$index[$k]]); $combination[$k] = 1; $reverse[$k] = $k; $index[$k] = $k; } if($combination[$one]==0 && $one-1>=0) //进栈:下一对'10'中'1'的位置 $stack[++$stackLenght] = $one-1; $combinations[] = $index; } return $combinations; } //后记:虽然我把01移位法优化到了每产生一个组合数只需要 //O(m)的时间复杂度,实际上上面每个版本的性能并非线性 //增强,它与n、m的大小关系密切,比如4有时比5性能要高,5有 //时性能比6要高,但是无论哪个版本它都比不上另一种算法,无论 //是时间复杂度还是空间复杂度,都比不上,更好的组合算法请查看 //分治位偏移法(每产生一个组合数只需要O(1)的时间复杂度) //以及另一种同样高效的动态建模法
分治位偏移量法
动态建模法
相关文章推荐
- 组合优化学习笔记<之>从贪心算法到子集系统再到拟阵
- 两个组合优化问题及算法
- 优化后的组合算法
- 算法——组合篇01
- SEO探索四月百度算法调整 H1与title标签优化成看点
- MySQL查询优化器源码分析--多表连接优化算法之二,find_best(),搜索表之间的各种组合以得到最优的查询计划
- sku组合查询算法探索
- “金箍咒”我的全排列组合算法设计演化之四优化算法与补充
- [算法优化]优化阶乘算法的探索
- MySQL查询优化器源码分析--多表连接优化算法之三,greedy_search(),搜索表之间的各种组合以得到最优的查询计划
- sku组合查询算法探索
- 优化后的组合算法
- [算法与数据结构] - No.12 动态规划之01背包以及01背包一维数组优化
- 利用动态规划(非递归)探索一个高效的(n,m)组合算法,名字待定
- sku组合查询算法探索
- 海量数据库的查询优化及分页算法方案
- 探索推荐引擎内部的秘密,第 3 部分: 深入推荐引擎相关算法 - 聚类
- 优化算法——拟牛顿法之L-BFGS算法
- 关于移动渐近线优化算法(MMA)的程序说明