子集和全排列问题

 列出所有子集(采用列出2进制数的方法)

 问题描述:列出给定集合的所有子集合,包括空子集。

        思路:一个集合的所有子集合的个数是个(n是集合中元素的个数),而一个位数为n的二进制也可以表示个数,所以,只要产生出了所有二进制数,就可以列出所有的子集了。在二进制的求解中,先来看这样一个例子。

11111 01111
+                 1
--------------------
11111 10000

          当这个数加1时,如果当前位是1,那么当前位就变成0并且向前进1位;接着前一位如果是1,也会变成0并且继续进位;以此类推,直到遇到当前位是0的情况,就变成1,然后整个加法就完成了。依照此思路,很容易写出代码。


  1. #include <stdio.h>
  2.  #include <math.h>
  3.  #define MAX 1000
  4.  
  5.  int n=4;// the number of the set elements
  6.  int set[MAX]={1,2,3,4};
  7.  int path[MAX]={0};
  8.  int count=1;
  9.  
  10.  //prototype
  11.  void print_set();
  12.  
  13.  int main()
  14.  {
  15.      int sum=(int)pow(2.0,n)-1;
  16.      int index,index_re;
  17.      printf("%d:{}\n",count);
  18.      for(index=0;index<sum;index++)
  19.      {
  20.          for(index_re=n-1;index_re>=0;index_re--) 
  21.          {
  22.              if(path[index_re]==1) 
  23.                  path[index_re]=0;
  24.              else
  25.              {
  26.                  path[index_re]=1;
  27.                  break;
  28.              }
  29.          }
  30.          count++;
  31.          printf("%d:{",count);
  32.          print_set();
  33.      }
  34.      return 0;
  35.  }
  36.  
  37.  void print_set()
  38.  {
  39.      int index;
  40.      for(index=0;index<n;index++)
  41.      {
  42.          if(path[index]!=0) 
  43.              printf("%d ",set[index]);
  44.      }
  45.      printf("}\n");
  46.  }

         参考资料:《C语言名题精选百则技巧篇》


列出所有子集-----字典顺序

问题描述:写一个程序,用字典顺序把一个集合的所有子集找出来。

         此题的思路来自《C语言名题精选百则技巧篇》:字典顺序,也就是字符串比较时的顺序规则。可以采取这样的思路

         先定义n是集合的个数并且集合是已经从小到大排好顺序的{1,2,3....n}的集合。集合从{1}开始(此时下标index=0),

         1.当state[i]<n时,就向右进行扩展,将state[2]=2;接着将state[3]=3;

         2.当state[index]==n时,就不能向右边进行扩展了,此时就需要向左边处理了。此时的集合是{1,2,3,....,n-2,n-1,n},那么,要找比这个集合刚好大一点的,就是{1,2,3,....n-2,n},所以就可以归纳出规则为:将index减1并且将state[index]加1。

         3.如果state[0]==n,那么循环就结束,反之则重复第1,2步,直到state[0]==n。

         代码如下:

复制代码
 1 #include <stdio.h>
 2 #define MAX  1000
 3 
 4 int main()
 5 {
 6     int n=3;
 7     int set[MAX]={1};
 8     int index=0;
 9     int count=2;
10     printf("1:{}\n2:{1}\n");
11     while(set[0]!=n)
12     {
13         if(set[index]<n)  
14         {   
15             set[index+1]=set[index]+1;
16             index++;
17         }
18         else
19         {
20             index--; 
21             set[index]++;
22         }
23         int a_index;
24         count++;
25         printf("%d:{",count);
26         for(a_index=0;a_index<=index;a_index++)
27             printf("%d ",set[a_index]);
28         printf("}\n");
29     }
30     return 0;
31 }
复制代码

12345

1235

124

1245

125

13

134

1345

135

14

145

15

2         子集不是全排列

参考资料:《C语言名题精选百则技巧篇》


产生全排列问题


[cpp]  view plain copy
  1. //全排列的递归实现  
  2. #include <stdio.h>  
  3. #include <string.h>  
  4. void Swap(char *a, char *b)  
  5. {  
  6.     char t = *a;  
  7.     *a = *b;  
  8.     *b = t;  
  9. }  
  10. //k表示当前选取到第几个数,m表示共有多少数.  
  11. void AllRange(char *pszStr, int k, int m)  
  12. {  
  13.     if (k == m)  
  14.     {  
  15.         static int s_i = 1;  
  16.         printf("  第%3d个排列\t%s\n", s_i++, pszStr);  
  17.     }  
  18.     else  
  19.     {  
  20.         for (int i = k; i <= m; i++) //第i个数分别与它后面的数字交换就能得到新的排列  
  21.         {  
  22.             Swap(pszStr + k, pszStr + i);  
  23.             AllRange(pszStr, k + 1, m);  
  24.             Swap(pszStr + k, pszStr + i);  
  25.         }  
  26.     }  
  27. }  
  28. void Foo(char *pszStr)  
  29. {  
  30.     AllRange(pszStr, 0, strlen(pszStr) - 1);  
  31. }  
  32. int main()  
  33. {  
  34.     printf("         全排列的递归实现\n");  
  35.     printf("  --by MoreWindows( http://blog.csdn.net/MoreWindows )--\n\n");  
  36.     char szTextStr[] = "123";  
  37.     printf("%s的全排列如下:\n", szTextStr);  
  38.     Foo(szTextStr);  
  39.     return 0;  

产生全排列——字典顺序

先讲递归的做法,n个元素一共有II!个排列,而1〜n都有可能出现在第一个位置,因为要用字符串顺序来排,所以这n!个排列是以1开头的(n-1)!个在前,接着是以2开始的 (n-1)!个,…,最后是以n开头的(n-1)!个。就用以i开头的为例,i已经在第1位,自第2 位起到第n位止中不会有i,因此以i开始的字典顺序以i 1 2 3…(i-1) (i+1)…(n-1) n 起,而以in (n-1)…(i+1) (i-1)…32 1结束。如果用递归方式,以字典顺序来排n-l个 元素的程序,不过是把该(n-1)!排列(其中不含i)排出来,接在i的后面而已。
当以i起头的(n-1)!个排完后,如何产生以i+1起头,字典顺序的下一个呢?再套用上 面的说法,这一组(n-1)!个排列以(i+1) 1 2 3…(i-1) i (i+2)…n起,到(i+1) n (n-1)···(i+2)i(i-1)…321止。可以把i起头的最后一个中的i+1 (它在倒数第i位)与i互换, 因此得到:
(i+1k)n(n-l)…(i+2)i(i-1)…32 1
再把后面的n-1个元素反转:
(i+1) 1 2 3 …(i-1)i(i+2)…(n-1)n
这不就是(i+1)起头的第一个元素吗?
在写程序时要注意:因为要处理在后面的n-1个元素,所以用两个变量L与R来指出要 处理的部分;如果用perrnu[]数组来存放一个排列的话,那么程序的架构就如程序12-7所示。

【程序】

while (1) {
排列perm [L+1]到permu [R]的部分;
把 perm [ L ]与 perm [ i ]互换;
把perm [L+1]到perm [R]反过来排;
i--;
}
当然,这还缺少递归的终止条件(以上面来看是永运不会停止的);不过这很简单,当 递归调用时,若L到R之间至少有3个元素,就做下去,而当只有两个元素时就不必递归 调用了,此时接下来的两步正好就是两个元素的排列(两个元素的排列只有两种,一种是 输入,另一种是两个元素互换——反过来排)。由此,递归韹序就可以写出来,这就是程序 PERMU—LR.C。

【问题实现】
C代码   收藏代码
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3.   
  4. #define  LOOP         1  
  5. #define  SWAP(a,b)    { int t; t = a; a = b; b = t; }  
  6. #define  REVERSE(a,b) { int i, j;                            \  
  7.                         for (i=(a), j=(b); i < j; i++, j--)  \  
  8.                              SWAP(perm[i], perm[j]);         \  
  9.                       }  
  10. #define  DISPLAY(n)   { int i;                               \  
  11.                         printf("\n");                        \  
  12.                         for (i = 0; i < n; i++)              \  
  13.                              printf("%d ", perm[i]);         \  
  14.                       }  
  15.                           
  16. void  again(int  perm[], int L, int R)  
  17. {  
  18.      int  i = R;  
  19.   
  20.      while (LOOP) {  
  21.           if (R - L + 1 > 2) {  
  22.                again(perm, L+1, R);  
  23.                DISPLAY(R);  
  24.           }  
  25.           if (i > L ) {  
  26.                SWAP(perm[L], perm[i]);  
  27.                REVERSE(L+1, R);  
  28.                DISPLAY(R);  
  29.                i--;  
  30.           }  
  31.           else  
  32.                break;  
  33.      }  
  34. }  
  35.   
  36.   
  37. void permut(int perm[], int n)  
  38. {  
  39.      int  i;  
  40.   
  41.      for (i = 0; i < n; i++)  
  42.           perm[i] = i + 1;  
  43.      again(perm, 0, n-1);  
  44. }  
  45.   
  46.   
  47. #define    MAXSIZE   20  
  48.   
  49. void main(void)  
  50. {  
  51.      int   perm[MAXSIZE];  
  52.      char  line[100];  
  53.      int   n;  
  54.   
  55.      gets(line);  
  56.      n = atoi(line);  
  57.      permut(perm, n);  
  58. }  
 


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回溯法是一种常用于解决组合问题的算法,可以用来解决子集排列问题。 对于子集问题,可以使用回溯法来求解。具体步骤如下: 1. 定义一个数组来存储当前的子集,初始为空集。 2. 从原始数组的第一个元素开始,依次将元素加入到当前的子集中,并计算子集的和。 3. 如果子集的和等于目标和,则将当前的子集加入到结果集中。 4. 如果子集的和小于目标和,则继续向下搜索,将下一个元素加入到子集中。 5. 如果子集的和大于目标和,则回溯到上一个元素,将其从子集中删除,并继续向下搜索。 6. 如果已经搜索到原始数组的最后一个元素,且子集的和不等于目标和,则回溯到上一个元素,将其从子集中删除,并继续向下搜索。 7. 重复步骤2到步骤6,直到搜索完所有的子集。 对于排列问题,也可以使用回溯法来求解。具体步骤如下: 1. 定义一个数组来存储当前的排列,初始为空。 2. 从原始数组的第一个元素开始,依次将元素加入到当前的排列中。 3. 如果当前的排列长度等于原始数组的长度,则将当前的排列加入到结果集中。 4. 如果当前的排列长度小于原始数组的长度,则继续向下搜索,将下一个元素加入到排列中。 5. 如果当前的排列中已经包含了某个元素,则回溯到上一个元素,将其从排列中删除,并继续向下搜索。 6. 如果已经搜索到原始数组的最后一个元素,且当前排列长度不等于原始数组的长度,则回溯到上一个元素,将其从排列中删除,并继续向下搜索。 7. 重复步骤2到步骤6,直到搜索完所有的排列。 需要注意的是,在回溯过程中,需要维护一个访问数组,用来记录哪些元素已经被访问过,避免重复访问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值