畢業生求職必會算法 約瑟夫環問題

寫在前面: 我是 「揚帆向海」,這個暱稱來源於我的名字以及女朋友的名字。我熱愛技術、熱愛開源、熱愛編程。技術是開源的、知識是共享的

這博客是對自己學習的一點點總結及記錄,如果您對 Java算法 感興趣,可以關注我的動態,我們一起學習。

用知識改變命運,讓我們的家人過上更好的生活

相關文章

點此查看 【算法系列】 博客文章


一、問題描述

約瑟夫問題(有時也稱爲約瑟夫斯置換,是一個出現在計算機科學和數學中的問題。在計算機編程的算法中,類似問題又稱爲約瑟夫環。又稱“丟手絹問題”。)

據說著名猶太歷史學家 Josephus有過以下的故事:在羅馬人佔領喬塔帕特後,39 個猶太人與Josephus及他的朋友躲到一個洞中,39個猶太人決定寧願死也不要被敵人抓到,於是決定了一個自殺方式,41個人排成一個圓圈,由第1個人開始報數,每報數到第3人該人就必須自殺,然後再由下一個重新報數,直到所有人都自殺身亡爲止。然而Josephus 和他的朋友並不想遵從。首先從一個人開始,越過k-2個人(因爲第一個人已經被越過),並殺掉第k個人。接着,再越過k-1個人,並殺掉第k個人。這個過程沿着圓圈一直進行,直到最終只剩下一個人留下,這個人就可以繼續活着。問題是,給定了和,一開始要站在什麼地方纔能避免被處決?Josephus要他的朋友先假裝遵從,他將朋友與自己安排在第16個與第31個位置,於是逃過了這場死亡遊戲。
在這裏插入圖片描述

二、問題分析

這個問題可以這樣來看,有N個人圍成一圈,第一個人從1開始報數,報到3的出圈被殺掉;下一個人接着從1開始報數… …這樣循環反覆,直到剩下最後兩個人,求出最後兩個人的位置。

三、實現邏輯

1. 構建一個單向循環鏈表(鏈表的尾部指向開頭)

① 首先創建循環鏈表的頭節點,讓head指向該節點,並形成環形;

② 之後每當創建一個新的節點,就把該節點添加到已有的環形鏈表中

在這裏插入圖片描述

2. 遍歷單向的循環鏈表

在此遍歷中,當有節點被刪除以後,就要向後移動節點。
在這裏插入圖片描述

注意

噹噹前節點的值等於當前節點的下一個節點的值的時候,循環結束

四、代碼實現

package com.study.algorithm;

import java.util.Scanner;

/**
 * @Description: 使用循環鏈表解決約瑟夫環問題
 * @Author: 揚帆向海
 * @Date: Created in 2020/5/2
 */
public class JosephCircle {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("請輸入總人數N(N>=2):");
        int n = scanner.nextInt();
        if (n < 2) {
            System.out.println("您好!請確保輸入的人數大於等於 2");
            return;
        }
        // 構建鏈表並獲取頭節點,把頭節點賦值給currentNode
        Node currentNode = buildData(n);
        // 用來計數
        int count = 0;
        // 循環鏈表當前節點的上一個節點
        Node beforeNode = null;
        // 遍歷循環鏈表
        while (currentNode != currentNode.next) {
            count++;
            if (count == 3) {
                // 向後移動節點
                beforeNode.next = currentNode.next;
                System.out.println("出環的編號是: " + currentNode.data);
                count = 0;
                currentNode = currentNode.next;
            } else { // 向後移動節點
                beforeNode = currentNode;
                currentNode = currentNode.next;
            }
            // 表示只有兩個節點了,不再進行出環操作
            if (beforeNode.data == currentNode.next.data) {
                break;// 跳出循環
            }
        }
        // 輸出最後留在環中的編號
        System.out.println("最後留在環中的編號是: " + currentNode.data + "," + currentNode.next.data);
    }

    /**
     * 構建單向循環鏈表
     *
     * @param n 人數
     * @return 返回頭節點
     */
    private static Node buildData(int n) {
        // 循環鏈表的頭節點
        Node head = null;
        // 循環鏈表當前節點的前一個節點
        Node prev = null;
        for (int i = 1; i <= n; i++) {
            Node newNode = new Node(i);
            // 如果是第一個節點
            if (i == 1) {
                head = newNode;
                prev = head;
                // 跳出當前循環,進行下一次循環
                continue;
            }
            // 如果不是第一個節點
            prev.next = newNode;
            prev = newNode;
            // 如果是最後一個節點
            if (i == n) {
                prev.next = head;
            }
        }
        return head;
    }
}

/**
 * 鏈表節點
 */
class Node {
    // 當前存儲的數據
    int data;
    // 當前節點的下一個節點
    Node next;

    public Node(int data) {
        this.data = data;
    }
}

測試結果:

請輸入總人數:41
出環的編號是: 3
出環的編號是: 6
出環的編號是: 9
出環的編號是: 12
出環的編號是: 15
出環的編號是: 18
出環的編號是: 21
出環的編號是: 24
出環的編號是: 27
出環的編號是: 30
出環的編號是: 33
出環的編號是: 36
出環的編號是: 39
出環的編號是: 1
出環的編號是: 5
出環的編號是: 10
出環的編號是: 14
出環的編號是: 19
出環的編號是: 23
出環的編號是: 28
出環的編號是: 32
出環的編號是: 37
出環的編號是: 41
出環的編號是: 7
出環的編號是: 13
出環的編號是: 20
出環的編號是: 26
出環的編號是: 34
出環的編號是: 40
出環的編號是: 8
出環的編號是: 17
出環的編號是: 29
出環的編號是: 38
出環的編號是: 11
出環的編號是: 25
出環的編號是: 2
出環的編號是: 22
出環的編號是: 4
出環的編號是: 35
最後留在環中的編號是: 16,31

由於水平有限,博客中難免會有一些錯誤,有紕漏之處懇請各位大佬不吝賜教!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章