生成组合对象

组合对象中最重要的类型就是排列、组合和给定集合的子集。一般来说,这种情况出现在要对不同的选择进行考虑的问题中

生成排列

为了简单起见,假设需要对元素进行排列的集合是从1到n的简单整数集合。为了使它更具有一般性,可以把它们解释为n个元素集合{a1...an}\{a_1...a_n\}{a1​...an​}中元素的下标。对于生成{1...n}\{1...n\}{1...n}的所有n!个排列的问题,减一技术有什么好建议呢?该问题的规模减一就是要生成所有(n一1)!个排列。假设这个较小的问题已经解决了,我们可以把n插入n一1个元素的每一种排列中的n个可能位置中去,来得到较大规模问题的一个解。按照这种方式生成的所有排列都是独一无二的,并且它们的总数量应该是n(n-1)!=n!。这样,
我们得到了{1...n}\{1...n\}{1...n}的所有排列。
我们既可以从左到右也可以从右到左把n插入前面生成的排列中。从实际情况来看,
下面这种做法是有好处的:一开始从右到左把n插入12…(n-1)的位置中,然后每处理一
个{1...n−1}\{1...n-1\}{1...n−1}的新排列时,再调换方向。下图给出了应用该方法从底向上对n=3的情况
进行处理的例子。

以这种次序来生成排列有什么好处呢?因为实际上它满足所谓的最小变化要求:因为仅仅需要交换直接前趋中的两个元素就能得到任何一个新的排列。这个最小变化要求不仅有利于提高该算法的速度,而且对使用这些排列的应用也有好处。例如,穷举查找解旅行推销
员问题时需要城市的一个排列。如果这个排列是由最小变化算法生成的,从它前趋的路线
长度中算出一条新路线的长度所需要的时间将是一个常量,而不是线性的。

Johnson-Trotter算法

对于n的较小值来说,不用生成排列的方式来得到n个元素的相同次序的排列是有可能的。为了做到这一点,我们给一个排列中的每个元素k赋予一个方向。我们在所讨论的每个元素上画一个小箭头来指出它的方向,例如:

如果元素k的箭头指向一个相邻的较小元素,我们说它在这个以箭头标记的排列中是移动mobile)的。例如,对于排列3241来说,3和4是移动的,而2和1不是。通过使用移动元素这个概念,我们可以给出所谓的Johnson-Trotter算法的描述,它也是用来生成排列的。伪代码如下:

Java代码实现

package com.算法.减治法;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** @Author Lanh**/
public class 移动生成排列 {public static void main(String[] args) {List<String> list = johnsonTrotter(3);System.out.println(list);}public static List<String> johnsonTrotter(int n){List<String> list = new ArrayList<>();int[] direct = new int[n];int[] result = new int[n];for (int i=0;i<n;i++) direct[i] = -1;for (int i=0;i<n;i++) result[i] = i+1;int K_index = 0;list.add(Arrays.toString(result));while (K_index!=-1){K_index = -1;//求最大的移动元素kfor (int j=0;j<n;j++){if (j+direct[j]>=0&&j+direct[j]<n&&result[j]>result[j+direct[j]]){if (K_index==-1||result[j]>result[K_index]) K_index = j;}}if (K_index!=-1){//把k和它箭头指向的相邻元素互换int temp = result[K_index];int new_index = K_index+direct[K_index];result[K_index] = result[new_index];result[new_index] = temp;temp = direct[K_index];direct[K_index] = direct[new_index];direct[new_index] = temp;//调转所有大于k的元素的方向for (int j=0;j<n;j++){if (result[j]>result[new_index]) direct[j] *= -1;}//将新排列添加到列表中list.add(Arrays.toString(result));}}return list;}
}

字典序生成排列

如何按照字典序生成a1a2...ana_1a_2...a_na1​a2​...an​后面的排列呢?如果an−1<ana_{n-1}<a_nan−1​<an​(肯定有一半序列是这样的情况),可以简单地调换最后这两个元素的位置。例如,123后面应该是132。如果an−1>ana_{n-1}>a_nan−1​>an​,则需要找到序列的最长递减后缀ai+1>ai+2>...>an(但ai>ai+1)a_{i+1}>a_{i+2}>...>a_n(但a_i>a_{i+1})ai+1​>ai+2​>...>an​(但ai​>ai+1​);然后把aia_iai​与后缀中大于它的最小元素进行交换,以使aia_iai​增大;再将新后缀颠倒,使其变为递增排列。例如,经过这样的变换,362541后面会跟着364125。

伪代码

LexicographicPermute(n)//以字典序产生排列//输入:一个正整数n//输出:在字典序下{1,...,n}所有排列的列表初始化第一个排列123...nwhile 最后一个排列有两个连续升序的元素 do找出使得ai<a_i+1的最大的i  //a_i+1>a_i+2>...>an找到使得ai<aj的最大索引j   //j>=i+1,因为ai<a_i+1交换ai和aj   //a_i+1 a_i+2 ... an仍保持降序将a_i+1到an的元素反序将这个新排列添加到列表中

Java代码实现

package com.算法.减治法;import java.util.*;/*** @Author Lanh**/
public class 字典序生成排列 {public static void main(String[] args) {List<String> list = lexicographicPermute(5);System.out.println(list);}public static List<String> lexicographicPermute(int n){List<String> list = new ArrayList<>();int[] result = new int[n];for (int i=0;i<n;i++) result[i] = i+1;list.add(Arrays.toString(result));//标志最后一个排列有两个连续升序的元素boolean flat = true;while (flat){flat = false;int i = -1;int j = -1;//找出使得ai<a_i+1的最大的i  //a_i+1>a_i+2>...>anfor (int k=0;k<n-1;k++) {if (result[k]<result[k+1]) {i = k;j = k+1;flat = true;}}if (flat) {//找到使得ai<aj的最大索引j   //j>=i+1,因为ai<a_i+1for (int k=n-1;k>j;k--) {if (result[i]<result[k]) {j = k;//break;  本来想break的,但是我循环条件设置了k>j,但是j=k了,注定不会再循环,所以我就不写break了}}//交换ai和aj   //a_i+1 a_i+2 ... an仍保持降序int temp = result[i];result[i] = result[j];result[j] = temp;//将a_i+1到an的元素反序for (int k=1;k<(n-i+1)/2;k++) {temp = result[i+k];result[i+k] = result[n-k];result[n-k] = temp;}//将这个新排列添加到列表中list.add(Arrays.toString(result));}}return list;}
}

测试结果

生成子集

我们研究过背包问题,它要求找出物品中最有价值的一个子集,并且能够装入一个承重量有限的背包中。当时讨论了求解该问题的穷举查找方法,它是以生成给定物品集合的所有子集为基础的。在本节中,我们来讨论一个算法,该算法能够生成一个抽象集合A={a1...an}\{a_1...a_n\}{a1​...an​}的所有2n2^n2n个子集。

减一思想也可以应用到这个问题中来。集合A={a1...an}\{a_1...a_n\}{a1​...an​}的所有子集可以分为两组:不包含ana_nan​的子集和包含ana_nan​的子集。前一组其实就是{a1...an−1}\{a_1...a_{n-1}\}{a1​...an−1​}的所有子集,而后一组中的每一个元素都可以通过把ana_nan​添加到{a1...an−1}\{a_1...a_{n-1}\}{a1​...an−1​}的一个子集中来获得。因此,一旦我们得到了{a1...an−1}\{a_1...a_{n-1}\}{a1​...an−1​}所有子集的列表,我们可以把列表中的每一个元素加上an,再把它们添加到列表中,以得到{a1...an}\{a_1...a_n\}{a1​...an​}的所有子集。下面给出了应用该算法来生成{a1,a2,a3}\{a_1,a_2,a_3\}{a1​,a2​,a3​}的所有子集的示例。

和生成排列相同,我们不必对较小集合生成幂集。有一个直接解决该问题的简便方法,它是基于这样一种关系:n个元素集合A={a1...an}\{a_1...a_n\}{a1​...an​}的所有2n2^n2n个子集和长度为n的所有2n2^n2n个位串之间的一一对应关系。建立这样一种对应关系的最简单方法是为每一个子集指定一个位串。如果aia_iai​属于该子集,bib_ibi​=1;如果aia_iai​不属于该子集,bib_ibi​=0。(这就是位向量的思想。)例如,位串000将对应于一个三元素集合的空子集,111将对应于该集合本身,也就是{a1,a2,a3}\{a_1,a_2,a_3\}{a1​,a2​,a3​},而110将表示{a1,a2}\{a_1,a_2\}{a1​,a2​}。如果利用这种对应关系,我们可以产生从0到2n2^n2n-1的二进制数来生成长度为n的所有位串。顺便说一句,如果有必要,应在数字前添加相应个数的0。例如,对于n=3的情况,我们得到:

请注意,当该算法以字典序(在两个符号0和1构成的字母表中)生成位串时,子集的排列次序绝对是很不自然的。因此,我们有可能希望得到所谓的挤压序(squashed order),其中,所有包含a的子集必须紧排在所有包含a1,...,aj−1(j=1,...,n−1)a_1,...,a_{j-1}(j=1,...,n-1)a1​,...,aj−1​(j=1,...,n−1)的子集后面,就像图4.10给出的三元素集合的子集列表。很容易就能对基于位串的算法进行一个调整,让它生成相关子集的挤压序
一个更有挑战性的问题是,是否存在一种生成位串的最小变化算法,使得每一个位串和它的直接前趋之间仅仅相差一位。(就子集来讲,我们希望每一个子集和它的直接前趋之间的区别,要么是增加了一个元素,要么是删除了一个元素,但两者不能同时发生。)该问题的答案为“是”。这是二进制反射格雷码的一个例子。对于n=3时,有:

伪代码

BRGC(n)//递归生成n位的二进制反射格雷码//输入:一个正整数n//输出:所有长度为n的格雷码位串列表if n=1,表L包含位串0和位串1else 调用BRGC(n-l)生成长度为n-1的位串列表L1把表L1倒序后复制给表L2把0加到表L1中的每个位串前面把1加到表L2中的每个位串前面把表L2添加到表L1后面得到表Lreturn L

Java代码实现

package com.算法.减治法;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;/*** @Author Lanh**/
public class 二进制反射格雷码生成子集 {public static void main(String[] args) {List<String> list = BRGC(3);System.out.println(list);}public static List<String> BRGC(int n){if (n == 1) {List<String> list = new ArrayList<>();list.add("0");list.add("1");return list;}else {List<String> L1 = BRGC(n-1);List<String> L2 = new ArrayList<>(L1);Collections.reverse(L2);for (int i=0;i<L1.size();i++){L1.set(i,"0"+L1.get(i));}for (int i=0;i<L2.size();i++){L2.set(i,"1"+L2.get(i));}L1.addAll(L2);return L1;}}
}

结果

减治法——生成组合对象相关推荐

  1. 减治法在组合问题中的应用 ——8枚硬币问题

    实验题目 在8枚外观相同的硬币中,有一枚是假币,并且已知假币与真币的重量不同,但不知道假币与真币相比较轻还是较重.可以通过一架天平来任意比较两组硬币,设计一个高效的算法来检测这枚假币. 实验要求 1) ...

  2. 生成组合对象的算法——Johnson-Trotter算法的python实现

    伪代码是Anany Levitin 著作的<算法设计与分析基础> ,初学代码,发现网上几乎没有Johnson-Trotter算法的python实现,所以自己根据伪代码琢磨了一下,写了这个代 ...

  3. 减治法在生成子集问题中的应用(JAVA)--递归、二进制反射格雷码

    减治法在生成组合对象问题中的应用 生成子集问题:经典的背包问题就是求解一个最优子集的问题,这里我们来讨论一个更简单的问题.对于任意一个集合来说,它都存在2^n个子集(一个集合所有的子集集合称为幂集). ...

  4. 减治法在生成全排列中的应用(JAVA)--回溯、Johnson-Trotter算法、自字典序

    减治法在生成组合对象问题中的应用 在深入浅出讲算法思想--蛮力法思想分析及应用这篇文章的最优解问题中中已经初步讲解了这类应用,下面我们将使用减治法再次思考这类问题. 1.全排列问题,在数学中求解一个n ...

  5. 减治法(Decrease and Conquer)

    减治法 减治法是一种一般性的算法设计技术,它利用了一个问题给定实例的解和同样问题较小实例的解之间的关系.一旦建立了这样一种关系,我们既可以自顶至下(递归)也可以自底至上地运用它(非递归). 减治法有3 ...

  6. 使用ThreeBSP库进行实现差集(相减)、并集(组合、相加)、交集(两几何体重合的部分)

    简介 之前我们一直使用Three.js默认提供的几何体,今天我们使用ThreeBSP库,可以将现有的模型组合出更多个性的模型来使用.我们可以使用ThreeBSP库里面的三个函数进行现有模型的组合,分别 ...

  7. Android开发如何理解Java静态代理 动态代理及动态生成代理对象原理 看这篇就够了

    动态代理与静态代理 前言 代理模式 静态代理 动态代理 JDK代理 动态生成代理对象原理 生成class数据源码 动态代理类真身 总结 前言 近期在研究Hook技术,需要用到动态代理,说到动态代理就会 ...

  8. R语言生成组合图并保存实战:实际上只保存了最后一个图问题、ggsave生成组合图并保存(保存完整组合图)

    R语言生成组合图并保存实战:实际上只保存了最后一个图问题.ggsave生成组合图并保存(保存完整组合图) 目录

  9. Fabricjs使用Group实现组合对象

    场景 Fabricjs一个简单强大的Canvas绘图库快速入门: Fabricjs一个简单强大的Canvas绘图库快速入门_BADAO_LIUMANG_QIZHI的博客-CSDN博客 在上面的基础上, ...

  10. java 组合对象_Java 中组合模型之对象结构模式的详解

    Java 中组合模型之对象结构模式的详解 一.意图 将对象组合成树形结构以表示"部分-整体"的层次结构.Composite使得用户对单个对象和组合对象的使用具有一致性. 二.适用性 ...

最新文章

  1. C++二维数组名与数组指针的思考
  2. 211高校副校长:我发了170多篇论文,最满意的一篇不到2分
  3. 揭秘:GitHub Star 5W人追更,这个框架是打工人石锤了!
  4. Cannot read property 'nodeType' of null; audio元素默认样式下载按钮
  5. python装饰器原理-Python装饰器原理
  6. 函数和常用模块【day04】: 总结(十二)
  7. php获取post表单数据_PHP获取POST表单数据
  8. 实现手机左右滑屏效果
  9. CTF(Pwn) 当题目为我们提供Libc版本.so文件, 与 不提供的区别
  10. 数据库连接池_DataSource_数据源(简单介绍C3P0和Druid)
  11. java public object_Java中Object类
  12. unity打包IOS填坑1
  13. 11.6 通信实例与ASCII码
  14. 狂团KtAdmin框架正式免费开源发布,助力独立版SAAS系统快速开发
  15. 中国各主要大城市经纬度数据
  16. 在线作图p图|图片生成|做图HTML源码
  17. 微信小程序模板消息测试- formId 的获取
  18. [人生感悟]写在毕业前
  19. 抽象工厂(代码实现)
  20. c#.net连接access数据库

热门文章

  1. CAD测量图纸时数据显示不出来怎么办?
  2. 转载: dropout为啥要rescale?
  3. 【CicadaPlayer】av_rescale_q 学习:转换PTS和Duration
  4. 问卷测试软件制作,最详细测评!在微信里做问卷调查,这 4 款小程序你一定会用到...
  5. linux服务篇-Squid服务
  6. STM32F401超声波proteus仿真
  7. 不同超声诊断仪器的原理和FPGA在超声中应用
  8. vue优化技巧之mixins
  9. python实现奇异值分解_奇异值分解原理及Python实例
  10. 什么是MTU?为什么MTU值普遍都是1500?