问题:
- 魔术师手中有A—K的黑桃牌13张,每次不看牌,数数:1翻过来刚好为A;1、2翻过来刚好为2;1、2、3翻过来刚好为3(不翻的牌放在手中这迭牌的最下面,翻开的牌翻开放在一侧);……;1、……、13刚好为K。最后放在一旁的牌的顺序为1、2、3、4、5、……、J、Q、K。问:原先魔术师手中的牌的循序是怎样的?
- 解决这个问题我们通过一个图来进行解释:
- 这里我们能够看到上面图中所表示的意思。图中刚开始有13个空白框,在第一个空白框中写入A、然后从下一个空白框开始计数(不是空白框的跳过),第二次数数时数到2在该框中写入2,第三次数到3写入3,依次类推,直到空白框
被写满。我们可以看到只是一个循环过程,循环的结束条件是空白框全部都被写入数字或字母。下面就给出C语言的代码:
#include<stdio.h>
int main()
{
int i, j = 1;
int arr[13] = {0};
for(i = 1; i <= 13; i++)
{
int n = 1;
while(n <= i)
{
if(j > 13)
{
j = 1;
}
if(arr[j])
{
j++;
}
else
{
if(n == i)
{
arr[j] = i;
}
n++;
j++;
}
}
}
return 0;
}
这个问题的处理方法有很多种,这里就介绍了最基础的C语言解法,在我们学过了数据结构后就可以利用循环链表和循环队来处理,相对好理解一点。
通过上面的问题,我们还能过解决另外一个问题:有N个人围成一圈,并将它们编号从1–N,然后开始报数,规定每隔M个人就淘汰一个人,问最后剩下的人为原先的几号?
- 问题分析:这又是一个循环成圈的问题,由问题可知需要一个N个大小的空间来存放这些人的号码,然后每隔M个人淘汰一个,我们可以理解为每次从1开始报数,报数为M+1的人淘汰,然后下一个人有从1开始报数,直到剩下最后一个人。先来演示一个例子:假设有10个人围成一圈,每隔2个人淘汰一个人。我们可以知道N= 10,M= 2;这个问题我们可以用一个大小为10个元素的整型数组来完成,依次将号码填入数组中,每次淘汰的人的号码我们将其从新赋值为0,依次直到只剩下医德元素不为0结束,则最后留下的数字就为剩下最后一个人的号码。
- 如图所示:
- 经过几次的循环后实现了要求,求出了最后剩下的人的号码。
- 代码实现为:
- 问题分析:这又是一个循环成圈的问题,由问题可知需要一个N个大小的空间来存放这些人的号码,然后每隔M个人淘汰一个,我们可以理解为每次从1开始报数,报数为M+1的人淘汰,然后下一个人有从1开始报数,直到剩下最后一个人。先来演示一个例子:假设有10个人围成一圈,每隔2个人淘汰一个人。我们可以知道N= 10,M= 2;这个问题我们可以用一个大小为10个元素的整型数组来完成,依次将号码填入数组中,每次淘汰的人的号码我们将其从新赋值为0,依次直到只剩下医德元素不为0结束,则最后留下的数字就为剩下最后一个人的号码。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void main()
{
int m, n;
printf("请输入人数N:");
scanf_s("%d", &n);
printf("请输入相隔M:");
scanf_s("%d", &m);
int *arr = (int *)malloc(sizeof(int) * n);
for (int i = 0; i < n; i++)
{
arr[i] = i + 1;
}
int count = n;
int k = 0, j = 0;
while (count > 1)
{
if (arr[k] != 0)
{
j++;
}
if (j == m + 1)
{
arr[k] = 0;
j = 0;
count--;
}
k++;
if (k == n)
{
k = 0;
}
}
for (int i = 0; i < n; i++)
{
if (arr[i] != 0)
{
printf("最后剩下的号码为:%d\n", arr[i]);
}
}
free(arr);
}
- 总结:上面的两个问题都可以归为约瑟夫环的一类问题,正如上面所说的,解决这类问题最好理解的方法是利用数据结构中的循环队列和循环链表,但使用C语言中的数组来实现可以可以让我们加深对循环和数组的理解与应用,有开拓思维的帮助。