枚举、尺取、排列组合、二进制法

文章介绍了枚举技术在解决算法问题中的应用,包括排列、组合的实现,如全排列的递归算法,以及利用二进制法生成集合子集。此外,还提到了尺取法和指针i、j的两种扫描方式在处理数组问题中的作用。文章强调了枚举时注意剪枝和优化的重要性。
摘要由CSDN通过智能技术生成

目录

枚举技术:排列

手写组合代码:二进制法

尺取法

指针i、j的两种方向


枚举法

  1. 需要处理大量同类情况

  2. 暴力枚举所有情况

  3. 利用计算机强大的算力

注意:

  1. 不要遗漏任何情况

  2. 如果枚举量太大,需要剪枝

枚举的思想:将问题的所有可能成为答案的解一一列举,然后根据问题所给出的条件判断此解是否合适,如果合适就保留,反之则舍弃。

枚举解题的要素:确定枚举解的范围,以及判断条件选取合适枚举方法,进行逐一枚举,此时应注意能否覆盖所有的可能的解在枚举时使用判断条件检验,留下所有符合要求的解。

枚举的步骤

  1. 根据题目确定枚举的范围,并选取合适的枚举方式,不能遗漏任何一个真正解,同时避免重复。

  2. 为了提高解决问题的效率,看题目是否存在优化,将可能成为解的答案范围尽可能缩小。

  3. 根据问题找到合理、准确描述、易编码的验证条件。

  4. 枚举并判断是否符合第3步确定的的条件,并保存符合条件的解。

  5. 按要求输出枚举过程中留下的符合条件的解。

枚举技术:排列、组合

把所有情况排列、组合出来,逐个处理

 

//全排(组合型枚举)
int n;//共计N个数
int m;//选m个数
vector<int> chosen;
void calc(int x) {
    if (chosen.size() > m || chosen.size() + (n - x + 1) < m) //剪枝
        return;
    if (x == n + 1) { //选够了m个数输出
        for (int i = 0; i < chosen.size(); i++)
            printf("%d ", chosen[i]);
            //也可以不输出,存放起来也是可以的,主要是看题目。
        puts("");
        return;
    }
    calc(x + 1);
    chosen.push_back(x);
    calc(x + 1);
    chosen.pop_back();//消除痕迹
}
int main()
{
    cin>>n>>m;
     calc(1);
}

//全排(排列型枚举)
int n; //共计N个数
int order[20];
bool chosen[20];
void calc(int k)
{
    if (k == n + 1)
    {
        for (int i = 1; i <= n; i++)
            cout << order[i] << " ";
•        puts("");
•        return;
    }
    for (int i = 1; i <= n; i++)
    {
        if (chosen[i])
            continue;
        order[k] = i;
        chosen[i] = 1;
        calc(k + 1);
        chosen[i] = 0;
        order[k] = 0;
    }
}
int main()
{
    cin >> n;
    calc(1);
}

枚举技术:排列

C++ STL:

求“下一个”排列组合的函数next_permutation()。

返回值:如果没有下一个排列组合,返回false,否则返回true。

每执行next_permutation()一次,会把新的排列放到原来的空间里。

注意,它排列的范围是[first, last),包括first,不包括last。next_permutation()从当前的全排列开始,逐个输出更大的全排列,而不是输出所有的全排列。

#include <bits/stdc++.h>
using namespace std;
int main(){
    string s="bca";
    sort(s.begin(), s.end());  //字符串内部排序,得到最小的排列“abc”
    do{
       cout<<s<<"\n";
    }while(next_permutation(s.begin(),s.end()));
    return 0;
}

输出结果:

abc
acb
bac
bca
cab
cba

手写组合代码:二进制法

一个包含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},它的子集和二进制数的对应关系是:

子集φa0a1a1, a0a2a2, a0a2, a1a2, a1, a0
二进制数000001010011100101110111

每个子集对应了一个二进制数。二进制数中的每个1,对应了子集中的某个元素。

子集中的元素,是不分先后的,这正符合组合的要求。

#include<bits/stdc++.h>
using namespace std;
int a[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14};
void print_subset(int n){
    for(int i=0;i<(1<<n);i++) {
      //i:0~2^n,每个i的二进制数对应一个子集。一次打印一个子集,最后得到所有子集
​
        for(int j=0;j<n;j++)//打印一个子集,即打印i的二进制数中所有的1
​
           if(i & (1<<j))    //从i的最低位开始,逐个检查每一位,如果是1,打印
               cout<<a[j]<<" ";
        // & 同时为1 才为1       
        cout<<"\n";
    }
}
int main(){
    int n=3;  print_subset(n);        // 打印前n个元素a[0]~a[n-1]的所有子集
}

理解:

0001 0010 0011 0100 0101 0110 0111 1 2 3 4 5 6 7

n = 0 , 1 , 2; 原n: 0000 0001 0010 左移后: 0001 0010 0100 1 2 4

与上面的1~7对应,如果同时为1则输出,否则不输出。

尺取法

两种写法:

第一种:

int i = 0, j = n - 1;
while (i < j) {
       ......         
       i++;          //i从头扫到尾
       j--;          //j从尾扫到头
}

第二种:

for (int i = 0, j = n - 1; i < j; i++, j--) {
    ......
}

【问题描述】输入n个整数,放在数组a[]中。找出其中的两个数,它们之和等于整数m。

(1)对数组从小到大排序;

(2)i和j分别指向头和尾,i和j向中间移动:

  • a[i] + a[j] > m,让j减1:大的变小

  • a[i] + a[j] < m,让i加1:小的变大

  • 直至a[i] + a[j] = m

指针i、j的两种方向

反向扫描:i、j方向相反,i从头到尾,j从尾到头,在中间相会。 “左右指针”

同向扫描:i、j方向相同,都从头到尾,速度不同,例如让j跑在i前面。 “快慢指针”

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值