这是一个典型的命题逻辑推理问题,相关知识各位找本离散数学看吧,我就不详述了。2、3楼两位觉得无从下手,那么我就从如何下手说起吧。
在解决任何一个问题,尤其是用计算机解决时,之前,先要分析问题。而要想借助某种手段分析问题的前提是,先用某种方式描述清楚问题。也可以说是将问题符号化。再换一种说法就是对问题建模。
上面的话可能有点抽象,但应该不难理解。两位无从下手的原因就在于不知道如何用代码表述这个问题。而表述问题的这种能力就是衡量一个编程者水平的指标,也是我们进行软件学习的主要目的。要提高这一能力只能靠我们不断的学习与练习。
形而上的的东西说完了,现在看看上面那几句云里雾里的东西是如何具体落实在这个问题上的。
问题中有10个人,分别用字母A-J表示(这就是一个符号化,只是这是具象到纸面的符号化)。这些人在满足一系列条件下有些人会参加比赛,另一些人会不参加比赛。
这意味着,每个人在一个结果中有两种可能的状态,参加或不参加。10个人的状态构成一组结果。由此可知可能的结果共有2的10次方组即1024组。这是问题的解空间。在计算机中表示一组结果的方法有很多种。用1表示参加,用0表示不参加,用一个布尔型或整型或你喜欢的其它型变量表示一个人,用一个数组表示一个结果等等等等。
在我的代码中用1个二进制位表示一个人,从低位到高位分别表示A-J。共用了10位。这样一组结果只需要用一个大于10位的量来表示就可以了。我用的是int型。我的编译器是GCC,它是32位的。在TC里int是16位的。
到这里,问题已经被表述完整了。接下来就是在这一表述下的运算。我的代码就是对解空间中的每一个解验证一遍那一组条件,然后将满足所有条件的解输出。关于如何验证条件满足与否,我就以第一个条件为例说明一下。
Test函数的第1个循环用于将由整数表述的解转换成由数组表述的解。整数表述占用的空间小,便于传递,但做条件判断不方便。数组表述易于运算,但浪费的空间多。所以我在不同的阶段使用不同的表述方式。
条件1:如果A参加,B也参加;
这是一个蕴含式描述。即A->B,等价于!A || B。即是说对于一组结果,如果其中A为1,且B也为1;或者A为0,那么这组结果满足该条件。
Test函数中的第1个if语句就是描述这一条件的。由于各条件之间是与关系,即任意一个条件不成立,解就是不成立的。所以我将if语句级联着写。其实它们是嵌套关系,只不过我省略了花括号和缩进,而且这样写我觉得更美观也易于理解。
为了方便大家讨论,再复制一次代码到这里。
程序代码:
#include <stdio.h>
void Test(int comb)
{
int a[10], i;
for(i = 0; i < 10; a[i] = (comb >> i++) & 1);
if(!a[0] || a[1]) /*1. 如果A参加,B也参加;*/
if(a[2] || !a[3]) /*2. 如果C不参加,D也不参加;*/
if(!a[0] || !a[2]) /*3. A和C中只能有一个人参加;*/
if(a[1] && !a[3] || !a[1] && a[3]) /*4. B和D中有且仅有一个人参加;*/
if(!(a[3] && !a[4] && !a[5] && !a[6] && !a[7] ||
!a[3] && a[4] && !a[5] && !a[6] && !a[7] ||
!a[3] && !a[4] && a[5] && !a[6] && !a[7] ||
!a[3] && !a[4] && !a[5] && a[6] && !a[7] ||
!a[3] && !a[4] && !a[5] && !a[6] && a[7] ||
!a[3] && !a[4] && !a[5] && !a[6] && !a[7])) /*5. D、E、F、G、H 中至少有2人参加;*/
if(a[2] && a[6] || !a[2] && !a[6]) /*6. C和G或者都参加,或者都不参加;*/
if(!(!a[2] && a[4] && a[6] && a[8] ||
a[2] && !a[4] && a[6] && a[8] ||
a[2] && a[4] && !a[6] && a[8] ||
a[2] && a[4] && a[6] && !a[8] ||
a[2] && a[4] && a[6] && a[8])) /*7. C、E、G、I中至多只能2人参加;*/
if(!a[4] || a[5] && a[6]) /*8. 如果E参加,那么F和G也都参加;*/
if(!a[5] || !a[6] && !a[7]) /*9. 如果F参加,G、H就不能参加;*/
if(a[8] || a[9] || a[7]) /*10. 如果I、J都不参加,H必须参加。*/
{
for(i = 0; i < 10; i++)
if(a[i]) printf("%c ", 'A' + i);
printf("\n");
}
}
int main()
{
int i;
for(i = 1; i <= 0x3FF; Test(i++));
return 0;
}
8楼这位提出一个交集的思想,不过在11楼的描述我实在是没看懂。编程的乐趣就在于想,进而写出来获得成功的快感。如果嫌累,那我觉得可能不适合走这一条路。现在做什么事情不累呢?话又说回来,搞软件如果很轻松那这个行业又凭什么拿那么高的薪水呢?
关于交集谈谈我的理解。
上面的代码是对每一个解做一系列的条件判断,然后确认该解是成立还是不成立。
换个角度,这个问题还可以这样解决,用一个条件过滤解空间,得到关于该条件成立的解集(解空间收缩)。用下一个条件再过滤这个解集,得到新的既满足之前条件又满足现在条件的解集。当所有的条件都过滤完后,剩下的解集就是问题要求的结果。
听起来不错。分析一下算法的时间复杂度:
设人数为N,条件数为M;
算法1的复杂度为 O(2^N * M);
算法2的复杂度为 O(2^N + 2^(N-1) + 2^(N-2) + ... + 2^(N-M))=》 O(2^N);
算法2的复杂度公式是一个宏观统计公式,认为每次过滤可以滤去一半的解。
由上面的分析可知,当条件的数量不变时,两个算法的时间复杂度是相同的。当条件的数量为变量时,算法2优于算法1。
总的来说,2的N次方这样的复杂度不可能解大规模的命题的。对于这个问题,如果人数扩大到30人,想找到所有解将是很困难的。但找出几个解还是可以的。
下面是我按照算法2的思路完成的代码,希望大家指正。
程序代码:
#include <stdio.h>
typedef char BOOL;
typedef struct node
{
char a[10];
struct node * next;
}NODE;
NODE ns[1024];
void Initial()
{
int i, j;
for(i = 0; i < 1024; i++)
{
for(j = 0; j < 10; ns[i].a[j] = (i >> j++) & 1);
ns[i].next = (i == 1023) ? NULL : &ns[i + 1];
}
}
BOOL T1(char *a) { return !a[0] || a[1]; }
BOOL T2(char *a) { return a[2] || !a[3]; }
BOOL T3(char *a) { return !a[0] || !a[2]; }
BOOL T4(char *a) { return a[1] && !a[3] || !a[1] && a[3]; }
BOOL T5(char *a) { return !(a[3] && !a[4] && !a[5] && !a[6] && !a[7] ||
!a[3] && a[4] && !a[5] && !a[6] && !a[7] ||
!a[3] && !a[4] && a[5] && !a[6] && !a[7] ||
!a[3] && !a[4] && !a[5] && a[6] && !a[7] ||
!a[3] && !a[4] && !a[5] && !a[6] && a[7] ||
!a[3] && !a[4] && !a[5] && !a[6] && !a[7]); }
BOOL T6(char *a) { return a[2] && a[6] || !a[2] && !a[6]; }
BOOL T7(char *a) { return !(!a[2] && a[4] && a[6] && a[8] ||
a[2] && !a[4] && a[6] && a[8] ||
a[2] && a[4] && !a[6] && a[8] ||
a[2] && a[4] && a[6] && !a[8] ||
a[2] && a[4] && a[6] && a[8]); }
BOOL T8(char *a) { return !a[4] || a[5] && a[6]; }
BOOL T9(char *a) { return !a[5] || !a[6] && !a[7]; }
BOOL T10(char *a) { return a[8] || a[9] || a[7]; }
BOOL (*condition[])(char *) = { T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 };
void Test()
{
int i;
NODE *head, *p, *q;
head = ns;
for(i = 0; i < 10; i++)
{
p = head;
q = p->next;
while(q)
{
if(condition[i](q->a)) p = q; else p->next = q->next;
q = q->next;
}
}
}
void Output()
{
int i;
NODE * p;
p = ns[0].next;
while(p)
{
for(i = 0; i < 10; i++)
if(p->a[i]) printf("%c ", 'A' + i);
printf("\n");
p = p->next;
}
}
int main()
{
Initial();
Test();
Output();
return 0;
}