问题描述
编号为1-N的N个士兵围坐在一起形成一个圆圈,从编号为 1 的士兵开始依次报数(1,2,3…这样依次报),数到3的士兵会被杀死出列,之后的士兵再从 1 开始报数。直到最后剩下一士兵,求这个士兵的编号。
01
数组
首先申请一个n个元素的数组空间,全部设置为0,表示所有人都在圈内,之后设立一个计数器,从零开始计数,记到指定数目时有人出圈,将计数器清零,该人出圈(数组对应的元素改为1),从下一个人开始继续计数。
#include <stdio.h>
int main()
{
int num, person;
int flag[1024] = {0}; //标志位,表示人在不在圈内
int k = 0, i = 1; //k计数器 i数组下标,初始化成1,表示第一个人
printf("Pleaes input...\n");
scanf("%d", &num);
person = num;
while (1)
{
if (flag[i] == 0) //人在圈内
{
k++;
if (k == 3) //报数为3
{
k = 0; //k回到0
flag[i] = 1; //踢出
person--;
}
}
i++;
if (i == num + 1)
{
i = 1;
}
if (1 == person)
{
break;
}
}
for (i = 1; i <= num; i++)
{
if (flag[i] == 0)
{
printf("%d\n", i);
break;
}
}
return 0;
}
02
单向循环链表
用链表解决约瑟夫环的方法和数组相同,只是选择了不同的数据结构而已。把所有士兵编号后放入单向循环链表,从0开始报数,数字3则把节点删除。当链表长度为1时,留在链表中的节点就是最后存活的士兵。
#include <stdio.h>
#include <stdlib.h>
struct Node
{
int data; //数据域
struct Node *next; //指针域
};
typedef struct Node Node;
/*
日期:20200202
函数描述:根据士兵人数创建没有头节点的单向循环链表
函数参数:士兵人数
函数返回值:返回第一个节点的地址
*/
Node *CreateLink(int p)
{
if (p <= 0)
{
return NULL;
}
Node *h = (Node *)malloc(sizeof(Node) * 1);
if (NULL == h)
{
return NULL;
}
h->data = 1; //节点的编号
h->next = h; //形成循环链表
Node *q = h;
int i;
for (i = 0; i < p - 1; i++)
{
Node *m = (Node *)malloc(sizeof(Node) * 1);
if (NULL == m)
{
return NULL;
}
m->data = i + 2;
m->next = h;
q->next = m;
q = m;
}
return h;
}
/*
日期:20200202
函数描述:根据节点地址求链表的长度
函数参数:某个节点的地址(不一定是第一个节点)
函数返回值:返回链表长度
*/
int LinkLength(Node *h)
{
int length = 1;
Node *p = h->next;
while (p != h)
{
length++;
p = p->next;
}
return length;
}
/*
日期:20200202
函数描述:删除链表中的节点
函数参数:指向要删除节点的指针
函数返回值:返回删除节点的下一个节点的地址
*/
Node *DeleteNode(Node *p)
{
Node *m = p;
while(m->next != p)
{
m = m->next;
}
m->next = p->next;
free(p);
return m->next;
}
/*
日期:20200202
函数描述:约瑟夫环
函数参数:链表第一个节点的地址
函数返回值:返回最后留下士兵的编号
*/
int Joseph(Node *h)
{
if (NULL == h)
{
return -1;
}
int k = 0;
Node *p = h;
while (LinkLength(p) != 1)
{
k++;
if (3 == k)
{
k = 0;
p = DeleteNode(p); //节点从链表中删除 返回下一个节点的地址
}
else
{
p = p->next;
}
}
return p->data;
}
int main()
{
int person; //记录人数
printf("请输入人数:\n");
scanf("%d", &person);
Node *head = CreateLink(person); //创建循环链表,返回头指针
if (NULL == head)
{
printf("单链表创建失败!\n");
exit(1);
}
int last = Joseph(head); //处理约瑟夫环
printf("最后一个士兵是%d\n", last);
return 0;
}
03
递归
如果我们把士兵删除后,重新给这些士兵编号的话,那么删除前和删除后,这些编号存在某种数学关系,我们只需要找出这个关系即可。
我们定义递归函数Joseph(n)的返回结果是存活士兵的编号,显然当n = 1时,Joseph(n) = 1。假如我们能够找出Joseph(n) 和Joseph(n-1) 之间的关系的话,我们就可以用递归的方式来解决了。我们假设人员数为n, 报数到3的人就自杀。则刚开始的编号为:
进行了一次删除之后,删除了编号为 m 的节点。删除之后,就只剩下n - 1个节点了,对删除后的节点重新编号:
假设old为删除之前的节点编号,new为删除了一个节点之后的编号,则old与new之间的关系为:
old = (new + 2) % n + 1;
这样,我们就得出Joseph(n) 与Joseph(n - 1)之间的关系了,而Joseph(1) = 1。于是得出以下代码:
int Joseph(int n)
{
if (1 == n) return 1;
return (Joseph(n - 1) + 2) % n + 1;
}
当然,我们写这篇文章还有一部分装X的成分在,所以干脆让代码更简洁一点,一行代码搞定:
#include <stdio.h>
int Joseph(int n)
{
return (n == 1) ? n : ((Joseph(n - 1) + 2) % n + 1);
}
int main()
{
int person;
printf("请输入人数:\n");
scanf("%d", &person);
printf("最后一个士兵 %d\n", Joseph(person));
return 0;
}
卧槽,以后面试官让你手写约瑟夫问题,你就扔这一行代码给它。
更多内容,关注公众号 学益得智能硬件!