C++数学与算法系列之排列和组合

news2024/11/23 12:20:28

1. 前言

本文将聊聊排列和组合,排列组合是组合学最基本的概念,在程序运用中也至关重要。

  • 排列问题:指从给定个数的元素中取出指定个数的元素进行排序。

  • 组合问题:指从给定个数的元素中仅仅取出指定个数的元素,不排序。

2.排列

排列的定义:

  • n个不同元素中,任取m(m≤n,m与n均为自然数)个不同的元素按照一定的顺序排成一列,叫做从n个不同元素中取出m个元素的一个排列。如从1,2,3,4,5中选择 3 个数字进行排列,则认为1,2,33,2,1是两种不同的排列。

  • n个不同元素中取出m(m≤n)个元素的所有排列的个数,叫做从n个不同元素中取出m个元素的排列数,用符号 A(n,m)表示。

Tips: 排列的英文是 Permutation 或者 Arrangement,故使用 P 或者 A 表示都可以,二者含义一样。

计算从 5个数字中任选择3个数字有多少种排列方式?

解决此问题时,先把问题演变成从 5个数字中选择 5个数字进行排列,其有多少种方案?

  • 1 数字可以在 5 个数字中任选择一个,故有 5 种选择。

7.png

  • 因第 1 个数字已经选择了一个,第 2 个数字只能在剩下的数字中选择,也就是只能在剩下的 4 个数字中选择,则有 4 种选择。

8.png

  • 同理,第 3个数字有 3种选择,第 4 个数字只有2种选择,第五个数字只能有1种选择。

  • 所有的排列数是 5*4*3*2*1=120种方案,是不是看起来很熟悉,就是求 5的阶乘。

下面使用穷举法求解上述问题中排列的个数:

#include <iostream>
using namespace std;
int main(int argc, char** argv) {
	int count=0;
	for(int a=1; a<=5; a++) {
		for(int b=1; b<=5; b++) {
			if(b==a)continue;
			for(int c=1; c<=5; c++) {
				if(c==b || c==a )continue;
				for(int d=1; d<=5; d++) {
					if(d==a || d==b || d==c)continue;
					for(int e=1; e<=5; e++) {
						if(e==d || e==c || e==b || e==a) continue;
						count++;
					}
				}
			}
		}
	}
	cout<<count<<endl;
	return 0;
}
//输出结果:120

既然是求 5 的阶乘,可以简化程序。

#include <iostream>
#include <cmath>
using namespace std;
int main(int argc, char** argv) {
	int num=5;
	int result=1;
	for(int i=1; i<=num; i++)
		result*=i;
	cout<<result;
	return 0;
}
// 120

如果不是选择 5 个数字,而是选择 4个数字?

  • 则第 1 个数字有5种选择,第 2 个数字有4种选择,第 3 个数字有3种选择,第 4个数字有2种选择,最终可选择的个数为5*4*3*2=120,和前面相比较,即为 5的阶乘除以 1的阶乘

如果不是选择 4 个数字,而是选择 3个数字?

  • 则第 1 个数字有5种选择,第 2 个数字有4种选择,第 3 个数字有3种选择,最终可选择的个数为5*4*3=60。即为5!除以2!的阶乘。

可推导出从 n 个数字中选择m个数字的排列个数的公式:

2.jpg

从推论可知,求A(n,m)的排列个数可以通过乘法原理求解:

  • 计算排列的个数,先确定高位的可能个数,再逐一确认次高位可能个数,一直到最低位的可能个数……完成它需要分成m个步骤。
  • 最高位有 n 种方法,次高位有n-1种方法……最低位有 n-m+1种方法。则最终的排列个数有:n*(n-1)*(n-2)……(n-m-1)种。

利用A(n,m)排列公式求解个数的算法:

#include <iostream>
#include <cmath>
using namespace std;
/*
*求阶乘函数
*/
int getJc(int num) {
	int res=1;
	for(int i=1; i<=num; i++)
		res*=i;
	return res;
}
/*
*求A(n,m)的排列个数
*/
int main(int argc, char** argv) {
	int n;
	int m;
	int count=0;
	cin>>n;
	do {
		cout<<"m小于n:"<<endl;
		cin>>m;
	} while(m>n);
	//求 n 的阶乘
	int nJc=getJc(n);

	//求 n-m 的阶乘
	int nmJc=1;
	//如果 n 等于 m
	if(n==m)
		nmJc==1;
	else {
		nmJc=getJc(n-m);
	}
	count=nJc/nmJc;
	cout<<count;
	return 0;
}

输出结果:

9.png

3. 组合

组合的定义:

  • n个不同元素中,任取m(m≤n)个元素并成一组,叫做从n个不同元素中取出m个元素的一个组合。
  • n个不同元素中取出m(m≤n)个元素的所有组合的个数,叫做从n个不同元素中取出m个元素的组合数。用符号 C(n,m) 表示。

Tips: C是单词Combination 的缩写。

组合与排列的区别:

组合对于找出来的数字的顺序没有要求,也就是说1,2,33,2,1只能算一种组合方案。

如何统计组合的个数?

可以根据排列公式推导。

如从 1,2,3选择 2个数字进行组合。先套用排列计算公式,共有 3*2=6种排列方案。即 [1,2]、[2,1]、[1,3]、[3,1]、[2,3]、[3,2] 6 种方案。求组合个数,则需要减去数字一样、顺序不一样重复方案,最终结果为 3

求解组合的个数可以先求解排列个数后,再排除重复的部分**。问题转为具体重复的会有多少?**

  • 如果从 4 个数字中选择 3 个数字,则任意选择的 3个数字会有 3!=6 种排列方案,但是,对于组合而言,是一种方案。

10.png

  • 同时,从5个数字中选择 4个数字排列,任意4个数字会有 4! 种排列。或者说从 n 个数字中任意选择 m 个数字,则m个数字的排列有m!种,对于组合而言,这 m!个排列数只计数 1 次。
  • 所以,求解n个数字中选择m个数字的组合数可以先计算排列数后,再在结果上除以 m!(m的阶乘)

2.png

在程序中套用上述公式,可以求解出 C(3,2)3 种组合数。

#include <iostream>
#include <cmath>
using namespace std;
/*
*求阶乘函数
*/
int getJc(int num) {
	int res=1;
	for(int i=1; i<=num; i++)
		res*=i;
	return res;
}
/*
*求C(n,m)的组合个数
*/
int main(int argc, char** argv) {
	int n;
	int m;
	int count=0;
	cin>>n;
	do {
		cout<<"m小于n:"<<endl;
		cin>>m;
	} while(m>n);
	//求 n 的阶乘
	int nJc=getJc(n);

	//求 n-m 的阶乘
	int nmJc=1;
	//如果 n 等于 m
	if(n==m)
		nmJc==1;
	else {
		nmJc=getJc(n-m);
	}
	//求 m 阶乘
	int mJc=m==0?1:getJc(m);
	count=nJc/(mJc*nmJc);
	cout<<count;
	return 0;
}

输出结果:

11.png

在上述组合公式的基础上,组合公式还可以发生如魔术般的变化,也许这就是数学的神奇之处。

3.1 运算法则一

如下图所示:

3.jpg

通过一个案例求证:假设有 4 名学生,选择 3 名学生打扫卫生,有多少种选择?

显然,这是一个组合问题,没有顺序的要求,即C(4,3)。有 2 种思路求解:

  • 4 个学生中选择 3名学生打扫卫生。套用组合的基础公式可知结果=4!/ 3!*1!=4种选择。如下图所示,可以理解为是正向选择。

3.png

  • 另一种方案,称为反向选择,因为有 4 个学生,每次选择一个学生回家,剩下的搞卫生,同样满足要求。相当于 4 个学生里面选择 1 名学生。结果 4!/1!*3!=4

4.png

组合公式的如上运算法则很容易理解。根据下面的组合公式,可知,从 n 中选择 m 和 从 n 中选择 n-m的最终表达式是一样的。

2.png

编程实现:

#include <iostream>
#include <cmath>
using namespace std;
/*
*求阶乘函数省略
*/

/*
* 求C(n,m)=C(n,n-m)的组合个数相同
*/
int main(int argc, char** argv) {
	int n;
	int m;
	int count=0;
	int count_=0;
	cin>>n;
	do {
		cout<<"m小于n:"<<endl;
		cin>>m;
	} while(m>n);
	//求 n 的阶乘
	int nJc=getJc(n);
	//求 m!阶乘
	int mJc=getJc(m);
	//求 n-m 阶乘
	int nmJc=1;
	if (n!=m)
		nmJc=getJc(n-m);
	//求 C(n,m)的组合数
	count=nJc /  (mJc*nmJc);
	//求 C(n,n-m)的阶乘,根据公式可知,分母仅是交换了相乘两数的位置
	count_=nJc / (nmJc* mJc);
	if(count==count_) {
		//验证通过
		cout<<"OK"<<endl;
	} else {
		cout<<"NO"<<endl;
	}
	return 0;
}

3.2 运算法则二

如下图所示,当从 n中取m-1和 m个数字得到的组合总数,可归纳为求解 n+1中取 m个数字的组合数。

4.jpg

直接套用公式验证 C(3,1)+C(3,2)C(4,2)的结果:

  • 3 个数字选择 1 个数字进行组合,结果=3!/1!*2!=3
  • 3个数字选择2个数字进行组合,结果=3!/2!*1!=3
  • 4个数字选择2个数字进行组合,结果=4!/2!*2!=6

结论是: C(3,1)+C(3,2)=C(4,2)

Tips: mm-1必须连续!如C(4,2)+C(4,4)并不等于C(5,4)C(7,3)+C(7,4)=C(8,4) 是成立的。

根据场景验证:

  • 如果有 4 名学生,需要 2 名学生留下来搞卫生,显然,可选择方案有 C(4,2)=4!/2!*2!=6种方案。

5.png

  • 换一种理解,如果学号为 1 的学生必须留下来,显然,只需要在剩下的 3 名学生中选择 1 名学生留下来。如果学号为 1的学生不留下来,则需要从剩下的 3 名学生中选择 2 名。

6.png

严格的证明,可以由原始公式直接推导。如下图所示:

12.png

编程实现:

#include <iostream>
#include <cmath>
using namespace std;
/*
*求阶乘函数省略……
*/

/*
* 求证 C(n,m)+C(n,m-1)=C(n+1,m)
*/
int main(int argc, char** argv) {
	int n;
	int m;
	int count=0;
	int count_=0;
	cin>>n;
	do {
		cout<<"m小于n:"<<endl;
		cin>>m;
	} while(m>n || m<1) ;
	//求 n 的阶乘
	int nJc=getJc(n);
	//求 m!阶乘
	int mJc=getJc(m);
	//求 n-m 阶乘
	int nmJc=1;
	if (n!=m)
		nmJc=getJc(n-m);
	//求 m-1 的阶乘
	int moneJc= getJc(m-1);
	//求 n-(m-1) 的阶乘
	int nmoneJc= getJc(n-m+1 );
	//求 n+1-m 阶乘
	int nonemJc= getJc( n+1-m );
	//求C(n,m)+C(n,m-1) 的组合数
	count=nJc /  (mJc*nmJc) + nJc / ( moneJc * nmoneJc ) ;
	//求 C(n+1,m)的阶乘 根据公式可知,分母仅是交换了相乘两数的位置
	count_= nJc*(n+1) / (mJc* nonemJc);
	if(count==count_) {
		//验证通过
		cout<<"OK"<<endl;
	} else {
		cout<<"NO"<<endl;
	}
	return 0;
}

3.3 运算法则三

如下图所示:

5.jpg

先直接套用公式验证 C(2,0)+C(2,1)+C(2,2) 的结果。

  • C(2,0) 2 个数字中选择 0个,结果为 1
  • C(2,1) 2个数字中选择 1个,结果为 2!/1!*1!=2
  • C(2,2) 2个数字中选择 2个,结果为 1
  • 所以 C(2,0)+C(2,1)+C(2,2)=4 和 22 结果一样。但这只是个例,不足以证明普适性。

用另一种方式验证公式的合理性:假设现有一个箱子,里面有 2 个苹果,请问选择任意个苹果数的方案有多少种?

方案一:你的角度。

  • 不选择(C(2,0)),可以认为是 1 种方案。
  • 选择 1 个苹果(C(2,1)),可以在 2 个苹果中任一个,则有 2 种方案。
  • 选择 2 个苹果(C(2,2)),只有 1 种方案。
  • C(2,0)+C(2,1)+C(2,2)=4

方案二:苹果的角度。

  • 每个苹果都是独立的个体,可以出来,也可以不出来。所以每个苹果都有 2 种选择。
  • 箱子中现在有 2 个苹果,根据乘法原理,也就是 22 相乘( 22 次方 )。
  • 所以 22=4。如果有 3 个苹果,则共有 23 种方案。

编程验证:

#include <iostream>
#include <cmath>
using namespace std;
/*
*求阶乘函数省略
*/

/*
* 求证 C(n,0)+C(n,1)+C(n,n)=2^n
*/
int main(int argc, char** argv) {
	int n;
	cin>>n;
	//当不取,或取全部时,组合个数都为 1
	int s=2;
	//求 n 阶乘
	int nJc =getJc(n);
	for(int i=1; i<n; i++) {
		//求 i 阶乘
		int iJc=getJc(i);
		//求 n -i 阶乘
		int niJc=getJc(n-i);
		s+= nJc / (iJc*niJc );
	}
	//求 2 的 n 次方
	int res= pow(2,n);
	if(res==s) {
		cout<<"OK"<<endl;
	} else {
		cout<<"NO"<<endl;
	}
	return 0;
}
//输入 2 
//输出 OK

3.4 运算法则四

如下图所示:

6.jpg

Tips: 组合公式的上面的数字是相同的,下面的数字必须连续。

可以用选择值日生的例子推导公式的正确。如果需要在学号为 1、2、3、4、5 的这 5 名学生中选择 3 名学生留下来当值日生,且必须在选择出来的 3名学生中指定一人为组长。则选择方案可以由以下的分解方式组成:

  • 学号为 1学生当组长,则只需要在剩下的 4名学生中选择2名学生,即 C(4,2)
  • 学号为 2学生当组长,则只需要在剩下的 3名学生中选择2名学生,即 C(3,2)
  • 学号为 3学生当组长,则只需要在剩下的 2名学生中选择2名学生,即 C(2,2)
  • 学号 4,5当组长,剩下人数不够凑成 3 个。没得选择。
  • C(5,3)=C(4,2)+C(3,2)+C(2,2)

3.5 运算法则五

如下图所示:

7.jpg

还是以上面的值日生为例,现在有 7 名学生,43 女,需要从 7 人中选择 3 人留下来值日,其组合数为 C(7,3),在所有组合数中一定出现如下的搭配:

  • 没有男生 C(4,0),则选择女生(3,3),即选择方案有 C(4,0)*C(3,3)
  • 选择 1 名男 C(4,1),则选择女生 C(3,2),选择出来的男生可以和选择出来的任一组女生搭配,显然方案数是 C(4,1)*C(3,2)
  • 选择 2 名男 C(4,2),则选择女生 C(3,1),共有 C(4,2)*C(3,1)种方案。
  • 选择 3 名男 C(4,3),则选择女生 C(3,0),共有 C(4,3)*C(3,0)种方案。

4. 总结

排列和组合公式是在数学上已经验证过的公式,本文中所提供的代码,都是使用此公式,解决具体的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/101609.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker镜像

镜像是一种轻量级、可执行的独立软件包&#xff0c;它包含运行某个软件所需的所有内容&#xff0c;我们把应用程序和配置依赖打包好形成一个可交付的运行环境(包括代码、运行时需要的库、环境变量和配置文件等)&#xff0c;这个打包好的运行环境就是image镜像文件。 只有通过这…

【设计模式】工厂方法模式Factory(Java)

文章目录1. 定义2. 类图3. Java实现案例3.1 抽象类&#xff1a;Pizza和PizzaStore3.2 具体披萨&#xff1a;北京两种上海两种共四种3.3 具体披萨店&#xff1a;北京店和上海店3.4 测试主方法1. 定义 工厂方法模式定义了一个创建对象的接口&#xff0c;但由子类决定要实例化的类…

基于JAVA的XX公司固定资产管理系统的设计与实现

开发工具(eclipse/idea/vscode等)&#xff1a; 数据库(sqlite/mysql/sqlserver等)&#xff1a; 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a; 本课题研究对象是中小企业财务管理系统&#xff0c;设计采用自己开发实践和所学知 识&#xff0c;系统部分主要分为以下…

汽车主机厂Adams/Car悬架动力学开发最全攻略

​​​​​​​一、写在前面 实际经历告诉我们&#xff0c;当我们接触一个新事物或学习一项新的技能时&#xff0c;入门往往是最为困难的&#xff0c;迷茫、彷徨、无助…… 正是基于同样的经历&#xff0c;在掌握Adams/Car软件的应用后&#xff0c;作者即开始构思如何将自己的…

论文投稿指南——中文核心期刊推荐(电工技术)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

linux后台自定义后台服务service(已filebeat举例)

文章目录一、配置攥写1&#xff09;安装filebeat和配置相关修改2&#xff09;常用命令二、启动顺序1&#xff09;命令循序2&#xff09;systemctl添加自定义系统服务&#xff08;服务填写指南&#xff09;3&#xff09;linux的systemctl命令详解及使用教程三、遇到的坑点和报错…

One-Hot 独热编码

1. 什么是独热编码 独热编码&#xff0c;又称一位有效编码。采用N位状态寄存器来对N个状态进行编码&#xff0c;直观来说就是有多少个状态就有多少比特&#xff0c;除了有效的比特为1外&#xff0c;其他都为0. 2. 独热编码过程 &#xff08;1&#xff09;将分类值映射到整数…

Simulcast与SVN

什么是Simulcast: 一个客户端向服务器发送高清&#xff0c;标清&#xff0c;低清三种视频流&#xff0c;服务器根据其他接收客户端的带宽情况分发不同的视频流。Simulcast不仅有客户端的工作&#xff0c;还有服务器的工作。 开启Simulcast的三种方式: Munging SDP方式 添加assr…

网络实验①——同Vlan下相互通信

实验要求&#xff1a; pc0与pc1互通pc2与pc3互通实验步骤&#xff1a; A交换机配置&#xff1a; enable config t hostname switch-A vlan 10 vlan 20 exit interface f0/1 switch access vlan 10 no shutdown interface f0/2 switch access vlan 20 no shutdown interface f0/…

无线耳机哪个品牌好一点?真无线蓝牙耳机推荐品牌

蓝牙耳机随着近几年的快速发展&#xff0c;已经成为了人们外出时必不可少的数码产品之一。而现如今&#xff0c;市面上的蓝牙耳机品牌众多&#xff0c;挑选起来让人眼花缭乱&#xff0c;人们在选择时不免陷入纠结。那么&#xff0c;无线耳机哪个品牌好一点&#xff1f;下面&…

day23【代码随想录】翻转二叉树、对称二叉树、相同的树、另一棵树的子树、完全二叉树的结点个数

文章目录前言一、翻转二叉树&#xff08;力扣226&#xff09;1、递归法1、使用前序遍历2、使用后序遍历2、迭代法1、层序遍历二、对称二叉树&#xff08;力扣101&#xff09;三、相同的树&#xff08;力扣100&#xff09;四、另一棵树的子树&#xff08;力扣572&#xff09;五、…

[附源码]Nodejs计算机毕业设计教师信息采集系统Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

docker容器中DPDK对网卡SR-IOV支持实验

主要是验证下容器运行DPDK&#xff0c;如何对SR-IOV进行支持 1 VF创建方法 在未开启SR-IOV时&#xff0c;通过DPDK提供的./dpdk-devbind.py脚本可知&#xff0c;当前系统一共有一块82599网卡&#xff0c;拥有2个网口&#xff0c;PCI的地址是18:00.0和18:00.1,如下图所示 启用…

华为配置动态路由ISIS协议

华为配置动态路由ISIS协议一、路由基础知识二、路由器配置接口IP地址&#xff08;一&#xff09;配置R1、R2、R3网络&#xff08;二&#xff09;配置R1、R2、R3环回网络接口&#xff08;三&#xff09;测试直连网络三、启动进程号&#xff0c;配置实体名称&#xff08;一&#…

JVM之堆

堆的基本内容&#xff1a; Java堆&#xff08;Java Heap&#xff09;是虚拟机所管理的内存中最大的一块&#xff0c;Java堆是被所有线程共享的一块内存区域&#xff0c;在虚拟机启动时创建&#xff0c;此内存区域的唯一目的就是存放对象实例&#xff0c;Java 世界里“几乎”所…

【每日挠头算法题】Leetcode 989. 数组形式的整数加法 —— 高精度加法解法

&#x1f451;作者主页&#xff1a;进击的安度因 &#x1f3e0;学习社区&#xff1a;进击的安度因&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;每日挠头算法题 文章目录一、题目描述二、思路及代码实现今天为大家带来的是力扣上的一道简单题&#xff1…

Spring的秒表StopWatch优雅的程序计时器 -第455篇

历史文章&#xff08;文章累计450&#xff09; 《国内最全的Spring Boot系列之一》 《国内最全的Spring Boot系列之二》 《国内最全的Spring Boot系列之三》 《国内最全的Spring Boot系列之四》 《国内最全的Spring Boot系列之五》 走进MyBatis源码一探Spring扩展点「知识…

排水监测传感器数据采集传感节点接入网关产品介绍

一、产品概述 传感节点接入网关是一款工业级网关&#xff0c;可将Modbus RTU协议的设备数据转换为MQTT协议格式再远传至数据中心&#xff0c;支持采集数字量输入信号&#xff0c;支持输出数字量控制信号。 传感节点接入网关具备一体化采集、传输、控制功能&#xff0c;集成了…

IDEA自定义JavaDOC注释模板

文章目录IDEA自定义JavaDOC注释模板方法注释模板1、创建模板2、设置模板内容-JavaDoc格式3、Define4、Edit VariablesIDEA自定义JavaDOC注释模板 JavaDOC注释模板共分为两种&#xff1a; 类注释模板方法注释模板 方法注释模板 1、创建模板 File–>Settings–>Editor…

FixMatch: Simplifying Semi-Supervised Learning with Consistency and Confidence

FixMatch: Simplifying Semi-Supervised Learning with Consistency and Confidence, NIPS, 2020 要点&#xff1a; 1、首先&#xff0c;基于 “弱增强的未标记样本的模型预测” 生成伪标签&#xff1b;接着&#xff0c;对于给定的样本&#xff0c;只有模型预测的置信度高时&a…