0402递归学习

递归学习

递归——自己调用自己

常见写法:

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);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值