从数组中取出n个元素的组合

转载自http://www.cnblogs.com/shuaiwhu/archive/2012/04/27/2473788.html 作者:Microgoogle

    如数组为{1, 2, 3, 4, 5, 6},那么从它中取出3个元素的组合有哪些,取出4个元素的组合呢?比如取3个元素的组合,我们的思维是:取1、2,然后再分别取3,4,5,6;取1、3,然后再分别取4,5,6;......取2、3,然后再分别取4,5,6;......这样按顺序来,就可以保证完全没有重复。这种顺序思维给我们的启示便是这个问题可以用递归来实现,但是仅从上述描述来看,却无法下手。

    我们可以稍作改变:1.先从数组中A取出一个元素,然后再从余下的元素B中取出一个元素,然后又在余下的元素C中取出一个元素2.按照数组索引从小到大依次取,避免重复

依照上面的递归原则,我们可以设计如下的算法,按照索引从小到大遍历:

  1. //arr为原始数组  
  2. //start为某位置选取元素的开始位置,因为之后要选择的元素序号需要大于start,所以需要这个变量进行标记  
  3. //result保存一次组合选取的元素的序号,比如第一次选的是1234,则result中则存储1234,之后根据序号进行输出  
  4. //count为result数组的索引值,起辅助作用;count用来确定现在在选的是第几位数,或者还剩几位数就凑够要选择的数的个数;是递归变量之一:递归变量就是每次递归都会起作用或者发生改变的变量,在画递归树分析的时候不止要写出每次的递归变量值,而且对递归变量的限制(例如在循环中,有循环条件限制),也都要列出来,才比较容易模拟清楚过程;而且要尽量选规模小的进行模拟;  
  5. //NUM为要选取的元素个数  
  6. //arr_len为原始数组的长度,为定值  
  7. void combine_increase(int* arr, int start, int* result, int count, const int NUM, const int arr_len)  
  8. {  
  9.   int i = 0;  
  10.   for (i = start; i < arr_len + 1 - count; i++)  //i=start是为了使后续选择的元素都比之前选的元素序号大,避免重复。
  11.   {                                            //i<arr_len+1-count是因为假如第一个元素后面还要选count-1个比它大的
  12.     result[count - 1] = i;                     //那么它最多只有arr_len-(count-1)种选择,而且数组从0开始,所以
  13.     if (count - 1 == 0)                       //有了循环约束条件
  14.     {  
  15.       int j;  
  16.       for (j = NUM - 1; j >= 0; j--)           //当只剩最后一个数需要选择的时候,就循环输出所有的其他位确定的组合
  17.         printf("%d\t",arr[result[j]]);  
  18.       printf("\n");  
  19.     }  
  20.     else  
  21.       combine_increase(arr, i + 1, result, count - 1, NUM, arr_len); //还有多于一位数需要选择的时候,就递归选择剩                                                                      //下数中少一位的组合,直到只剩一位; 
  22.   }  
  23. }  
 

        如果从状态转移的想法看,代码是利用当组合位数大于一时循环寻找组合位数少一的组合,组合位数为一的时候循环输出整个数组结果,然后不管什么情况下都确定一位。

        这里状态转移的特点是它是循环递归的,所以在考虑状态变化的时候考虑不清楚。另外在理解递归的过程中,画递归树模拟很重要。

        以下是转载的按照索引从大到小进行遍历的代码:

[cpp]  view plain copy
  1. //arr为原始数组  
  2.  //start为遍历起始位置  
  3.  //result保存结果,为一维数组  
  4.  //count为result数组的索引值,起辅助作用  
  5.  //NUM为要选取的元素个数  
  6.  void combine_decrease(int* arr, int start, int* result, int count, const int NUM)  
  7.  {  
  8.    int i;  
  9.    for (i = start; i >=count; i--)  //这里只要保证有比序号还小的count-1个数就可以
  10.    {  
  11.      result[count - 1] = i - 1;  
  12.      if (count > 1)  
  13.      {  
  14.        combine_decrease(arr, i - 1, result, count - 1, NUM);  
  15.      }  
  16.      else  
  17.      {  
  18.        int j;  
  19.        for (j = NUM - 1; j >=0; j--)  
  20.      printf("%d\t",arr[result[j]]);  
  21.        printf("\n");  
  22.      }  
  23.    }  
  24.  }  
测试代码:
[cpp]  view plain copy
  1. #include <stdio.h>  
  2.    
  3.  int main()  
  4.  {  
  5.    int arr[] = {1, 2, 3, 4, 5};  
  6.    int num = 4;  
  7.    int result[num];  
  8.    
  9.    combine_increase(arr, 0, result, num, num, sizeof(arr)/sizeof(int));  
  10.    printf("分界线\n");  
  11.    combine_decrease(arr, sizeof(arr)/sizeof(int), result, num, num);  
  12.    return 0;  
  13.  }  

结果:

[cpp]  view plain copy
  1. 1   2   3   4     
  2. 1   2   3   5     
  3. 1   2   4   5     
  4. 1   3   4   5     
  5. 2   3   4   5     
  6. 分界线  
  7. 5   4   3   2     
  8. 5   4   3   1     
  9. 5   4   2   1     
  10. 5   3   2   1     
  11. 4   3   2   1     
现在来看poj1753的解法:转载自microGoogle http://www.cnblogs.com/shuaiwhu/archive/2012/04/27/2474041.html

POJ 1753 Flip Game (递归枚举)

POJ 1753,题目链接http://poj.org/problem?id=1753,翻译一下整个题目的大概意思:
有4*4的正方形,每个格子要么是黑色,要么是白色,当把一个格子的颜色改变(黑->白或者白->黑)时,其周围上下左右(如果存在的话)的格子的颜色也被反转,问至少反转几个格子可以使4*4的正方形变为纯白或者纯黑?

主要思路如下:

1.对于每个格子,它要么反转0次,要么反转1次(当然,它的邻格子也跟着反转),因为它反转偶数次和反转0次的效果是一样的,同理反转奇数次的效果和反转1次的效果是一样的。
2.由于只有16个格子,我们可以选择0个格子,1个格子,2个格子,3个格子......进行反转,总的选择情况为


3.当0个格子被反转时,看它是否为纯色,否则选择一个格子进行反转(有16种选择),看反转后是否为纯色,否则选择两个格子进行反转(有120种选择),看反转后是否为纯色......
4.只要"3过程"中有纯色出现,就停止"3过程",输出相应的被选择的格子个数,结束。如果16个格子都被翻转了,还是没变成纯色,则输出“Impossible”。


对于从16个格子中选取n个格子的所有组合,请看前一篇文章从数组中取出n个元素的所有组合(递归实现),这里不再叙述。

整个程序代码如下:

复制代码
  1 /*
  2 POJ 1753 Flip Game (递归枚举)
  3 By Microgoogle
  4 */
  5 #include <stdio.h>
  6 #include <stdlib.h>
  7 
  8 //所有都是白的,或者所有都是黑的
  9 int all_white_or_black(int* bits, int len)
 10 {
 11   int i = 0;
 12   for (i = 0; i < len - 1; i++)
 13     if (bits[i] != bits[i + 1])
 14       return 0;
 15   return 1;
 16 }
 17 
 18 //改变一个格子的颜色,并根据其所在位置改变其周围格子的颜色
 19 void change_color(int* arr, int i)
 20 {
 21   arr[i] = !(arr[i]);
 22   int x = i/4;
 23   int y = i%4;
 24   if (y < 3)
 25     arr[i + 1] = !(arr[i + 1]);
 26   if (y > 0)
 27     arr[i - 1] = !(arr[i - 1]);
 28   if (x > 0)
 29     arr[i - 4] = !(arr[i - 4]);
 30   if (x < 3)
 31     arr[i + 4] = !(arr[i + 4]);
 32 }
 33 
 34 //递归判断
 35 //这个完全用了前一篇文章的递归方法,只是在else语句中添加了整个图形是否为纯色的判断而已
 36 void combine(int* arr, int len, int* result, int count, const int NUM, int* last)
 37 {
 38   int i;
 39   for (i = len; i >= count; i--)
 40   {
 41     result[count - 1] = i - 1;
 42     if (count > 1)
 43       combine(arr, i - 1, result, count - 1, NUM, last);
 44     else
 45     {
 46       int j = 0;
 47       //在这里生成arr的副本
 48       int* new_arr = (int*)malloc(sizeof(int)*16);
 49       for (j = 0; j < 16; j++)
 50         new_arr[j] = arr[j];
 51 
 52       for (j = NUM - 1; j >=0; j--)
 53       {
 54          change_color(new_arr, result[j]);
 55       }
 56       if (all_white_or_black(new_arr, 16))
 57       {
 58          *last = NUM;
 59          free(new_arr);
 60          break;
 61       }
 62       free(new_arr);
 63     }
 64   }
 65 }
 66 
 67 int main()
 68 {
 69   char str[5];
 70   int bits[16];
 71   int count = 15;
 72   int lines = 4;
 73   while (lines--)
 74   {
 75     scanf("%s", str);
 76     int i;
 77     for (i = 0; i < 4; i++)
 78     {
 79       if (str[i] == 'b')
 80         bits[count--] = 1;
 81       else
 82         bits[count--] = 0;
 83     }
 84   }
 85 
 86   if (all_white_or_black(bits, 16))
 87     printf("%d\n", 0);
 88   else
 89   {
 90     //生成bits数组的副本
 91     int* new_bits = (int*)malloc(sizeof(int)*16);
 92     int i;
 93     for (i = 0; i < 16; i++)
 94       new_bits[i] = bits[i];
 95 
 96     int j;
 97     //这里last用来接受combine函数里面的NUM,即需要的步数
 98     int last = 0;
 99     for (j = 1; j <= 16; j++)
100     {
101       int* result = (int*)malloc(sizeof(int)*j);
102       combine(new_bits, 16, result, j, j, &last);
103       if (last == j)
104       {
105         printf("%d\n", last);
106         break;
107       }
108       //new_bits已被改变,所以要还原为bits
109       for (i = 0; i < 16; i++)
110         new_bits[i] = bits[i];
111      
112       free(result);
113     }
114     free(new_bits);
115 
116     if (j == 17)
117       printf("Impossible\n");
118   }
119  
120   return 0;
121 }
复制代码

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值