• 子集生成——回溯法的准备篇


    在如何获得全排列的文字里,大家会发现递归可以说随处可见,而在回溯法中,递归更是实现枚举的基本手段。在生成子集的方法中,我们也将看到递归的影子

    为了简单,这里不涉及可重集的子集

    递归不能深究详细的过程,而要注意第一步向第二步如何推广,以及推广后的递归如何在边界终止。

    一、增量构造法

    这种思路和按照字典序枚举全排列的思路一致,本质是递归地构造子集。

    思路等同于解答树:

    1. 如果子集非空,则输出
    2. 递归调用,将a[cur-1]+1不断赋值给a[cur],然后递归找出剩余的子集
    3. 边界终止条件隐式的递归条件,当没有元素可加入时,递归就会停止

    代码如下:

     1 //定义数据
     2 int a[5];
     3 void print_subset(int n,int *a,int cur){
     4      for(int i=0;i<cur;i++)printf("%d%c",a[i],i<cur-1?' ':'
    ');//对于要求严格的题目,一定要保证输出没有多余的空格
     5      int s=cur?a[cur-1]+1:0;//定序技巧,避免子集重复
     6      for(int i=s;i<n;i++){
     7          a[cur]=i;//将新的元素加入子集的第cur个位置
     8          print_subset(n,a,cur+1);
     9      }
    10  }
    11 int main(){
    12     print_subset(5,a,0);
    13 }

    二、位向量法

    位向量法与直接构造法的区别在于数组的使用方式不同,位向量法用数组下标表示子集元素,而数组内容为1或0,表示该元素 在 或者 不在 集合内

    注意,和直接构造不同,每个元素都有0和1(不在/在)两种方式,因此,位向量法的解答树节点数比直接构造法多出一倍减一个,例如直接构造法的10个元素的解答树,有2^10=1024个节点,而用位向量法,因为每个元素都有0和1两种状态,因此有2*1024-1=2047个节点(根节点都只有一个,所以不是单纯2倍关系),因此速度理论上要慢一些,但多数情况下仍然够用

    位向量法需要指定显式的递归边界,因为我们不能明确知道究竟一次有多少元素会被打印。

     代码如下:

     1 int a[5];
     2 void print_subset(int n,int *a,int cur){
     3     if(cur==n){//显式的递归边界
     4         for(int i=0;i<n;i++)
     5             if(a[i])printf("%d ",i);
     6         printf("
    ");
     7         return;
     8     }
     9     a[cur]=1;//每个位置有0和1两种状态
    10     print_subset(n,a,cur+1);
    11     a[cur]=0;
    12     print_subset(n,a,cur+1);
    13 }

    三、二进制法(最快最简单代码最少,限制是数字会爆)

    本质和位向量法相同,但是由于是二进制,因此一个整数(1<<n)-1就可以代表一个全集,这种实现接近计算机底层,加上C语言自身就支持二进制的运算,如&(交集),|(并集),^(对称差和开关性)等,因此速度和代码简洁性都优于位向量法。

     二进制法从右向左表示集合中的元素(从低位到高位)如0110是数字6,代表的集合是{ 1,2 },010111是数字23,代表的是{ 0,1,2,4 },n个元素所有的可能的情况有2^n个,即(1<<n)-1种

    实现如下: 

     1 void print_subset(int n,int s){//s为{0,1,。。,n-1}的子集 
     2     for(int i=0;i<n;i++)
     3         if(s&(1<<i))printf("%d ",i);
     4     if(s)printf("
    ");
     5 }
     6 int main(){
     7     int n=5;
     8     for(int i=0,len=1<<n;i<len;i++)//枚举子集
     9         print_subset(n,i);
    10 }

    其实我觉得我对于二进制还不是很熟悉,因此二进制法应当继续加深练习。今天就到这,拜拜~~~

  • 相关阅读:
    【题解】 「APIO2019」桥梁 操作分块+带权并查集 LOJ3145
    csp-s模拟测试42「世界线·时间机器·密码」
    csp-s模拟测试41「夜莺与玫瑰·玫瑰花精·影子」
    NOIP模拟测试38「金·斯诺·赤」
    NOIP模拟测试「简单的区间·简单的玄学·简单的填数·简单的序列」
    NOIP模拟测试34「次芝麻·呵呵呵·长寿花」
    理科卷math·english·chinese·biology·chemistry·physics
    NOIP模拟测试28「阴阳·虎·山洞」
    NOIP模拟测试30「return·one·magic」
    NOIP模拟测试29「爬山·学数数·七十和十七」
  • 原文地址:https://www.cnblogs.com/luruiyuan/p/5625427.html
Copyright © 2020-2023  润新知