问题介绍
问题描述
约瑟夫(Joeph)问题的一种描述是:编号为1,2,…,n的n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。一开始任选一个正整数作为报数上限值m,从第一个人开始按顺时针方向自1开始顺序报数,报到m时停止报数。报m的人出列,将他的密码作为新的m值,从他在顺时针方向上的下一个人开始重新从1报数,如此下去,直至所有人全部出列为止。试设计一个程序求出出列顺序。
基本要求
利用单向循环链表存储结构模拟此过程,按照出列的顺序印出各人的编号。
测试数据
m的初值为20;密码:3,1,7,2,4,8,4(正确的结果应为6,1,4,7,2,3,5)。
实现提示
程序运行后首先要求用户指定初始报数上限值,然后读取各人的密码。设n≤30。
代码研究
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
typedef int Status ;
typedef struct Node{
int order ;
int code ;
struct Node *next ;
}Node, *LinkList;
//完成数据n的输入功能
int Input(){
int n ;
scanf("%d", &n) ;
fflush(stdin) ;
if(n <= 30) return n ;
else{
printf("请重新输入:\n") ;
Input() ;
}
}
//打印出链表的长度
int GetLength(LinkList l) {
int len = 1 ;
LinkList p ;
p = l ;
p = p->next ;
while(p != l) {
len ++ ;
p = p->next ;
}
return len ;
}
//创建一个循环链表,并键入数据
LinkList CreatList( int e ) {
//创建一个链表LinkList和一个储存头结点的node
LinkList l ;
Node *l_head ;
l = (LinkList)malloc(sizeof(Node)) ;
l_head = l ;
//尾插法输入元素
Node *temp, *p ;
p = l ;
int i = 0, order = 1 ;
for( i = 0 ; i < e ; i ++) {
//把数据存入临时存储的链表中
temp = (LinkList)malloc(sizeof(Node)) ; //每次循环都创造一个新的结点
scanf("%d", &temp->code) ;
fflush(stdin) ;
temp->order = order ;
//利用尾插法将临时存储的结点插入到l中
temp->next = p->next ;
p->next = temp ;
p = temp ;
order ++ ;
}
l_head = l_head->next ;
p->next = l_head ; //将指向l末尾的指针指回头结点,形成循环链表
return p ;
}
//打印出链表的信息
Status ShowList(LinkList l) {
LinkList p ;
p = l ;
p = l->next ;
while(p != l) {
printf("%d-%d ",p->order, p->code) ;
p = p->next ;
}
printf("%d-%d", l->order, l->code) ;
printf("\n") ;
return OK ;
}
//找到报数为m的成员对应的指针
LinkList FindMember(LinkList l , int m, int n) {
int counter = 1 ;
LinkList p ;
p = l;
p = p->next ;
while(counter != m ) {
p = p->next ;
counter ++ ;
}
return p ;
}
//将一个元素插入现有链表的尾部
LinkList InsertList (LinkList l, LinkList temp) {
Node *p, *q;
p = l ;
q = temp;
q->next = p->next ;
p->next = q ;
return l ;
}
//删除一个链表元素
LinkList DeleteList(LinkList l, Node* index, int len){
Node *p ;
//如果只剩下一个元素,直接将指针l释放
if(len == 1)
l = NULL ;
//进行删除操作
p = l ;
while(p->next != index) {
p = p->next ;
}
p->next = p->next->next ;
return l ;
}
Status Entrance(LinkList l, int m, int Number) {
LinkList l_code ;
Node *index, *temp_code ;
index = l ;
int len = 0 ;
while(l) {
len = GetLength(l) ;
//寻找报数为m的成员,输出她/他的序号和密码值
index = FindMember( index , m, Number) ;
printf("%d ", index->order) ;
//替换m值
m = index->code ;
//将index的值备份
Node *temp_index ;
temp_index = index ;
//将m成员从链表中删除
if(temp_index == l) l = l->next ;
l = DeleteList(l, temp_index, len) ;
}
return OK;
}
int main() {
//数据n的输入
printf("请输入小组的成员数据n:\n") ;
int Number = 0 ;
Number = Input() ;
LinkList l;
l = CreatList( Number ) ;
printf("请输入m的初始值\n") ;
int m = 0 ;
scanf("%d", &m) ;
Entrance(l , m, Number) ;
return 0 ;
}
设计介绍
在设计算法的过程中没有什么高大尚的思路,作为代码路上的小菜鸡一只,除了趟雷掉坑写bug,没有什么太多的收获。虽然最后代码可以正常运行,但目测只能跑这一组数据,就当是心情日记,给大家分享一下吧。
想要实现的功能
1.输入n的操作
2.实现创建单向循环链表存放的操作
3.实现打印单向循环链表的操作
作这个功能是为了调代码的时候方便,但事实上我还是太自信了,它除了为我调试代码提供便利,同时也为我创造了无数bug。
4.找到报m的成员,确定其指针
找到报数人员相当于作业中的核心问题了,这里就能体现出合理的数据结构对于解决问题的优势了。循环链表可以很简单粗暴的用计数的方法避免掉复杂的数学运算。
5.输出m的序号、替换m值
6.创建一个新的链表,储存密码的序列
(重新为自己的链表编号)
(ps:没实现,被bug堆满了,最后全部删除)
7.将m成员从链表中删除