C语言中递归和排列组合详解

 更新时间:2022年01月13日 10:08:04   作者:布布要成为最强的人  
大家好,本篇文章主要讲的是C语言中递归和排列组合详解,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览

排列组合三大问题:

1.打印n个数的全排列
2.打印n个数中任意m个数的全排列
3.打印n个数中任意m个数的组合

1.打印n个数的全排列

这个题实际上是可以直接用STL中的next_permutation()函数,代码如下:

#include<bits/stdc++.h>
using namespace std;
int main(){
	int data[4]={5,2,4,1};
	sort(data,data+4);//先排序得到字典序最小的序列
	do{
		for(int i=0;i<4;i++)
			cout<<data[i]<<" ";
		cout<<endl;
	}while(next_permutation(data,data+4));
}
这样输出出来的全排列是按照字典序输出的,这是它的优点。

如果用递归求全排列呢?
假如给了n个数123…n,求其全排列的数量,应当如何解决呢,下面给出一个递归的思路:

一开始先按照字典序排列,然后把第一个数依次和后面的数交换:
1 2 3 4 5…n
2 1 3 4 5…n
.
.
.
n 2 3 4 5…1
这是第一层递归,只要第一个数不同,不需要管后面n-1个数

然后在上面的每个数列中去掉第一个数,对后面的n-1个数做如上操作,例如取第二组做该操作,则该第二层的递归为:
1 3 4 5…n
3 1 4 5…n
.
.
.
n 3 4 5…1

重复以上步骤,直到用完所有的数字。

这么讲并不好理解,我从小规模到大规模来阐述这个思想:

假如只有两个数1,2需要进行全排列工作:
先按字典序排成1,2,这是第一层递归的第一组
把1去掉,只留下一个数,那么只有1种情况。
第一层递归的第二组是2,1,这也是最后一组了
把2去掉,只留下一个数,那么只有1种情况
因此两个数的全排列是两种情况

假如有三个数1,2,3需要进行全排列工作:
直接看第一层递归的三种情况:
1、2、3;2、1、3;3、2、1
每一种情况都把第一个数去掉,就变成只有2个数的全排列了
而由上述所知,两个数的全排列有两种情况
那么第一层递归的三种情况都各自包含两种情况即3×2=6

往后依旧借用前面的标准即可。
可是放到代码实现的时候可不能做完一层删一个数,只能实现的了保留那层递归的第一个数,然后继续对下面的数做递归操作,这样就完美符合了递归的思想。
代码实现如下:

#include<bits/stdc++.h>
using namespace std;
#define Swap(a,b){int temp=a;a=b;b=temp;}
//也可以用STL的swap函数,但是速度慢一些
int data[]={1,2,3,4,5};
int num=0;
void Perm(int begin,int end){
    if(begin==end)num++;//递归到底了,自然只有一种情况,num++
    else{
        for(int i=begin;i<=end;i++){
        	//i要注意从begin开始,自己和自己换的也算是一种情况
            Swap(data[begin],data[i]);
            Perm(begin+1,end);//保留第一个数,进入下一层递归
            Swap(data[begin],data[i]);//要记得换回来
        }
    }
}
int main(){
	Perm(0,4);
	cout<<num<<endl;
}

如果想要输出这个排列,直接在Perm函数中的if语句下面做循环输出即可。
需要注意的是:这样输出出来的并不一定符合字典序。

2.打印n个数中任意m个数的全排列

这个只需要把上面if语句中的条件改一下就行,改成begin==m即可
思路是一样的,从小规模列起就好了。

3.打印n个数中任意m个数的组合

这个和上面的第2个问题就不一样了,组合问题只需要选m个数而无须做排列,应该怎么实现呢?
利用二进制的思想,原理如下:
设一个集合{a0,a1,a2,…,an-1},子集共有2的n次方个,其中包括空集。
例如一个n=3的集合{a0,a1,a2},其子集为{φ},{a0},{a1},{a1,a0},{a2},{a2,a0},{a2,a1},{a2,a1,a0}。为什么以这个顺序来排呢?因为这样非常符合二进制位权值的思想。刚好可以和二进制对应:

φa0a1a1 a0a2a2 a0a2 a1a2 a1 a0
000001010011100101110111

如何输出这些子集?,还是利用二进制位权的思想,利用相与运算得出其二进制数中的每一个1,直接对应数字,完全代码如下:

#include<bits/stdc++.h>
using namespace std;
void print_subset(int n){
    for(int i=0;i<(1<<n);i++){
        for(int j=0;j<n;j++)//打印子集,即打印i的二进制数中的每一个1
            if(i&(1<<j))
                cout<<j<<" ";
        cout<<endl;
    }
}
int main(){
	int n;
	cin>>n;
	print_subset(n);
}

回到问题3,要找到任意m个数的组合,只需要做一个判断:确定一个子集对应的二进制数中1的数量。这是解题的关键。
有一个很巧妙的做法:kk=kk&(kk-1)
重复使用该式子,直到kk为0,即可得出1的数量。

完整代码如下:

#include<bits/stdc++.h>
using namespace std;
void print_subset(int n,int k){
    for(int i=0;i<(1<<n);i++){
        int num=0,kk=i;
        while(kk){
            kk=kk&(kk-1);
            num++;
        }
        if(num==k){
            for(int j=0;j<n;j++)//打印子集,即打印i的二进制数中的每一个1
                if(i&(1<<j))
                    cout<<j<<" ";
            cout<<endl;
        }
    }
}
int main(){
	int n,k;
	cin>>n>>k;
	print_subset(n,k);
}

总结

到此这篇关于C语言中递归和排列组合详解的文章就介绍到这了,更多相关C语言递归和排列组合内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • c++11 符号修饰与函数签名、函数指针、匿名函数、仿函数、std::function与std::bind

    c++11 符号修饰与函数签名、函数指针、匿名函数、仿函数、std::function与std::bind

    这篇文章主要介绍了c++11 符号修饰与函数签名、函数指针、匿名函数、仿函数、std::function与std::bind,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-05-05
  • 利用C语言实现页面置换算法的详细过程

    利用C语言实现页面置换算法的详细过程

    一个好的页面置换算法,应具有较低的页面更换频率,从理论上讲,应该保留最近重复访问的页面,将以后都不再访问或者很长时间内不再访问的页面调出,下面这篇文章主要给大家介绍了关于利用C语言实现页面置换算法的相关资料,需要的朋友可以参考下
    2022-11-11
  • C++中AVL树的底层以及实现方法总结

    C++中AVL树的底层以及实现方法总结

    这篇文章主要介绍了C++中AVL树的底层以及实现方法的相关资料,AVL树是一种自平衡的二叉搜索树,每个节点的左右子树高度差不超过1,通过旋转操作保持平衡,详解了AVL树的结构、插入、旋转、查找和遍历方法,展示了其保持平衡的机制及对应代码实现,需要的朋友可以参考下
    2024-10-10
  • C语言编程中统计输入的行数以及单词个数的方法

    C语言编程中统计输入的行数以及单词个数的方法

    这篇文章主要介绍了C语言编程中统计输入的行数以及单词个数的方法,利用最基础的循环和判断语句写成,需要的朋友可以参考下
    2015-11-11
  • C++中实现调试日志输出

    C++中实现调试日志输出

    在 C++ 编程中,调试日志对于定位问题和优化代码至关重要,本文将介绍几种常用的调试日志输出方法,并教你如何在日志中添加时间戳,希望对大家有所帮助
    2025-01-01
  • 使用ShellClass获取文件属性详细信息的实现方法

    使用ShellClass获取文件属性详细信息的实现方法

    本篇文章是对ShellClass获取文件属性详细信息的实现方法进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • udp socket客户端和udp服务端程序示例分享

    udp socket客户端和udp服务端程序示例分享

    这篇文章主要介绍了udp socket客户端和udp服务端程序示例,需要的朋友可以参考下
    2014-03-03
  • C++代码实现逆波兰式

    C++代码实现逆波兰式

    这篇文章主要为大家详细介绍了C++代码实现逆波兰式,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-11-11
  • C++ VTK实例之高斯随机数的生成

    C++ VTK实例之高斯随机数的生成

    这篇文章主要介绍了VTK的一个实例之高斯随机数的生成,本文演示了从一个平均数是0.0和标准偏差是2.2的高斯分布中随机生成3个随机数。感兴趣的同学可以学习一下
    2021-11-11
  • C语言获取文件长度的方法

    C语言获取文件长度的方法

    这篇文章主要介绍了C语言获取文件长度的相关知识,包括使用标准库方法和使用Linux系统调用,本文通过实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2023-10-10

最新评论