转载自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.按照数组索引从小到大依次取,避免重复
依照上面的递归原则,我们可以设计如下的算法,按照索引从小到大遍历:
- //arr为原始数组
- //start为某位置选取元素的开始位置,因为之后要选择的元素序号需要大于start,所以需要这个变量进行标记
- //result保存一次组合选取的元素的序号,比如第一次选的是1234,则result中则存储1234,之后根据序号进行输出
- //count为result数组的索引值,起辅助作用;count用来确定现在在选的是第几位数,或者还剩几位数就凑够要选择的数的个数;是递归变量之一:递归变量就是每次递归都会起作用或者发生改变的变量,在画递归树分析的时候不止要写出每次的递归变量值,而且对递归变量的限制(例如在循环中,有循环条件限制),也都要列出来,才比较容易模拟清楚过程;而且要尽量选规模小的进行模拟;
- //NUM为要选取的元素个数
- //arr_len为原始数组的长度,为定值
- void combine_increase(int* arr, int start, int* result, int count, const int NUM, const int arr_len)
- {
- int i = 0;
- for (i = start; i < arr_len + 1 - count; i++) //i=start是为了使后续选择的元素都比之前选的元素序号大,避免重复。
- { //i<arr_len+1-count是因为假如第一个元素后面还要选count-1个比它大的
- result[count - 1] = i; //那么它最多只有arr_len-(count-1)种选择,而且数组从0开始,所以
- if (count - 1 == 0) //有了循环约束条件
- {
- int j;
- for (j = NUM - 1; j >= 0; j--) //当只剩最后一个数需要选择的时候,就循环输出所有的其他位确定的组合
- printf("%d\t",arr[result[j]]);
- printf("\n");
- }
- else
- combine_increase(arr, i + 1, result, count - 1, NUM, arr_len); //还有多于一位数需要选择的时候,就递归选择剩 //下数中少一位的组合,直到只剩一位;
- }
- }
如果从状态转移的想法看,代码是利用当组合位数大于一时循环寻找组合位数少一的组合,组合位数为一的时候循环输出整个数组结果,然后不管什么情况下都确定一位。
这里状态转移的特点是它是循环递归的,所以在考虑状态变化的时候考虑不清楚。另外在理解递归的过程中,画递归树模拟很重要。
以下是转载的按照索引从大到小进行遍历的代码:
- //arr为原始数组
- //start为遍历起始位置
- //result保存结果,为一维数组
- //count为result数组的索引值,起辅助作用
- //NUM为要选取的元素个数
- void combine_decrease(int* arr, int start, int* result, int count, const int NUM)
- {
- int i;
- for (i = start; i >=count; i--) //这里只要保证有比序号还小的count-1个数就可以
- {
- result[count - 1] = i - 1;
- if (count > 1)
- {
- combine_decrease(arr, i - 1, result, count - 1, NUM);
- }
- else
- {
- int j;
- for (j = NUM - 1; j >=0; j--)
- printf("%d\t",arr[result[j]]);
- printf("\n");
- }
- }
- }
- #include <stdio.h>
- int main()
- {
- int arr[] = {1, 2, 3, 4, 5};
- int num = 4;
- int result[num];
- combine_increase(arr, 0, result, num, num, sizeof(arr)/sizeof(int));
- printf("分界线\n");
- combine_decrease(arr, sizeof(arr)/sizeof(int), result, num, num);
- return 0;
- }
结果:
- 1 2 3 4
- 1 2 3 5
- 1 2 4 5
- 1 3 4 5
- 2 3 4 5
- 分界线
- 5 4 3 2
- 5 4 3 1
- 5 4 2 1
- 5 3 2 1
- 4 3 2 1
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 }