问题描述
据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。 [1]
17世纪的法国数学家加斯帕在《数目的游戏问题》中讲了这样一个故事:15个教徒和15 个非教徒在深海上遇险,必须将一半的人投入海中,其余的人才能幸免于难,于是想了一个办法:30个人围成一圆圈,从第一个人开始依次报数,每数到第九个人就将他扔入大海,如此循环进行直到仅余15个人为止。问怎样排法,才能使每次投入大海的都是非教徒。
算法解析
编号 为 1, 2,…, N 的 N 个人 按 顺时针 方向 围坐 一圈, 每人 持有 一个 密码( 正 整数), 一 开始 任选 一个 正 整数 作为 报数 上 限值 M, 从 第一 个人 开始 按 顺时针 方向 自 1 开始 按 顺序 报数, 报到 M 时 停止 报数。 报 M 的 人 出列, 将他 的 密码 作为 新的 M 值, 从 他在 顺时针 方 向上 的 下一 个人 开始 重新 从 1 报数, 如此 下去, 直至 所有人 全部 出列 为止。 试 设计 一个 程序 求出 出列 顺序。
显然 当 有人 退出 圆圈 后, 报数 的 工作 要从 下一 个人 开始 继续, 而 剩下 的 人 仍然是 围成 一个 圆圈 的, 因此 可以 使用 循环 单 链 表。 由于 退出 圆圈 的 工作 对应 着 表中 结点 的 删除 操作, 对于 这种 删除 操作 频繁 的 情况, 选用 效率 较 高的 链 表 结构。 为了 程序 指针 每 一次 都 指向 一个 具体 的 代表 一个 人的 结点 而 不需要 判断, 链 表 不带 头 结点。 所以, 对于 所有人 围成 的 圆圈 所 对应 的 数据 结构 采用 一个 不带 头 节点 的 循 环链 表 来 描述。 设 头 指针 为 p, 并 根据 具体 情况 移动。
为了 记录 退出 的 人的 先后 顺序, 采用 一个 顺序 表 进行 存储。 程序 结束 后再 输出 依次 退出 的 人的 编号 顺序。 由于 只 记录 各个 节点 的 data 值 就可以, 所以 定义 一个 整型 一 维 数组。 如 int quit[ n]; n 为 一个 根据 实际问题 定义 的 一个 足够 大的 整数。
代码实现
int JosephusLoop(st_dataNode * head, int * output, int len, int M){
if(NULL == head || NULL == output || len < 0){
printf("%s: param error\n",__func__);
return PARAM_ERR;
}
st_dataNode * suicide = NULL;
st_dataNode * prev = NULL;
st_dataNode * next = NULL;
int cnt = 0, k = 0;
prev = head;
suicide = head->next;
next = suicide->next;
while(suicide->next != suicide){
cnt++;
/*报数为M,那么自杀*/
if(0 == cnt % M){
/*记录到输出*/
output[k++] = suicide->data;
/* 自杀 */
free(suicide);
/*摘掉suicide*/
prev->next = next;
suicide = next;
next = suicide->next;
/*重新计数*/
cnt = 0;
continue;
}
prev = suicide;
suicide = next;
next = suicide->next;
}
output[k++] = suicide->data;
free(suicide);
return SUCCESS;
}
void testJosephusLoop(void){
int len = getListLen(ghead1);
int i = 0;
int M = 1768 % len;
/*存储结果*/
int output[100] = {0};
/*形成环*/
st_dataNode * tail = NULL;
tail = ghead1;
while(NULL != tail->next){
tail = tail->next;
}
tail->next = ghead1;
JosephusLoop(ghead1, output, 1000, M);
printf(" [ ");
for(i = 0; i < len; i++){
printf(" %d ", output[i]);
}
printf(" ] \n");
ghead1 = NULL;
return;
}
编译调试
gcc listMain.c list.c -o a.exe -DDEBUG
调试输出
========= Dump List 0x1630230 ===========
0 1 3 4 5 6 6 7 8 9 11 19 22 28 29 32 47 53 116 119
===================================
[ 8 47 5 28 3 22 4 32 7 0 53 19 11 29 119 6 6 116 9 1 ]