题目描述
求解约瑟夫(Joseph)问题。有n个小孩围成一圈,给他们从1开始依次编号,从编号为1的小孩开始报数,数到第m个小孩出列,然后从出列的下一个小孩重新开始报数,数到第m个小孩又出列,…,如此反复直到所有的小孩全部出列为止,求整个出列序列。
如当n=6,m=5时的出列序列是5,4,6,2,3,1。n,m不大于20
输入
n m的值
输出
出列序列
样例输入
6 5
样例输出
5 4 6 2 3 1
问题解析
我们可以看到这个约瑟夫问题(即小孩报数)的问题,其实就是链表问题。我们通过这道题需要掌握的是循环单链表的写法(以c语言为例)
一、循环链表的写法
1.先定义链表的结构体
struct child{int data;struct child* next;
};
然后再全局变量定 “头节点”和“约瑟夫环的两个参数”
struct child* headnode;
int n,m;
下面,我们来定义两个函数,分别是“创建空节点”与“创建有数据的节点”
struct child* emptynode(){struct child* p = (struct child*)malloc(sizeof(struct child));p->data = NULL;p->next = NULL;return p;
}struct child* createnode(int num){struct child* p = (struct child*)malloc(sizeof(struct child));p->data = num;p->next = NULL;return p;
}
我们可以看出这两段代码的主要差别,其实就是p->data=NULL和p->data=num
接下来定义一个函数“createlistBytail”,以尾接法,创建循环单链表
void createlistBytail(struct child* headnode,int num){struct child* r=headnode;for (int i=2; i<=num; i++ ){struct child* s = createnode(i);s->next = r->next;r->next = s;r=s;} r->next = headnode;
}
那么其中需要注意的是,这段代码看似为单纯的创建单链表,其实我们在结尾处添加了一段代码
r->next = headnode;
这段代码保证了单链表的循环,也正因为这段代码,使得我们的单链表成功升级为循环单链表!
当然,为了检验我们的链表是否创建成功,我们可以写入这列代码,来遍历出链表的数据
void printlist(struct child* headnode){struct child* pMove = headnode;while(pMove) {printf("%d",pMove->data );pMove = pMove->next ;}
}
接下来,我们开始创建solve函数,实现小孩的报数功能
void solve(struct child* headnode,int n,int m){int i,j;struct child* p;struct child* q;for(i=1;i<=n;i++){p = headnode;j = 1;for(j=1;j<m-1;j++){p = p->next ;}q = p->next ;printf("%d ",q->data );p->next = q->next ;headnode = p->next ;}
}
其中,我们需要注意的是 我们在进行第二个for循环时,j<m-1这个数值不要搞错。我们为什么要j<m-1呢?因为我们要保证p是在第m个人的前一个人,这样我们就可以使q指向p->next,也就是我们想要的那个第m个人,实现精准查找,然后精准“删除”。但是问题来了,我们为什么不直接将p指向第m个人呢,这样我们直接输出p不就好了么:(。非也非也!你只是想到了输出数值的事,但是后面的事你却没能想到。
我们在这段函数的主要任务是什么呢?1.输出第m个人2.删除m,让m前一个人接上m后一个人3.将领头人headnode变成m的下一个人,也就是说从m的下一个人开始报数。这样我们不难发现我们将p设为m的前一个人,是要将p-next指向q->next,实现删除m的目的,因为我们节点的指针能指向某节点的后面,却指向不了节点的前面,所以哦我们需要这样的一个p指向m前一个点。
最后,我们编写主函数,将上面的函数包装进去
其中我们要注意的是,创建空节点与数据节点不在主函数里。
int main(){struct child* headnode = createnode(1);scanf("%d %d",&n,&m);createlistBytail(headnode,n);//printlist(headnode);solve(headnode,n,m);return 0;
}
这样,我们的代码就算结束了。最后,给大家奉上“小孩报数”(即约瑟夫问题)的完整版代码
#include<stdio.h>
#include<stdlib.h>
struct child{int data;struct child* next;
};struct child* headnode;
int n,m;struct child* emptynode(){struct child* p = (struct child*)malloc(sizeof(struct child));p->data = NULL;p->next = NULL;return p;
}struct child* createnode(int num){struct child* p = (struct child*)malloc(sizeof(struct child));p->data = num;p->next = NULL;return p;
}void createlistBytail(struct child* headnode,int num){struct child* r=headnode;for (int i=2; i<=num; i++ ){struct child* s = createnode(i);s->next = r->next;r->next = s;r=s;} r->next = headnode;
}void printlist(struct child* headnode){struct child* pMove = headnode;while(pMove) {printf("%d",pMove->data );pMove = pMove->next ;}
}void solve(struct child* headnode,int n,int m){int i,j;struct child* p;struct child* q;for(i=1;i<=n;i++){p = headnode;j = 1;for(j=1;j<m-1;j++){p = p->next ;}q = p->next ;printf("%d ",q->data );p->next = q->next ;headnode = p->next ;}
}int main(){struct child* headnode = createnode(1);scanf("%d %d",&n,&m);createlistBytail(headnode,n);//printlist(headnode);solve(headnode,n,m);return 0;
}