目次
序文
今日は、ジョセフ問題という興味深い問題をやってみます。この問題はヨーロッパ中世の物語に由来します。今度は、この興味深い問題をプログラミングで解決してみます。見てみましょう!
物語の背景
有名なユダヤ人歴史家ヨセフスは次のような話をした と言われています: ローマ人がチョタパットを捕らえた後、39 人のユダヤ人がヨセフスとその友人たちと一緒に洞窟に隠れました。39 人のユダヤ人は敵に捕まるくらいなら死んだ方がマシだと決心しました。自殺方法が決められ、41人が円形に並び、最初の人が数え始め、3人目が数えられるたびにその人は自殺しなければならず、全員が自殺するまでまた次の人が数え直す、死ぬまで。しかし、ヨセフスと彼の友人たちは従いたくありませんでした。まず 1 人から始めて、k-2 人を横切り (最初の人が横切られているため)、k番目の人を殺します。次に、k-1 人を通り過ぎて、k人目を殺します。このプロセスは円に沿って続き、最終的に 1 人だけが残り、その人が生き続けることができます。問題は、 と が与えられた場合、処刑を避けるために最初にどこに立っておけばよいかということです。ヨセフスは友人にまず従うふりをしてもらい、友人と自分を16位と31位に配置し、デスゲームを脱出した。
17 世紀のフランスの数学者ガスパールは、「数字のゲーム」の中で、15 人の信者と 15 人の非信者が深海で危険にさらされ、残りの半数が生き残る前に海に投げ込まれなければならなかったという話をしました。それは難しいので、最初の人から数えて30人が円陣を組んで9人ごとに海に投げ込まれるという方法を考え出し、これを15人になるまで続けました。海に投げ込まれるたびに非信者になるようにするにはどうすればよいかを尋ねます。
ジョセフ問題
キーボードから n と s の 2 つのデータを取得します。n は人数を表し、s は最初の人から数えることを表します。s 人目が数えられると、その人はアウトになります。問題: 最後に残ったのは誰ですか? 数人?
循環リンク リスト ソリューション
ここでは、循環リンク リストの形式でこの問題を解決できます。プロセス図は次のとおりです。
まず、現在入力されている人数に基づいて対応する循環リンク リストを作成し、そのリストに各人の位置を順番に保存します。
次に、以下のように 1 人目から数え始め、3 人目が数えられた時点で削除操作を実行し、4 番目のノードから新しいラウンドを開始します...
コードは次のようになります。
#include <stdio.h>
#include<stdlib.h>
//节点
typedef struct node {
int num;
struct node* next;
}Node;
//创建一个环形链表
Node* create_list(int n) {
Node* head, * tail;
head = tail = NULL;
for (int i = 0; i < n; i++) {
Node* p = (Node*)malloc(sizeof(Node));
p->num = i + 1; //依次标记当前位置
if (head == NULL) {
head = p;
tail = p;
head->next = NULL;
}
else
{
tail->next = p;
tail = p;
}
tail->next = head;
}
return head; //返回头结点
}
int main() {
int n, s;
printf("请输入:");
scanf("%d %d", &n, &s);
Node* cur = create_list(n);
int count = 1; //此时cur指向的是第一个节点,所以count为1
while (n != 1) { //当n=1时候,结束循环,此时剩下最后一个人
count++;//先进行count统计
if (count == s) {
n--;
//进行删除节点操作
Node* del_node = cur->next;
cur->next = del_node->next;
free(del_node);//释放掉这个节点
//此时count回归到1,也就是重新开始新的一轮
count = 1;
}
cur = cur->next;
}
printf("最后剩下的是:%d\n", cur->num);
}
アレイソリューション
リンクリストとは異なり、配列は人数をカスタマイズできないため、配列の番号が事前に書き込まれており、配列の実行効率が高くなります。循環リンクリストはトラバーサルによって実行する必要があり、時間計算量がより高い位置は O(n) であり、配列は時間計算量 O(1) でこの位置を直接見つけることができます。1 つずつトラバースする必要はありません。コードは次のとおりです。
//数组实现
#include<stdio.h>
void function(int* num, int length, int s, int start) {
int count = 0;
int i = start - 1;//标记起始位置
int n = length; //当前人数
while (n != 1) {
i = (i + s - 1) % n; //下一个出局人的位置
for (int j = i + 1; j < length; j++)
num[j - 1] = num[j]; //进行删除操作,把要删除的数字后面的依次往前移动,覆盖掉这个要删除的数字
n--;//删除操作完成,减少一个人
if (i == n) { //当i超出数组范围的时候,i就回归到第一个为止
i = 0;
}
}
printf("最后一个 :%d", num[i]);
return;
}
int main() {
int num[6];//这里就已经定义好了6个人
int s; //每次数到s,出局一个
printf("请输入:");
scanf("%d", &s);
for (int i = 0; i < sizeof(num) / sizeof(int); i++)
num[i] = i+1;
function(num, sizeof(num) / sizeof(int), s, 0);
}
今日はここまでです、また次回!
壁紙を共有する: