【问题描述】
约瑟夫(Joseph)问题的一种描述是:编号为1,2,…,n的n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。开始任选一个正整数作为报数上限值m,从第一个人开始按顺时针方向自1开始顺序报数,报到m时停止报数。报m的人出列,将他的密码作为新的m值,从他在顺时针方向上的下一个人开始重新从1报数,如此下去,直至所有人全部出列为止。试设计一个程序求出出列顺序。
【基本要求】
利用单向循环链表存储结构模拟此过程,按照出列的顺序印出各人的编号。
【测试数据】
m的初值为20;n=7,7个人的密码依次为:3,1,7,2,4,8,4,首先m值为6(正确的出列顺序应为6,1,4,7,2,3,5)。
#include<stdio.h>
#include<stdlib.h>
typedef struct node{
int data;/*存储密码*/
int num;/*存储序列号*/
struct node *next;
}node,*Linklist;
/*创建存有密码、序列号的链表*/
Linklist creatlist(int n,int *a){
Linklist head;
Linklist p,q;
int i=1;
if((head=(Linklist)malloc(sizeof(node)))==NULL){
printf("分配内存失败");
return NULL;
}
head->data=a[i-1];
head->num=i;
head->next=head;
q=head;
i++;
while(i<=n){
if((p=(Linklist)malloc(sizeof(node)))==NULL){
printf("分配内存失败");
return NULL;
}
p->data=a[i-1];
p->num=i;
q->next=p;
q=q->next;
q->next=head;
i++;
}
return head;
}
/*采用递归算法找到出列人*/
void findnode(Linklist head,int m,int n){
Linklist q;
int i;
if(n!=0){
if(m>1){
q=head->next;
for(i=1;i<m-1;i++){
head=head->next;
q=q->next;
}
head->next=q->next;
head=head->next;
m=q->data;
printf("%d\t",q->num);
free(q);
findnode(head,m,n-1);
}
else{
q=head;
head=head->next;
printf("%d\t",q->num);
free(q);
findnode(head,m,n-1);
}
}
}
int main(){
int n;
printf("请输入人数:\n");
scanf("%d",&n);
int a[n];
printf("请输入每个人持有的密码:\n");
int i;
for(i=0;i<n;i++){
scanf("%d",&a[i]);
}
Linklist head;
head=creatlist(n,a);
int m;
printf("请输入密码的上限值:\n");
scanf("%d",&m);
printf("出列序号:\n");
findnode(head,m,n);
return 0;
}
运行结果:
总结:
1、对链表还不是太熟悉,尤其是这种循环链表,有些东西还得仔细想,比如44-47行:
for(i=1;i<m-1;i++){
head=head->next;
q=q->next;
}
一开始不知道i<m还是i<m-1,在运行了代码后才知道应该i<m-1。
2、对各种情况考虑不够完善,总是会少考虑一或几种情况,比如在findnode函数中,当这个密码m=1和m>1时是完全不一样的,少考虑了m=1的情况,输出结果就会出现这种情况:
6 1 4 7 2 5 3
因为当m=1时,i<m-1=0显然是不成立的,不信可以将m=1时的情况删去,运行一下看看结果,是不是很好玩啊。
3、其实最近也有关注算法的效率问题,在findnode函数中,当这个密码m的值越来越大,它做的无用功也越来越多,为了提高它的效率,我的办法是 m%n 既取余,当然在实际中是不能这样的,因为每个人都要报数,但是这种方法我没有试(真实情况是我比较懒),有心人可以做做看