递归学习
递归——自己调用自己
常见写法:
int digui(int i)
{
if( )//递归停止条件
{
}
else//继续进行的语句
{
digui(i++);//通常这里会改变传入的值
}
}
1.问题1:打印n个数的全排列
使用递归!
求 {1 2 3 4 5......n}的全排列的思路:
(1)让第一个数不同,得到n个数列(把第1个和后面每个数交换即可):
1 2 3 4 5......n
2 1 3 4 5......n
.....
n 2 3 4 5......1
以上n个数列,只要第一个数不同,不管后面n-1个数是怎么排列的,这n个数列都不同。这是递归的第一层。
(2)继续:在上面的每个数列中,去掉第一个数,对后面的n-1个数进行类似的排列。例如从上面第2行的{2 1 3 4 5......n}进入第二层(去掉首位2):
1 3 4 5......n
3 1 4 5......n
......
n 3 4 5......1
以上n-1个数列,只要第一个数不同,不管后面n-2个数是怎么排列的,这n-1个数列都不同。这是递归的第二层。
(3)继续以上过程,直到用完所有数字。
递归求 {1 2 3 4 5......n}的全排列的思路:
递归的第一层:n个情况。 递归的第二层:n-1个情况。 ...... 递归的第n层:1个情况。
总共有:n×(n-1)×(n-2)×......×1个情况
#include<iostream>
using namespace std;
int star[]= {1,2,3,4,5,6,7,8,9,10}; //0不用
#define Swap(a,b) {int temp = a; a = b; b = temp;} //交换
int num=0; //统计全排列的个数,验证是不是3628800
int Perm(int begin,int end)
{
int i;
if(begin == end) // 结束,输出一个全排列
{
num++; //统计全排列的个数;
//for(int i=0;i<10;i++)
//cout<<star[i]<<" ";
//cout<<endl;
}
else
for(i = begin; i <= end; i++)
{
Swap(star[begin],star[i]);//交换位置,逐步前提
Perm(begin+1,end);
Swap(star[begin],star[i]);//将位置还回去,对下一次排列负责
}
}
int main()
{
Perm(1,10); //求从第1个数到第10个数的全排列。
cout<<num<<"\n"; //打印出排列总数,应该是3628800。
}
递归(算法)求全排列(暴力法): (1)n个元素的全排列=(一个元素作为前缀)+(剩下n-1个元素的全排列); (2)结束:如果只有一个元素的全排列,说明已经排完,输出数组; (3)不断将每个元素放作第一个元素,然后将这个元素作为前缀,并将其余元素继续全排列,等到结束。出去后还需要还原数组。
问题2:打印n个数中任意m个数的全排列
例如在10个数中取任意3个数的全排列,在递归程序中只修改一个地方就可以了:
if(begin == 3)
{
// 把Perm()函数中的end改为3即可。
cout<<data[0]<<data[1]<<data[2]<<endl;
//打印10个数中3个数的全排列;
num++; //统计全排列的个数;
}
问题3:打印n个数中任意m个数的组合
先讨论子集生成问题: 一个包含n个元素的集合{a0, a1, a2, a3, ..., an-1}, 它的子集有{},{a0},{a1},{a2}, ..., {a0, a1, a2}, ..., {a0, a1, a2, a3, ..., an-1} 共2n个。
二进制法求子集:
例如n=3的集合{a0, a1, a2},它的子集和二进制数的对应关系是:
·每个子集对应一个二进制数;
·这个二进制数中的每个1,都对应了这个子集中的某个元素。
·子集的数量是2n个,因为所有二进制数的总个数是2n。
#include <bits/stdc++.h>
using namespace std;
void print_subset(int n){
for(int i=0; i< (1<<n); i++) {
//i:0~2n,每个i的二进制数对应一个子集。
for(int j=0;j<n;j++) //打印一个子集,即打印i的二进制数中所有的1。
if(i & (1<<j))
cout<< j << " ";
cout << endl;
}
}
int main(){
int n;
cin>>n; // n:集合中元素的总数量。
print_subset(n); // 打印所有的子集。
}
对照子集生成的二进制方法,很容易看出,在n个元素的集合中找k个元素的子集,这个子集对应了1的个数为k的二进制数。
如何判断二进制数中1的个数为k个?
一个神奇的操作:kk= kk & (kk - 1),它能消除kk的二进制数的最后一个1。 例如:7,二进制是111 ——111&(111-1) = 111&110 = 110。
利用这个操作,可以计算出二进制数中1的个数。 例如:7,二进制是111——111&(111-1) = 111&110 = 110,110&(110-1) =110&101 =100 ,100&(100-1) =100&011 =000 所以有3个1。
打印n个数中任意m个数的组合:
#include <bits/stdc++.h>
using namespace std;
void print_set(int n,int k){
for(int i = 0; i < (1<<n); i++) {
int num = 0, kk = i; //num统计i中1的个数;kk用来处理i。
while(kk){
kk = kk&(kk-1); //清除kk中最后一个1。
num++; //统计1的个数。
}
if(num == k){ //二进制数中的1有k个,符合条件。
for(int j = 0; j < n; j++)
if(i & (1<<j))
cout << j << " ";
cout << endl;
}
}
}
int main(){
int n,k; // n:集合中元素的总数量。k:个数为k的子集。
cin>>n>>k;
print_set(n,k);
}