约瑟夫问题:
据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
通俗的说一下规则:
- 所有的人(包括你自己)围成一个圈并编号
- 从第一个人开始顺时针报数,报到数字k的人被杀掉并移除圈
- 从被杀死的下一个人继续报数,报到数字k的人再被杀掉并移除
- 直到剩下最后一个人
这个问题的解决方法有很多种,可以用递归,用数学推导,用数组完成,下面介绍一种用链表模拟的方法解决这个问题。
提供两段实现的代码供参考:
第一段代码:
///在一个主函数中实现
#include<stdio.h>
#include<malloc.h>
typedef struct List
{
int data;
struct List *next;
}LinkList;
int main()
{
LinkList *L,*r,*s;
L = (LinkList *)malloc(sizeof(LinkList));
r =L;
int n,i;
int k;
printf("Please input the number of people:\n");
scanf("%d",&n);
printf("The number to count each round:\n");
scanf("%d",&k);
for(i = 1;i<=n;i++) ///尾插法建立循环链表
{
s = (LinkList *)malloc(sizeof(LinkList));
s->data = i;
r->next = s;
r= s;
}
r->next =L->next; //让最后一个链表的下一个节点指向开头
LinkList *p,*q;
p = L->next;
while(p->next != p) //开始模拟(判断条件要注意:因为最后肯定剩下一个人, 所以最后一个数据的next还是他本身)
{
for(i = 1;i<k-1;i++)
{
p = p->next; //从第一个人开始数k-2次
}
q = p->next;
p->next = p->next->next; //将该节点从链表上删除,第k个人死去
free(q);
p = p->next; //p指针指向死去的下一个人
}
printf("The last one :%d",p->data);
return 0;
}
结果展示:
第二段代码:
///用函数实现
#include <stdio.h>
#include <malloc.h>
#define LEN sizeof (LinkList) //定义struct List这个类型的长度
typedef struct List //定义结构体
{
int num;
struct List *next;
}LinkList;
LinkList *create(int m) //创建循环链表
{
LinkList *head,*p1,*p2;
int i;
head=p1=(LinkList*)malloc(LEN);
head->num=1;
for(i=1,p1->num=1;i<m;i++)
{
p2=(LinkList*)malloc(LEN);
p2->num=i+1;
p1->next=p2;
p1=p2;
}
p2->next=head;
return head;
}
LinkList *findout(LinkList *start,int n) //找到需要删除结点的前一个结点
{
int i;
LinkList *p;
p=start;
for(i=1;i<n-1;i++)
p=p->next;
return p;
}
LinkList *letout(LinkList *last) //删除下一个结点并返回下下个结点
{
LinkList *out,*next;
out=last->next;
last->next=out->next;
next=out->next;
printf("%d-->",out->num); //打印死亡顺序
free(out);
return next;
}
int main()
{
int m,n,i,king;
LinkList *p;
printf("Please input the number of people:\n");
scanf("%d",&m);
printf("The number to count each round:\n");
scanf("%d",&n);
if(n==1)
{
king=m;
}
else
{
p=create(m);
for(i=1;i<m;i++)
{
p=findout(p,n);
p=letout(p);
}
king=p->num;
free(p);
}
printf("\n");
printf("The last one is:%d\n",king);
return 0;
}
结果展示:
===============================================================
约瑟夫问题的另一种解法:参考 约瑟夫环问题的两种解法(详解):https://blog.youkuaiyun.com/okasy/article/details/79503398
递归实现:参考 https://www.cnblogs.com/Bravewtz/p/10089717.html
循环链表实现C++代码:
#include<iostream>
using namespace std;
/************约瑟夫问题****************/
typedef struct CLinkList
{
int data;
struct CLinkList *next;
}node;
int main()
{
///建立循环链表
node *L,*r,*s;
L = new node;
r =L;
int n = 41,i;
int k = 3;
for(i = 1;i<=n;i++) //尾插法建立链表
{
s = new node;
s->data = i;
r->next = s;
r= s;
}
r->next =L->next; //让最后一个结点指向第一个有数据结点
node *p;
p = L->next;
delete L; //删除第一个空的结点
///模拟解决约瑟夫问题
while(p->next != p) //判断条件:因为最后肯定剩下一个人, 循环链表的最后一个数据的next还是他本身
{
for(i = 1;i<k-1;i++)
{
p = p->next; //每k个数死一个人
}
cout<<p->next->data<<"->";
p->next = p->next->next; //将该节点从链表上删除。
p = p->next;
}
cout<<p->data<<endl;
return 0;
}
运行结果:
附上华为2016研发工程师编程题
这道题就是一个约瑟夫问题。
#include <iostream>
using namespace std;
typedef struct CLinList
{
int data;
struct CLinList *next;
}node, *Pnode;
int main()
{
int n;
while (cin >> n)
{
Pnode L, r, s;
L = new node;
r = L;
int i;
int k = 3;
for (i = 0; i < n; i++)
{
s = new node;
s->data = i;
r->next = s;
r = s;
}
r->next = L->next;
Pnode P;
P = L->next;
delete L;
while (P->next != P)
{
for (i = 1; i < k - 1; i++)
{
P = P->next;
}
//cout << P->next->data << "->";
P->next = P->next->next;
P = P->next;
}
cout << P->data << endl;
}
return 0;
}