排列组合问题的通用算法

排列组合(递推矩阵) 排列与组合有什么区别? 排列与元素的顺序有关,组合与顺序无关。 如何递推出对应的排列组合呢? ①组合 通过上面的组合公式可以得到下面的数据表格 n\m 0 1 2 3 4 5 6 … 0 0 0 0 0 0 0 0 … 1 1 0 0 0 0 0 0 … 2 1 2 0 0 0 0 0 … 3 1 3 6 0 0 0 0 … 4 1 4 12 24 0 0 0 … 5 1 5 20 60 120 0 0 … 6 1 6 30 20 360 720 0 … … 阅读详情

    尽管排列组合是生活中经常遇到的问题,可在程序设计时,不深入思考或者经验不足都让人无从下手。由于排列组合问题总是先取组合再排列,并且单纯的排列问题相对简单,所以本文仅对组合问题的实现进行详细讨论。以在n个数中选取m(0<m<=n)个数为例,问题可分解为:
1. 首先从n个数中选取编号最大的数,然后在剩下的n-1个数里面选取m-1个数,直到从n-(m-1)个数中选取1个数为止。
2. 从n个数中选取编号次小的一个数,继续执行1步,直到当前可选编号最大的数为m。
很明显,上述方法是一个递归的过程,也就是说用递归的方法可以很干净利索地求得所有组合。
下面是递归方法的实现:
/// 求从数组a[1..n]中任选m个元素的所有组合。
/// a[1..n]表示候选集,n为候选集大小,n>=m>0。
/// b[1..M]用来存储当前组合中的元素(这里存储的是元素下标),
/// 常量M表示满足条件的一个组合中元素的个数,M=m,这两个参数仅用来输出结果。
void combine( int a[], int n, int m,  int b[], const int M )
{ 
 for(int i=n; i>=m; i--)   // 注意这里的循环范围
 {
  b[m-1] = i - 1;
  if (m > 1)
   combine(a,i-1,m-1,b,M);
  else                     // m == 1, 输出一个组合
  {   
   for(int j=M-1; j>=0; j--)
    cout << a[b[j]] << " ";
   cout << endl;
  }
 }
}

因为递归程序均可以通过引入栈,用回溯转化为相应的非递归程序,所以组合问题又可以用回溯的方法来解决。为了便于理解,我们可以把组合问题化归为图的路径遍历问题,在n个数中选取m个数的所有组合,相当于在一个这样的图中(下面以从1,2,3,4中任选3个数为例说明)求从[1,1]位置出发到达[m,x](m<=x<=n)位置的所有路径:
1  2  3  4
    2  3  4
        3  4
上图是截取n×n右上对角矩阵的前m行构成,如果把矩矩中的每个元素看作图中的一个节点,我们要求的所有组合就相当于从第一行的第一列元素[1,1]出发,到第三行的任意一列元素作为结束的所有路径,规定只有相邻行之间的节点,并且下一行的节点必须处于上一行节点右面才有路径相连,其他情况都无路径相通。显然,任一路径经过的数字序列就对应一个符合要求的组合。
下面是非递归的回溯方法的实现:
/// 求从数组a[1..n]中任选m个元素的所有组合。
/// a[1..n]表示候选集,m表示一个组合的元素个数。
/// 返回所有组合的总数。
int combine(int a[], int n, int m)
{  
 m = m > n ? n : m;

 int* order = new int[m+1];   
 for(int i=0; i<=m; i++)
  order[i] = i-1;            // 注意这里order[0]=-1用来作为循环判断标识
 
 int count = 0;                               
 int k = m;
 bool flag = true;           // 标志找到一个有效组合
 while(order[0] == -1)
 {
  if(flag)                   // 输出符合要求的组合
  {  
   for(i=1; i<=m; i++)                   
    cout << a[order[i]] << " ";
   cout << endl;
   count++;
   flag = false;
  }

  order[k]++;                // 在当前位置选择新的数字
  if(order[k] == n)          // 当前位置已无数字可选,回溯
  {
   order[k--] = 0;
   continue;
  }    
 
  if(k < m)                  // 更新当前位置的下一位置的数字         
  {
   order[++k] = order[k-1];
   continue;
  }
 
  if(k == m)
   flag = true;
 }

 delete[] order;
 return count;
}

下面是测试以上函数的程序:
int main()
{
 const int N = 4;
 const int M = 3;
 int a[N];
 for(int i=0;i<N;i++)
  a[i] = i+1;

 // 回溯方法
 cout << combine(a,N,3) << endl; 

 // 递归方法
 int b[M];
 combine(a,N,M,b,M); 

 return 0;
}

由上述分析可知,解决组合问题的通用算法不外乎递归和回溯两种。在针对具体问题的时候,因为递归程序在递归层数上的限制,对于大型组合问题而言,递归不是一个好的选择,这种情况下只能采取回溯的方法来解决。

    n个数的全排列问题相对简单,可以通过交换位置按序枚举来实现。STL提供了求某个序列下一个排列的算法next_permutation,其算法原理如下:
1. 从当前序列最尾端开始往前寻找两个相邻元素,令前面一个元素为*i,后一个元素为*ii,且满足*i<*ii;
2. 再次从当前序列末端开始向前扫描,找出第一个大于*i的元素,令为*j(j可能等于ii),将i,j元素对调;
3. 将ii之后(含ii)的所有元素颠倒次序,这样所得的排列即为当前序列的下一个排列。
其实现代码如下:
template <class BidirectionalIterator>
bool next_permutation(BidirectionalIterator first, BidirectionalIterator last)
{
  if (first == last) return false;   // 空範圍
  BidirectionalIterator i = first;
  ++i;
  if (i == last) return false;       // 只有一個元素
  i = last;                          // i 指向尾端
  --i;

 for(;;)
 {
  BidirectionalIterator ii = i;
  --i;
  // 以上,鎖定一組(兩個)相鄰元素
  if (*i < *ii)                     // 如果前一個元素小於後一個元素
  { 
   BidirectionalIterator j = last;  // 令 j指向尾端
   while (!(*i < *--j));            // 由尾端往前找,直到遇上比 *i 大的元素
   iter_swap(i, j);                 // 交換 i, j
   reverse(ii, last);               // 將 ii 之後的元素全部逆向重排
   return true;
  }
  if (i == first)                   // 進行至最前面了
  { 
   reverse(first, last);            // 全部逆向重排
   return false;
  }
 }
}


下面程序演示了利用next_permutation来求取某个序列全排列的方法:
int main()
{
 int ia[] = {1,2,3,4};
 vector<int> iv(ia,ia+sizeof(ia)/sizeof(int));

 copy(iv.begin(),iv.end(),ostream_iterator<int>(cout," "));
 cout << endl;
 while(next_permutation(iv.begin(),iv.end()))
 {
  copy(iv.begin(),iv.end(),ostream_iterator<int>(cout," "));
  cout << endl;
 }

 return 0;
}
注意:上面程序中初始序列是按数值的从小到大的顺序排列的,如果初始序列无序的话,上面程序只能求出从当前序列开始的后续部分排列,也就是说next_permutation求出的排列是按排列从小到大的顺序进行的。

 Copyright@戴维 2006.5  于北京 

 

算法 64式 17、排列组合算法整理 1算法思想 排列组合 1.1含义 排列: 含义:从元素列表中取出指定个数的元素进行排序 公式:从n个不同元素中取出m个元素的排列个数=n!/(n-m)! 组合: 含义:从元素列表中取出指定个数的元素,不排序。 公式:从n个不同元素中取出m个元素的组合个数=n!/(m! * (n-m)!) 1.2特点 1.3应用 字符串的排列:需要使用回溯(成功输出结果,设置变量,递归,清除对... 阅读详情

相关推荐

组合数学及其算法

第一章 引论 1.1 组合数学研究的对象 1.2 组合问题典型实例 1.2.1 分派问题 1. 2.2 染色问题 1.2.3 幻方问题 1.2.4 36军官问题 1.2.5 中国邮路问题 习 题 第二章 排列与组合 2.1 两个基本计数原理 2.2 无重集的排列与组合 2.3 重集的排列与组合 2.4 排列生成算法 2.4.1 序数法 2.4.2 字典序法 2.4.3 轮转法 2.5 组合生成算法 .2.6 应用举例 习 题 第三章 容斥原理 3.1 引 言 3.2 容斥原理 3.3 几个重要公式 3.4 错位排列 3.5 有限制的排列 3.6 棋阵多项式 3.7 禁位排列 习 题 第四章 鸽巢原理 4.1 鸽巢原理 4. 2 鸽巢原理的推广形式 4. 3 ramsey数 4.4 ramsey数的性质 4.5 ramsey定理 习 题 第五章 母函数 5.1 母函数概念 5.2 幂级数型母函数 5.3 整数的拆分 5.4 ferrers图 5.5 指数型母函数 习 题 第六章 递归关系 6.1 引言 6.2 几个典型的递归关系.. 6.3 用母函数方法求解递归关系 6.4 常系数线性齐次递归关系的求解 6.5 常系数线性非齐次递归关系的求解 6.6 非常系数非线性递归关系的求解 6.7 差分表法 6.8 stirling数 习 题 第七章 polya定理 7.1 有限集的映射 7.2 群的基本概念 7.3 置换群 7.4 置换的奇偶性 7.5 置换群下的共轭类 7.6 burnside引理 7.7 polya定理 7.8 polya定理的母函数型式 7.9 不标号图的计数 习 题 第八章 图论基础 8.1 图的基本概念 8.2 同构图、完全图与二分图 8.3 通路、回路与图的连通性 8.4 euler图与hamilton图 8.5 割集与树 8.6 图的矩阵表示法 8.7 平面图、对偶图与色数 8.8 匹配理论 8.9 网络流 习 题 第九章 拉丁方与区组设计 9.1 引言 9.2 拉丁方 9.3 有限域 9.4 正交拉丁方的构造 9.5 完全区组设计 9.6 平衡不完全区组设计(bibd) 9.7 区组设计的构造 9.8 steiner三连系 9.9 hadamard矩阵 习 题 第十章 线性规划 10.1 lp问题引例 10.2 lp问题的一般形式 10.3 lp问题的标准型 10.4 可行域和最优可行解 10.5 单纯形法 10.6 单纯形表格法 10.7 两阶段法 10.8 对偶原理 10.9 对偶单纯形法 10.10 应用举例 习 题 第十一章 组合优化算法与计算的时间复杂度理论 11.1 dijkstra算法 11.2 floyd算法 11.3 kruskal算法 11.4 求最优树的破圈法和统观法 11.5 二分图中最大匹配与最佳匹配的算法 11.6 fleury算法 11.7 中国邮路问题及其算法 11.8 深度优先搜索法--dfs算法 11.9 项目网络与关键路径法 11.10 网络最大流算法 11.11 状态转移法 11.12 好算法、坏算法和np类问题 11.13 npc类问题 11.14 货郎问题的近似解 习 题... 参考文献

组合数学的一些常见公式

1.特殊的排列组合: 1.在n个不同物体中,可重复地选取r个物体的排列数为: n^r。; 2.在n个不同物体中,可重复地选取r个物体的组合数为C(n+r-1, r); 3.n个物体中不相同的物体的总数是k个,即n=n1+n2+ ··· +nk,则这几个物体全排列数是: n! / (n1! * n2! * ··· * nk!); 4. 圆周排列(选取的物体不分先后):A(n, r) / r;

YanzheShi的专栏 4768

WhatsApp协议算法

我想其实有很多人还不太了WhatsApp群控,简单来说,它和我们常使用的微信聊天软件是类似的,只不过它是基于通讯录的,操作起来比较简单便捷,所以现在有很多国家都喜欢用WhatsApp。一般情况下,筛号不会造成账号被封的情况,但是呢,如果筛号的频率太快,就会被系统限制筛号能力,不再返回筛号结果,所以,筛号数量和速度一定要控制好。前天逛街突然灵光一现,为什么不试试多路访问呢,简单来说就是信道复用技术,马上跑回家,调取接口,接入通道,导入已经转换成协议的账号,成功登录!

2403_87755661的博客 1943

回朔算法(试探算法)-彩票号码组合

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法

菜鸟腾飞 6209

【东华大学oj】找到最大数(一维数组->中等)

一天,老师布置了一个作业,要求对一串数字各不相同的数字串进行排序,大的在前,小的在后。接着,我从数字串中的第二个数字开始往后找,找到最大的一个,然后与第二个数字交换,这样的话我就能够确定排序后数字串中的第二个数字;明明研究发现,他的这个想法中最重要的一环就是在一串数字中找到最大的那个,然后与第一个数字交换,可是明明毕竟刚刚开始学习程序设计不久,无法写出较复杂的程序,所以明明想让你这位程序设计专家帮他的忙,帮助他把这段核心程序写出来,然后明明只需模仿着写就可以了。时间限制: 1S类别: 一维数组->中等。

的博客 524

深入解析i2cdetect:从基础命令到高级读写操作

本文深入解析Linux下i2cdetect工具,从基础安装、总线扫描到高级读写操作。作为i2c-tools的核心组件,i2cdetect是硬件调试的得力侦探,能快速识别I2C总线上的设备地址。文章还涵盖了i2cdump、i2cset、i2cget等进阶工具,并介绍了如何在C程序中调用读写接口进行嵌入式开发,是掌握I2C通信调试的实用指南。

weixin_29189987的博客 372

组合算法/全排列算法/求子集算法

目录写在前面组合 写在前面 组合 从后开始选: class Solution: def combine(self, n: int, k: int) -> List[List[int]]: ans=[] def sub_combine(n,k,lst=[]): #n个数里面选k个,保存在lst里 if(k==0): #已经选完 ans.append([i for i in lst])

weixin_43281875的博客 1910

微信聊天记录数据库解密实战:从WCDB到sqlcipher4的关键步骤解析

本文详细解析了微信桌面版聊天记录数据库的解密实战过程。通过逆向分析,定位了微信从WCDB框架到sqlcipher4加密的关键步骤,并成功提取了数据库密钥。文章重点介绍了如何通过动态调试、字符串定位等方法,追踪并验证sqlcipher4的加密密钥,最终实现对加密数据库文件的解密操作,为安全研究和数据恢复提供了技术参考。

weixin_29322553的博客 1071

解决排列组合问题的通用算法

很多网友发贴询问诸如:八皇后问题、彩票问题(从m中数中选择n(m>=n)的组合)等,其实这都可归结为排列组合的问题。解决这类问题,用for循环嵌套是不现实的(只能对指定的m、n编程,而且程序看上去异常繁琐),较好的方法是回朔法。下面给出这类问题的一般算法的c/c++描述:int combine(int a[],int sub){//a[1..?]表示候选集,sub表示一个排列(组合)的元素个数{ 

键者天行 6124

MIKE 21 教程 1.2 网格搭建界面介绍之点线面要素的高阶处理 (Mesh Generator 工具)

MIKE 21 教程 手把手教学系列 1.1 网格搭建界面介绍之点线面要素的高阶处理 (Mesh Generator 工具) ​ 上一篇文章讲解了如何创建基本的点、线、面要素 MIKE 21 教程 1.1 网格搭建界面介绍之点线面的创建 本节来讲一下对点线面要素的高阶处理,主要包括要素修改与打断线(break line)的讲解 ​

Hello Word 4148

新手必看!手把手教你在群晖 NAS 上玩转 Docker 容器(Container Manager)!(群晖 NAS 如何创建 Docker 容器以及 Compose 部署项目)

新手必看!手把手教你在群晖 NAS 上玩转 Docker 容器(Container Manager)!(群晖 NAS 如何创建 Docker 容器以及 Compose 部署项目)

XiaoqiangClub的博客 5161

解决排列组合问题的通用算法(转)

c/c++描述:int combine(int a[],int sub){//a[1..?]表示候选集,sub表示一个排列(组合)的元素个数{   int total=sizeof(a);   int order[sub+1];   int count=0;//符合条件的排列(组合)的个数   order[0]=-1;   for(int i=1;i&lt;=sub;i++)      order...

weixin_34246551的博客 140

【WRF工具】WRF 模型输出可视化工具 RIP4

【WRF工具】WRF 模型输出可视化工具 RIP4

WW、forever的博客 1594

LeetCode 子集合,排列组合,回文分离等问题的通用递归算法

通用算法思路总结: 初始结果列表。 可能要将数集排序,方便处理重复元素的情况。 调用递归函数。 书写递归函数,先要考虑原点状况,一般就是考虑什么情况下要将当前结果添加到结果列表中。 for循环遍历给定集合所有元素,不同题目区别在于进行循环的条件,具体看例子。每当一个元素添加到当前结果中之后,要再调用递归函数,相当于固定了前缀穷举后面的变...

weixin_34254823的博客 150

【Coze】在Coze平台使用源码创建工作流

Coze平台提供低代码工作流搭建功能,支持通过图形化界面快速构建自动化任务流程。用户可通过资源库创建工作流,导入JSON配置生成节点结构,连接起始节点并配置输入/输出字段(需定义数据类型)。平台提供试运行功能验证流程执行,支持可视化调试与发布上线。该方案适用于快速部署标准化业务逻辑,实现端到端的自动化处理。

Mr数据杨 2430

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

java递归实现回文算法_LeetCode 子集合,排列组合,回文分离等问题的通用递归算法...

通用算法思路总结:初始结果列表。可能要将数集排序,方便处理重复元素的情况。调用递归函数。书写递归函数,先要考虑原点状况,一般就是考虑什么情况下要将当前结果添加到结果列表中。for循环遍历给定集合所有元素,不同题目区别在于进行循环的条件,具体看例子。每当一个元素添加到当前结果中之后,要再调用递归函数,相当于固定了前缀穷举后面的变化。调用完之后要将当前结果中最后一个元素去掉,进行下一个循环才不会重复。...

weixin_29602351的博客 256

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

上一篇: 怎样将成员函数指针强制转换成void*指针?
下一篇: 动态规划算法剖析
sharpdew
博客等级 码龄23年 76粉丝 35原创
评论 12
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值