注:本文100%原创!
一、问题描述:
在某个镇某个夜晚,发生一起谋杀案,警察通过排查确定杀人凶手是4个嫌疑中的一个,以下是四个人的说词
A说:不是我
B说:是C
C说:是D
D说:C在胡说
其中只有3个人说了真话,1个人说了假话,现在根据这些信息,写一个程序来确定哪个是凶手。
二、问题分析
可以直接写死程序,但为了更加好玩,把程序写活,就得换另外一种思维了。有用户手动输入说话内容,然后根据内容来分析结果,输出。这里采用预备存储法,即根据用户的输入,预先得出有多少种可能的结果,然后再从中判断,最后输出。
三、涉及知识点
排列组合,动态数组,C语言简单操作。
四、举例分析
A说:不是我
B说:是C
C说:是D
D说:C在胡说
假如:用某个人说话的内容用大小写字母表示。小写字母表示凶手不是自己,大写字母表示凶手就是自己,即a表示,A是不是凶手,A表示A是凶手。所以案例输入应该为:
A说:a
B说:C
C说:D
D说:d
根据排列组合知识,得知:4个人,如果有3个人说真话,即从4个人中选择3个人出来(说真话的),或者从4个人中选择1个人出来(说假话),这里以说真话为例。所以为C(4,3)=4。
所以说话真假的矩阵也可以是:
ABCd (d说假话)
ABcD(c说假话)
AbCD(b说假话)
aBCD(a说假话)
我们定义一个result数组,用来判断最终结果。如果我们这样,如果某个人的说话内容是A,那么对应result[0]就+1,0对应A,1对应b......, 如果某个人的说话内容是a,那么result[1]+1, result[2]+1, reuslt[3]+1, 即result[0]不加,最终根据result[i]的数值大小来判断,如果result[i]等于总人数,那么第i号人就是凶手,可能有多种情况,然后转换成判断矩阵来计算。
五、运行效果图
案例测试:手动计算只有一种结果
换一种测试:说话内容不变,说真话人数变为2个,手动计算知道有两种结果
再换一种测试,7个人,5个说真话的,
其他的不演示了,因为人数越多越不好验证 ,数学系的同学深有体会!
下面附上代码,其中有包含了排列组合的代码
六、代码
C语言代码如下:
#include
#include
int cNumber(int n, int m);
int combine(int n, int m, char *tell);
char correct(char arr, char tell);
int check(int n, char *tell, char *people, int *result);
int main(void)
{
int m, n;
printf("请输入测试者人数:");
scanf("%d", &n);
printf("请输入说真话的人数:");
scanf("%d", &m);
char *tell = calloc(n, sizeof(char));
printf("请输入说话内容(假如X是凶手,请输入X, 否则请输入x)\n");
for(int i=0; i { printf("%c说的话:", 'A'+i); getchar(); //清空缓冲区 scanf("%c", &tell[i]); } printf("\n测谎开始!\n\n"); combine(n, m, tell); return 0; } //计算组合数种类 int cNumber(int n, int m) { int i, tmp=1, group=1; for(i=2; i<=m; i++) { tmp *= i; } for(i=0; i { group *= (n-i); } return group/tmp; } //纠正原话的结果 char correct(char show, char tell) { if(show>='A' && show<='Z') //说真话 { return tell; //返回原话 } else if(tell>='A' && tell<='Z') { return tell+'a'-'A'; //返回原话的反话 } else { return tell-('a'-'A'); //返回原话的反话 } } //检测出结果 int check(int n, char *tell, char *people, int *result) { int i, j, k; for(i=0; i { result[i] = 0; } for(i=0; i { for(j=0; j { if(tell[i] == people[j]) //直接说是people[j] { result[j]++; //people[j]怀疑性加一 break; } else if(tell[i]>='a' && tell[i]<='z' && (tell[i]- ('a'-'A')) == people[j]) //说不是people[j] { for(k=0; k { if(k!=j) { result[k]++; //除了people[i]外的人怀疑性加一 } } break; } } } //当且仅当只有一个result[i]==n时,检测结果合理 for(i=0, j=0 ; i { if(result[i]==n) { k=i; //记录凶手编号 j++; //假如检测出有多个凶手 } //printf("result[%d]=%d, ", i, result[i]); } if(j==1) { printf("\n测试结果: 凶手是%c!\n", people[k]); return 1; } return 0; } int combine(int n, int m, char *tell) { int i, j, k, count, group; group = cNumber(n, m); char **show = calloc(group, sizeof(char*)); //用来存储所有可能情况 for(i=0; i { show[i] = calloc(n, sizeof(char)); } for(i=0; i { for(j=0; j { show[i][j] = 'a'+ j; } } if(n { printf("输入组合数非法!\n"); return -1; } char *people = calloc(n, sizeof(char)); //用来存储有多少个人 for(int i=0; i { people[i] = i+'A'; } int *a = calloc(m, sizeof(int)); //用来存储每次产生的一种组合 for(i=0; i { a[i] = i+1; } for(j=m, count=0; a[0]<=(n-m+1);) //当为最后一种组合时,循环结束 { for(;a[m-1]<=n; a[m-1]++) //最后一位不断递增,直到达到最大值,产生进位 { count++; //printf("第%d种组合: ", count); //计算组合数种类 for(k=0; k { //printf("%c", arr[a[k]-1]); show[count-1][people[a[k]-1]-'A'] = people[a[k]-1]; } //printf("\n"); } for(j=m-2; j>=0; j--) //判断a[1]--a[m-2]是否有进位 { a[j]++; if(a[j] <= (j+n-m+1)) //a[j]不进位,a[j-1]也不进位 { break; } } for(j++; j>0 && j { a[j] = a[j-1] + 1; } } int *result = calloc(n, sizeof(int)); char *correct_tell = calloc(n, sizeof(char)); //计算所有组合情况对应的结果 for(i=0; i { for(j=0; j { correct_tell[j] = correct(show[i][j], tell[j]); //printf("correct_tell[%d]=%c ", j, correct_tell[j]); } k = check(n, correct_tell, people, result); if(k==1) { for(k=0; k { //printf("show[%d][%d]=%c ", i, k , show[i][k]); if(show[i][k]>='A' && show[i][k]<='Z') { printf("%c说真话", people[k]); } else { printf("%c说假话", people[k]); } printf("\n"); } } //printf("\n"); } //释放堆内存 for(i=0; i { free(show[i]); } free(show); free(people); free(result); free(correct_tell); free(a); getchar(); return 0; } 当然本次的测谎机有一点的局限性,人数最多26个,很显然的,当然可以修改;用户需要判断说话内容来输入;没有对用户的输入做太多合法性判断;当无法确定凶手时,无法输出,只能判断只有一个凶手的情况,不能判断出团伙作案的情况。 感兴趣的小伙伴可以点个关注哦,下次改造成语音识别的,做个真正的测谎机!