注:本文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 <stdio.h>
#include <stdlib.h>
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<n; 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<m; 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<n; i++)
{
result[i] = 0;
}
for(i=0; i<n; i++)
{
for(j=0; j<n; 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<n; k++)
{
if(k!=j)
{
result[k]++; //除了people[i]外的人怀疑性加一
}
}
break;
}
}
}
//当且仅当只有一个result[i]==n时,检测结果合理
for(i=0, j=0 ; i<n; 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<group; i++)
{
show[i] = calloc(n, sizeof(char));
}
for(i=0; i<group; i++)
{
for(j=0; j<n; j++)
{
show[i][j] = 'a'+ j;
}
}
if(n<m || m<1)
{
printf("输入组合数非法!\n");
return -1;
}
char *people = calloc(n, sizeof(char)); //用来存储有多少个人
for(int i=0; i<n; i++) //填充索引,有多少个
{
people[i] = i+'A';
}
int *a = calloc(m, sizeof(int)); //用来存储每次产生的一种组合
for(i=0; i<m; 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<m; 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<m; j++) //调整,使得a[index-1],a[index]顺序排列
{
a[j] = a[j-1] + 1;
}
}
int *result = calloc(n, sizeof(int));
char *correct_tell = calloc(n, sizeof(char));
//计算所有组合情况对应的结果
for(i=0; i<group; i++)
{
for(j=0; j<n; 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<n; 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<group; i++)
{
free(show[i]);
}
free(show);
free(people);
free(result);
free(correct_tell);
free(a);
getchar();
return 0;
}
当然本次的测谎机有一点的局限性,人数最多26个,很显然的,当然可以修改;用户需要判断说话内容来输入;没有对用户的输入做太多合法性判断;当无法确定凶手时,无法输出,只能判断只有一个凶手的情况,不能判断出团伙作案的情况。
感兴趣的小伙伴可以点个关注哦,下次改造成语音识别的,做个真正的测谎机!