递归打印全排列ANN ANM CNM

#include <iostream>
#include <bits/stdc++.h>
using namespace std;

/*
递归打印全排列
方法1:next_permuation()
方法2:多重循环
方法3:递归
第一层:4个
1 2 3 4 
2 1 3 4
3 2 1 4
4 2 3 1
第二层:3个
1 2 3 4 为例
1 2 3 4
1 3 2 4
1 4 3 2

最后4*3*2*1 得到全排列
*/
int num = 0;//用来计数
//dep代表深度 也代表从哪里开始置换 end-1代表有多少层 从0层开始计算
void Perm(vector<int> data, int dep, int end)
{
    if (dep == end){
        num++;
        for (int i = 0; i <= end; i++)
            cout << data[i]<< " ";
        cout << endl;
    }
    else {
        for (int i = dep; i <= end; i++) {
            swap(data[dep], data[i]);
            Perm(data,dep+1,end);
            swap(data[dep], data[i]);
        }

    }
    
}
int main() {

    int i;
    cin >> i;
    vector<int> data;
    for (int k = 0; k < i; k++)
    {
        int num;
        cin >> num;
        data.push_back(num);
    }
    Perm(data,0,i-1);
    return 0;
}

今天重新温习递归,重点在于掌握思想 层的概念,所有层执行相同操作,最后设置终止条件,类似于树一样,举个例子 :

1234 ->

1234 2134 3214 4231->

1234 1324 1432 。。。

。。。

当递归到最终层数时候打印即可。4*3*2*1中可能

这时候我们可能想到如何得到n个数挑m个数的全排列 很简单减少递归层数即可,

当我们调用递归第一层时候 有四种情况 第一位各不相同 第二层调用时候 第一位和第二位互不相同

所以 当你想选m个数全排列的时候只需要递归m层输出前m个数即可,以下是代码修改和注释:

void Perm(vector<int> data, int dep, int end,int m)
{
    if (dep == m){//m从0开始计数 m=1 代表一层
        num++;
        for (int i = 0; i <= m-1; i++)//因为传递了m层 只有前m位互不相同 从0开始计数 所以是m-1
            cout << data[i]<< " ";
        cout << endl;
    }
    else {
        for (int i = dep; i <= end; i++) {
            swap(data[dep], data[i]);
            Perm(data,dep+1,end);
            swap(data[dep], data[i]);
        }

    }
    
}

打印CNM:

原理就是n个数每个元素设为0/1 总共有2^n种可能性

找1的个数为m的即可 得到对应十进制数获得数组下标

操作:

找1:kk=kk&(kk-1)会将最低位1变0 右侧0变1 与原来的kk相与 去掉最低位1 循环次数为1的个数

获得当前排列:

例如我们已经得到1101 怎末获得排列:

每个二进制位对应一个元素 共n位

遍历每一位

for(int j=0;j<n;j++)

(1<<j)&当前2进制 j位是1 会获得1 输出j就是对应的元素下标

代码如下:

#include <bits/stdc++.h>
using namespace std;
/*
打印n个元素 m个元素的子集C n m
前置知识:
位操作符
按位与 (&):
示例:result = a & b; // 按位与操作
按位或 (|):。
示例:result = a | b; // 按位或操作
按位异或 (^):
示例:result = a ^ b; // 按位异或操作
按位取反 (~):
示例:result = ~a; // 按位取反操作
左移 (<<):
用于将位向左移动,并在右侧补零。
示例:result = a << 1; // 左移一位
右移 (>>):
用于将位向右移动。
示例:result = a >> 1; // 右移一位

接下来我举例说明
1 2 3 的子集 2^3个 每个元素可以当作0 1
所以求m个元素的集合 就是找 元素排列中 1的个数为m的个数
*/
void print_set(int n, int m)//求n个元素 m个元素的子集
{
    //遍历 2^n中可能
    for (int i = 0; i <= (1 << n); i++)//(1<<n)代表1左移n位代表2^n
    {
        int num = 0;//对1的位置进行计数
        int kk = i;
        //关键求1的个数操作:kk=kk&(kk-1) 直到kk=0
        while (kk)
        {
            kk = kk & (kk - 1);
            num++;
        }
        if (num == m)//说明1的个数为m正确
        {
            //得到对应的1 共有n位
            for (int j = 0; j < n; ++j)
            {
                if ((1 << j) & i)//对应位置的1判断
                    cout << j<<" ";
            }
            cout << endl;

        }
    }

}
int main() {

 
    print_set(4, 2);
    return 0;
}

关键在于掌握 :

(kk-1)&kk 

(1<<n)

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值