古代のパズルから現代の奇跡まで:魔法のジョセフリング(C言語)

序文

ジョセフの指輪は古くからある興味深い問題であり、人々の間の生死を賭けた争いが関係しており、人々の長期的な思考と探求を呼び起こしてきました。この問題はさまざまな方法で解決できますが、それぞれに独自の長所と短所があります。

配列を使用してジョセフ リングを実装すると、人員の順序を簡単かつ直観的に表すことができますが、配列サイズの静的な制限とデータ コピーの低い操作効率の影響を受けます。ただし、単一リンク リストの実装を使用すると、実行時にジョセフ リングのサイズを動的に調整し、ポインターの更新を通じてノードを削除できるため、効率が向上します。

さらに、ジョセフ リング問題は数式で解く方が効率的で、データ構造を構築したり走査したりする必要がなく、最後の生存者の数は単純な数学的計算で取得できます。このアプローチは、より大きな問題にうまく機能し、非常に効率的です。

各実装には独自の利点があり、適切なものを選択するのは実際のニーズによって異なります。いずれにせよ、ジョセフ リング問題を解決するには、数学とアルゴリズムへの興味と探求を刺激しながら、数学的思考とプログラミング スキルが必要です。

ヨセフの指輪の歴史的背景

ヨセフス問題は、古代ユダヤ人の歴史家フラウィウス・ヨセフスにちなんで名付けられた古代の数学の問題です。伝説によると、ヨセフスはユダヤ人の将軍で、ローマ軍の包囲下でユダヤ人の要塞に閉じ込められました。

伝統的なヨセフスの記述によると、彼と39人の同胞は洞窟に閉じ込められました。最後に残った者として、彼らはローマ人の捕虜になるよりは自殺したほうがいいと決心しました。そこで彼らは輪になって立ち、一人から数え、指定された数まで数えるごとにその人を殺し、次の人から数え始めることにしました。これは、一人だけが残り、彼だけが生き残るまで続きます。

この問題の中心は、どのポジションが最後に生き残るかを決定することです。ジョセフスは、彼自身の回想と経験に基づいて解決策を提案します。彼の説明によると、彼は7番目の位置に立っていました、つまり、円の中で7人目まで数えたとき、彼は最後の生存者になるでしょう。

配列メソッドで解決する

C 言語を使用してジョセフ リングを実現する場合、配列を使用して人員の順序を表し、ループと条件文を通じて殺害プロセスをシミュレートできます。詳細な説明は次のとおりです。

#include <stdio.h>

#define MAX_SIZE 100

// 约瑟夫环函数
int josephus(int n, int k)
{
    
    
    int circle[MAX_SIZE]; // 用数组表示约瑟夫环
    int i, index, count;

    // 初始化约瑟夫环
    for (i = 0; i < n; ++i)
    {
    
    
        circle[i] = i + 1;
    }

    index = 0; // 从第一个人开始
    count = 0; // 计数器

    // 开始杀人循环,直到只剩下一个人
    while (n > 1)
    {
    
    
        count++;

        // 数到第k个人就杀掉他
        if (count == k)
        {
    
    
            // 打印被杀的人的编号
            printf("杀死第 %d 个人\n", circle[index]);

            // 将被杀的人从约瑟夫环中移除
            for (i = index; i < n - 1; ++i)
            {
    
    
                circle[i] = circle[i + 1];
            }

            count = 0; // 重置计数器
            n--; // 约瑟夫环的人数减一
        }

        index++; // 移向下一个人

        // 当到达约瑟夫环的末尾时,回到开始位置
        if (index == n)
        {
    
    
            index = 0;
        }
    }

    // 返回最后幸存者的编号
    return circle[0];
}

int main()
{
    
    
    int n, k;
    int survivor;

    printf("请输入约瑟夫环的人数n:");
    scanf("%d", &n);

    printf("请输入每次数的数字k:");
    scanf("%d", &k);

    survivor = josephus(n, k);
    printf("最后幸存者的编号是:%d\n", survivor);

    return 0;
}

circleこのコードでは、最初にジョセフ リングを表す配列を宣言します。サイズは次MAX_SIZEのとおりです。次に、ループを使用してジョセフのリング内の人々を初期化し、1 から n まで順番にランク付けします。

次に、変数indexcount変数を使用して殺害プロセスをシミュレートします。index現在カウントされている人の位置と、countすでにカウントされている人の数を表示します。

キル ループでは、最初にカウンターをインクリメントしcount、次に k 人目に到達したかどうかを確認します。k 人目が数えられたら、殺された人の番号を出力し、その人をジョセフリングから外します。

その人を削除した後、その人を後ろに移動して空席を埋め、ジョセフリングの人数を 1 人減らす必要があります。

最後に、残り 1 人になるとループは終了し、最後の生存者の番号を返します。

main 関数では、ユーザーが入力したジョセフ リングの番号 n と各カウントの番号 k を受け取り、josephus最後の生存者の番号を計算して出力する関数を呼び出します。

C言語シングルリンクリストソリューション

C 言語を使用してジョセフ リングを実現する場合、単結合リストを使用して人員の順序を表現し、ループと条件文を通じて人を殺すプロセスをシミュレートすることもできます。詳細な説明は次のとおりです。

#include <stdio.h>
#include <stdlib.h>

// 定义单链表节点结构
typedef struct Node
{
    
    
    int data;
    struct Node *next;
} Node;

// 创建一个单链表节点
Node *createNode(int data)
{
    
    
    Node *newNode = (Node *)malloc(sizeof(Node));
    newNode->data = data;
    newNode->next = NULL;
    return newNode;
}

// 创建约瑟夫环
Node *createJosephusCircle(int n)
{
    
    
    Node *head = createNode(1);
    Node *prev = head;
    int i;
    for (i = 2; i <= n; ++i)
    {
    
    
        Node *newNode = createNode(i);
        prev->next = newNode;
        prev = newNode;
    }
    prev->next = head; // 将末尾节点指向头节点形成循环
    return head;
}

// 模拟杀人过程
int josephus(int n, int k)
{
    
    
    Node *head = createJosephusCircle(n);
    Node *current = head;
    Node *prev = NULL;
    int count = 0;

    while (head->next != head)
    {
    
    
        count++;
        if (count == k)
        {
    
    
            printf("杀死第 %d 个人\n", current->data);
            prev->next = current->next;
            free(current);
            current = prev->next;
            count = 0;
        }
        else
        {
    
    
            prev = current;
            current = current->next;
        }
    }

    int survivor = head->data;
    free(head);
    return survivor;
}

int main()
{
    
    
    int n, k;
    int survivor;

    printf("请输入约瑟夫环的人数n:");
    scanf("%d", &n);

    printf("请输入每次数的数字k:");
    scanf("%d", &k);

    survivor = josephus(n, k);
    printf("最后幸存者的编号是:%d\n", survivor);

    return 0;
}

このコードでは、まず単一のリンク リスト ノード構造を定義します。この構造には、従業員番号を格納するフィールドと次のノードへのポインタがNode含まれています。datanext

次に、createNode新しい単一リンク リスト ノードを作成し、そのノードへのポインタを返す関数を定義します。

次に、createJosephusCircleジョセフ リングを作成する関数を作成しました。ノードを1からnまで順番に作成し、nextポインタを使って接続します。最後に、nextエンド ノードのポインタをヘッド ノードに向けて、ループを形成します。

次に、josephus殺害プロセスをシミュレートする関数を作成しました。ポインタを使用してcurrent現在の人数を追跡し、prevポインタを使用して前のノードを記録して、人を殺害したときにリンク リストを更新できるようにします。変数を使用してcount数え、k 人目が数えられると、殺された人の番号を出力し、リンクされたリストから削除します。

最後に、ヘッド ノードの番号を最後の生き残りとして取得し、メモリを解放します。

main 関数では、ユーザーが入力したジョセフ リングの番号 n と各カウントの番号 k を受け取り、josephus最後の生存者の番号を計算して出力する関数を呼び出します。

単連結リストを使用してジョセフリングを実装するC言語コードの詳細説明です。

数式(再帰的方法)

数式を使用して、ジョセフの指輪の最後の生存者の数を計算します。これは再帰的または反復的に実行できます。反復アプローチを使用した詳細な説明は次のとおりです。

#include <stdio.h>

int josephus(int n, int k)
{
    
    
    int survivor = 0;
    int i;

    // 从n=1的情况开始递推计算
    for (i = 2; i <= n; ++i)
    {
    
    
        survivor = (survivor + k) % i;
    }

    // 因为编号从1开始,所以加1得到幸存者的编号
    survivor += 1;

    return survivor;
}

int main()
{
    
    
    int n, k;
    int survivor;

    printf("请输入约瑟夫环的人数n:");
    scanf("%d", &n);

    printf("请输入每次数的数字k:");
    scanf("%d", &k);

    survivor = josephus(n, k);
    printf("最后幸存者的编号是:%d\n", survivor);

    return 0;
}

このコードでは、josephusジョセフ リング内の人数と各カウントの数をパラメーター n と k で表す関数を定義します。

反復することで、n=2 から開始し、各人が殺された後の次の人の数を計算します。survivor計算中に生存者の数を追跡するために変数を使用します。初期値は 0 です。

反復の各ラウンドで、survivork を加算し、次に殺害される人の数を取得するために、人の総数 i を法として計算します。このようにして、最後の人だけが残るまで、次に殺される人を順番に見つけます。

最後に、survivorジョセフの指輪の人員番号付けスキームと一致するように 1 を追加し、最後の生存者の番号を返します。

main 関数では、ユーザーが入力したジョセフ リングの番号 n と各カウントの番号 k を受け取り、josephus最後の生存者の番号を計算して出力する関数を呼び出します。

ジョセフリングを実現するためのC言語コードを数式を使って詳しく解説します。

これら 3 つの方法の長所と短所、および最適化スキームをまとめます。

以下に、ジョセフ リングの 3 つの実装の長所と短所、および考えられる最適化ソリューションをまとめます。

1. 配列の実装:
利点:

  • シンプルかつ直観的で、理解と実装が簡単です。
  • 保存速度とアクセス速度は高速で、配列インデックスを直接使用するためポインター操作は必要ありません。

欠点:

  • 配列のサイズは静的であり、コンパイル時に決定する必要があるため、ジョセフ リングのサイズが制限されます。
  • 人物を削除すると次の人物を前に進める必要があり、データのコピー作業が多くなり効率が悪い。

最適化可能なソリューション

  • 実行時に必要に応じてジョセフ リングのサイズを変更できるように、動的配列または動的にリンクされたリストを使用します。

2. 単一リンクリストの実装:
利点:

  • 動的なメモリ割り当て。ジョセフ リングのサイズは実行時に動的に調整できます。
  • ノードの削除操作ではポインタを更新するだけで済み、より効率的です。

欠点:

  • ノード アクセスはリンク リストをトラバースすることによって実現する必要がありますが、これは配列のランダム アクセスよりも効率が低くなります。
  • リンク リスト ノードには、ポインタ情報を保存するために追加のメモリ スペースが必要です。

最適化可能なソリューション

  • 二重リンクリストを使用すると、ノードのアクセス効率が向上します。

3. 数式の実現:
メリット:

  • ジョセフ リングのデータ構造を構築してトラバースする必要はなく、計算は数式に直接基づいて行われるため、非常に効率的です。
  • ジョセフ リングのサイズに制限されず、非常に大きな問題に適しています。

欠点:

  • 最後に生き残った人の番号だけは取得できますが、殺された人の順序は取得できません。
  • 一部の特殊な k 値では、周期法則が発生する場合があります。

最適化可能なソリューション

  • 数式の計算プロセスは、すでに最適解であるため、単純な最適化では改善できません。

一般に、適切な実装の選択は状況によって異なります。ジョセフ リングのサイズが小さく、完全な殺害命令を取得することが要件である場合は、配列または単一リンク リストを選択して実装できます。ジョセフリングのサイズが大きい場合や、最後の生存者の数だけを取得したい場合には、数式が最適解となりますさらに、特定の要件に応じて、最適化スキームを組み合わせて、実装の効率と柔軟性を向上させることもできます。

ジョセフリング問題の魅力は、数学、論理、プログラミングを組み合わせており、問題を解く過程で思考力を発揮するだけでなく、数学やコンピュータサイエンスの魅力を体験できることです。この問題は、精神を訓練する機会であると同時に、数学とアルゴリズムへの素晴らしい旅でもあります。学術研究でも日常生活でも、ジョセフ リング問題は私たちに楽しみとインスピレーションをもたらします。

おすすめ

転載: blog.csdn.net/m0_75215937/article/details/131965513