C语言 数组中某些元素的和为某一定值 找出所有组合
题目:给出如下集合 {19061, 142852, 859322, 122058, 10424, 2274, 2274, 527099, 5723, 6408, 8652, 8652, 50435, 245580, 921051, 734785, 1614292, 617546, 4169671, 125437} 其中某些元素的和6023925。找出这些元素。
解:
#define TARGET 6023925
int main()
{
int numbers[] = {19061,142852,859322,122058,10424,2274,2274,527099,5723,6408,8652,8652,
50435,245580,921051,734785,1614292,617546,4169671,125437};
long sum = 0;
int len = sizeof(numbers)/sizeof(int);
int i = 0;
int j = 0;
int flag = 0;
long maxnum = 0;
int nums = 0;
char str[10240] = {0};
maxnum = pow(2, len);
for (i = 1; i <= maxnum; i++)
{
sum = 0;
for (j = 0; j < len; j++)
{
flag = (i >> j) % 2;
if ( flag == 1)
{
sum += numbers[len - j - 1];
sprintf(str + strlen(str), "%d ", numbers[len - j - 1]);
}
}
if (sum == TARGET)
{
printf("%s\n", str);
nums++;
}
str[0] = 0;
}
if (nums == 0)
{
printf("\n没找到可用的组合 检查数据是否正确\n");
}
else
{
printf("\n输出完毕,共%d种结果\n", nums);
}
getchar();
return 0;
}
这段代码的性能并不好,解决思路实际上就是暴力枚举。遍历集合内所有可能的组合,将所有的结果都与目标值进行比较。
每个元素对于最后的结果来讲都有两种可能,在或不在结果中。这实际上也就是一个二进制模型。假如一个集合有3个元素,那么组合的可能就有2的3次方个。那么就要进行8次比较(忽略掉所有元素都没有的情况就是7中情况)。
假设集合的名字为a_set。
第一次循环:循环变量值为1 ,二进制表示就是001,那么第一种组合的和就是a_set[0]。
第二次循环:循环变量值为2,二进制表示就是010,那么第二种组合的和就是a_set[1]。
第三次循环:。。。。。。3,。。。。。。。011,。。。。。。。。。。。a_set[0] + a_set[1]。
第四。。。。。。。。。。4,。。。。。。。100,。。。。。。。。。。。a_set[2]。
第五。。。。。。。。。。5,。。。。。。。101,。。。。。。。。。。。a_set[0] + a_set[2]。
。。。。。。。。。。。。6,。。。。。。。110,。。。。。。。。。。。a_set[1] + a_set[2]。
。。。。。。。。。。。。7,。。。。。。。111,。。。。。。。。。。。a_set[0] + a_set[1] + a_set[2]。
代码的其他瓶颈是集合内元素值以及目标值必须在代码要求的类型范围内,集合内元素的个数不能超过long类型的位数。当真的需要很多的集合元素时,不建议用这样的办法来实现,既耗资源又耗时间。
不过认真考虑这段代码还是有优化的空间的。假设集合中有5个元素,那么组合的情况就有2的5次方32种。其实我们不必遍历32种组合的结果。例如当我们已经找到了一个结果,这种结果用二进制来标识每个元素存在与否 01110(意思是集合从右向左的第二个第三个和第四个元素的和等于目标值),那么当我们得到这个结果后有些结果我们就不必再判断了。其他包含这种组合的组合,以及只含有这个组合中某些元素的组合。第一种情况带入例子就是说当01110可以时,01111和11110就一定不行,意思是我们已经知道目标值是那三个元素的和了,那么那个元素再加上任何一个值(0除外)都不能是目标值。同理少了这三个元素中的任何一个也都不可能是目标值。用数学方式来表达的话,就是已知一个集合是正确结果,那么和这个集合的交集等于两个集合其中一个。那么我们就可以断定另外一个集合肯定不是正解。
话说回来,其实上面两种情况我们有一种情况不必考虑的,那就是含有正解集合中的某些元素这种情况。因为在得到正解之前我们还是使用便利的方法,那些含有正解集合中的某些元素的集合早就被遍历过了,所以我们只需判断包含正解集合的集合就可以了。
代码改良后如下:
#define TARGET 6023925
int main()
{
int numbers[] = {19061,142852,859322,122058,10424,2274,2274,527099,5723,6408,8652,8652,
50435,245580,921051,734785,1614292,617546,4169671,125437};
long sumlist[1024];
long sum = 0;
int len = sizeof(numbers)/sizeof(int);
int i = 0;
int k = 0;
int flag = 0;
long maxnum = 0;
int nums = 0;
char next = 0;
char str[10240] = {0};
maxnum = pow(2, len);
for (i = 1; i <= maxnum; i++)
{
sum = 0;
for (k = 0; k < nums; k++)
{
if ( (i & sumlist[k]) == sumlist[k])
{
next = 1;
break;
}
}
if (next)
{
continue;
}
for (j = 0; j < len; j++)
{
flag = (i >> j) % 2;
if ( flag == 1)
{
sum += numbers[len - j - 1];
sprintf(str + strlen(str), "%d ", numbers[len - j - 1]);
}
}
if (sum == TARGET)
{
sumlist[nums] = i;
printf("%s\n", str);
nums++;
}
str[0] = 0;
}
if (nums == 0)
{
printf("\n没找到可用的组合 检查数据是否正确\n");
}
else
{
printf("\n输出完毕,共%d种结果\n", nums);
}
getchar();
return 0;
}
其实这段代码也不一定比第一种效率高很多,具体执行效率取决于目标值。目标集合的二进制值越小越好,越小的话我们就能越早找到目标集合,进而去排除那些后续我们不想要的。比如当我们将目标值设置为1352331时两种代码的执行时间就会大不相同。