设计题目 1: 约瑟夫生者死者游戏
[问题描述]:
约瑟夫生者死者游戏的大意是:
30
个旅客同乘一条船,因为严重超载,加上风高浪大,
危险万分;因此船长告诉乘客,只有将全船一半的旅客投入海中,其余人才能幸免遇难。无
奈,大家只得同意这种办法,并议定
30
个人围成一圈,由第一个人开始,依次报数,数到
第
9
人,便把他投入大海中,然后从他的下一个人数起,数到第
9
人,再将他投入大海,如
此循环,直到剩下
15
个乘客为止。问哪些位置是将被扔下大海的位置。
[设计要求]:
本游戏的数学建模如下:假设
n
个旅客排成一个环形,依次顺序编号
1
,
2
,…,
n
。从
某个指定的第
1
号开始,沿环计数,每数到第
m
个人就让其出列,且从下一个人开始重新
计数,继续进行下去。这个过程一直进行到剩下
k
个旅客为止。
本游戏的要求用户输入的内容包括:
(1)
旅客的个数,也就是
n
的值;
(2)
离开旅客的间隔数,也就是
m
的值;
(3)
所有旅客的序号作为一组数据要求存放在某种数据结构中。
本游戏要求输出的内容是包括:
(1)
离开旅客的序号;
(2)
剩余旅客的序号;
所以,根据上面的模型分析及输入输出参数分析,定义一种数据结构后进行算法实现。
设计题目
2
:
约瑟夫双向生死游戏
[问题描述]:
约瑟夫双向生死游戏是在约瑟夫生者死者游戏的基础上,正向计数后反向计数,然后再
正向计数。
具体描述如下:
30
个旅客同乘一条船,因为严重超载,加上风高浪大,危险万分;因此
船长告诉乘客,只有将全船一半的旅客投入海中,其余人才能幸免遇难。无奈,大家只得同
意这种办法,并议定
30
个人围成一圈,由第一个人开始,顺时针依次报数,数到第
9
人,
便把他投入大海中,然后从他的下一个人数起,逆时针数到第
5
人,将他投入大海,然后从
他逆时针的下一个人数起,顺时针数到第
9
人,再将他投入大海,如此循环,直到剩下
15
个乘客为止。问哪些位置是将被扔下大海的位置。
[设计思路]:
本游戏的数学建模如下:假设
n
个旅客排成一个环形,依次顺序编号
1
,
2
,…,
n
。从
某个指定的第
1
号开始,沿环计数,数到第
m
个人就让其出列,然后从第
m+1
个人反向计
数到
m-k+1
个人,让其出列,然后从
m-k
个人开始重新正向沿环计数,再数
m
个人后让其
出列,然后再反向数
k
个人后让其出列。这个过程一直进行到剩下
q
个旅客为止。
本游戏的要求用户输入的内容包括:
(1)
旅客的个数,也就是
n
的值;
(2)
正向离开旅客的间隔数,也就是
m
的值;
(3)
反向离开旅客的间隔数,也就是
k
的值;
(4)
所有旅客的序号作为一组数据要求存放在某种数据结构中。
双向循环链表实现:
链表采用带头结点的结构,便于链表的插入和判空。
插入:直接利用头结点的前驱指针,可以找到链表的尾巴,然后呢通过头和尾进行插入。
判空:令 头结点的后驱结点等于自身 为空。
删除:需要注意将删除结点的前驱结点的后继指针和后继结点的前驱指针改掉
实现思路:
由于链表带头结点,所以但头结点不包含信息,循环报数时(即以此遍历链表),会误将头结点作为玩家,因此头结点是一个冗余的结点,所以在游戏开始前,需要将头结点去掉,然后默认将编号为1的玩家当作头结点。游戏结束后指针一般不是指向编号最小的那个,所以可以通过前驱指针遍历,当当前结点的前驱结点的编号大于自身时循环结束,这样就找到编号最小的人。游戏结束后不要忘了将把链表改为有结点的链表,保证链表的完整性。
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define ElemType int
// 双向循环链表实现
// 带头结点-不用于保存数据 ,用于判空即 头结点的前驱和后继相等
typedef struct _Node{
struct _Node* pre;// 前驱
ElemType elem;// 编号
struct _Node* next;// 后继
} *List,Node;
// 创建一个空链表
List creatList(){
List list = (List)malloc( sizeof(Node) );
assert( list != NULL );
list->pre = list->next = list;// 指向自身便于后续插入
return list;
}
// 尾插
void push_back( List list, ElemType elem ) {
assert( list );
Node* node = (Node*)malloc( sizeof(Node) );
assert( node );
node->elem = elem;
node->next = list;// 新结点的后继是表头
node->pre = list->pre;// 新结点的后继是表头的前驱
list->pre->next = node;// 将老的尾部的next指向新结点
list->pre = node;// 将表头前驱指向新结点
}
// 从头遍历一遍链表
void printList( List list ){
Node* start = list->next;
while( start != list ){
printf("%d ",start->elem);
start = start->next;
}
printf("\n");
}
// 在传入结点所在的链表,将该结点删除
int erase( Node* node ){
assert( node != NULL );
if( node->pre == node ){// 空链表,删除失败
return 0;
}
node->pre->next = node->next;// 改变前驱结点的后继
node->next->pre = node->pre;// 改变后继结点的前驱
free( node );
return 1;
}
// 游戏,返回的链表保存了嘎的人的编号
List game1( List* list, int n, int m, int k ){
// 由于传入的链表是带有头结点的,多了一个空数据,不利于后续遍历
// 所以改造成无头结点的
if( (*list)->pre == *list ) {// 无玩家,直接退出
return ;
}
Node* t = *list;// 保存头结点,便于后续恢复
// 改造为无头结点的链表
*list = (*list)->next;
(*list)->pre = t->pre;
t->pre->next = *list;
free( t );
List die = creatList();
// 开始推人
int cnt = 1;
while( n > k ){
if( cnt == m ){
t = (*list)->next;
push_back( die, (*list)->elem );
erase( *list );
*list = t;
cnt = 1;
n--;
} else {
cnt++;
*list = (*list)->next;
}
}
// 恢复链表
// 首先找到最小元素位置
while( (*list)->pre->elem < (*list)->elem ){
(*list) = (*list)->pre;
}
// printf("*****%d\n",(*list)->elem);
// 将头结点插入
t = (Node*)malloc(sizeof(Node));
// 先让 头结点 主动与链表 逻辑链接
t->next = *list;
t->pre = (*list)->pre;
// 再让 链表 与头结点 逻辑链接
(*list)->pre->next = t;
(*list)->pre = t;
// 最后,链表指向头结点
*list = t;
return die;
}
// 游戏,返回的链表保存了嘎的人的编号
List game2( List* list, int n, int m, int k, int q ){
// 由于传入的链表是带有头结点的,多了一个空数据,不利于后续遍历
// 所以改造成无头结点的
if( (*list)->pre == *list ) {// 无玩家,直接退出
return ;
}
Node* t = *list;
*list = (*list)->next;
(*list)->pre = t->pre;
t->pre->next = *list;
free( t );
List die = creatList();
// 开始推人
int cnt = 1;
int isSwitch = 1;//初始为顺时针
while( n > q ){
if( isSwitch ) {
if( cnt == m ){
t = (*list)->next;
push_back( die, (*list)->elem );
erase( *list );
isSwitch = 0;
cnt = 1;
n--;
*list = t;
} else {
cnt++;
*list = (*list)->next;
}
} else {
if( cnt == k ){
t = (*list)->pre;
push_back( die, (*list)->elem );
erase( *list );
isSwitch++;
cnt = 1;
n--;
*list = t;
} else {
cnt++;
*list = (*list)->pre;
}
}
}
while( (*list)->pre->elem < (*list)->elem ){
(*list) = (*list)->pre;
}
// 将头结点插入
t = (Node*)malloc(sizeof(Node));
// 先让 头结点 主动与链表 逻辑链接
t->next = *list;
t->pre = (*list)->pre;
// 再让 链表 与头结点 逻辑链接
(*list)->pre->next = t;
(*list)->pre = t;
// 最后,链表指向头结点
*list = t;
return die;
}
int getLength( List list ){
Node* node = list->next;
int cnt = 0;
while( node != list ){
cnt++;
node = node->next;
}
return cnt;
}
int main(){
int n,m,k,i,q;
printf("第一个游戏的输入:(总人数,间隔,剩余人数)\n");
scanf("%d %d %d",&n,&m,&k);
List list1 = creatList();
List list2 = creatList();
for( i = 1; i <= n; i++ ){
push_back( list1, i );
}
// printf("长度:%d\n",getLength(list1));
// printf("长度:%d\n",getLength(list1));
List die1 = game1( &list1, n, m, k );
printf("题目一:\n");
printf("剩余旅客编号:(%d人,按编号升序 ) ",getLength(list1));
printList( list1 );
printf("离开旅客编号:(%d人按先离开次序) ",getLength(die1));
printList( die1 );
printf("\n");
printf("第二个游戏的输入:(总人数,顺时针间隔,逆时针间隔,剩余人数)\n");
scanf("%d %d %d %d",&n,&m,&k,&q);
for( i = 1; i <= n; i++ ){
push_back( list2, i );
}
List die2 = game2( &list2, n, m, k, q );
printf("题目二:\n");
printf("剩余旅客编号:(%d人,按编号升序 ) ",getLength(list2));
printList( list2 );
printf("离开旅客编号:(%d人按先离开次序) ",getLength(die2));
printList( die2 );
return 0;
}