約瑟夫環之遞歸算法

問題描述:
約瑟夫環是一個數學的應用問題:已知n個人(以編號1,2,3…n分別表示)圍坐在一張圓桌周圍。由第一個人開始報數,數到m的那個人出列;他的下一個人又從1開始報數,數到m的那個人又出列;依此規律重複下去,直到最後剩下一個人。
一般的思路就是通過一個count計數的方法,循環遍歷鏈表,逐一刪除。但是這樣的方法時間複雜度要有O(n×m),(總共要刪除n-1個數,每刪除一個需要遍歷m次)。那有沒有什麼辦法直接就找到我們需要刪除的那個節點呢,比如給你鏈表1→2→3→4→5→1,這是環狀鏈表。如果m=3的話,最後留下的是第4個節點。這裏我們就需要借用數學的方式來進行推到公式,利用遞歸的方式求解了。
遞歸求解思路:
(我通過遞歸的方式,返回需要刪除第幾個節點,所以,節點編號從1開始,返回的也是第幾個節點)
第一次刪除第m個節點很簡單,就是m%n的那個節點,但是第二次刪除節點就有點困難了,爲什麼,因爲,你需要從第m+1的節點重新從1開始數,數到第m個節點再次刪除。其實,不知道你們有沒有發現,其實第二次開始數的第1個節點,就是上一次的m+1號節點。所以前一個鏈表節點和後一個鏈表節點對應的編號關係就是:
oldNum=(newNum+m-1)%(當前節點數)+1 (1)
下面從圖片分析下:
這裏寫圖片描述

根據公式(1)的關係,你就可以由新鏈表找到對應就鏈表的編號位子了。當鏈表中由i個節點時,編號對應的公式是:num(i) = (num(i-1)+m-1)%i +1;
(如果鏈表從0開始編號的話,那麼公式就是num(i) = (num(i-1)+m)%i ),返回的是下標值,且最後num(1) = 0;)

代碼實現:

public class JosephusProblem {
   //鏈表節點類
    public static class Node {
        public int value;
        public Node next;
        public Node(int data) {
            this.value = data;
        }
    }
    public static Node josephusKill2(Node head, int m) {
        if (head == null || head.next == head || m < 1) {
            return head;
        }
        Node cur = head.next;
        int tmp = 1; // 表示鏈表長度
        /*
        *計算鏈表長度
        */
        while (cur != head) {
            tmp++;
            cur = cur.next;
        }
        tmp = getLive(tmp, m); // 返回鏈表中應該刪除的那個值位置
        while (--tmp != 0) {
            head = head.next;
        }
        head.next = head;
        return head;
    }

    public static int getLive(int i, int m) {
        if (i == 1) {
        //如果從0開始編號的話,此處return 0
            return 1;
        }
        //如果從0開始編號的話,此處(getLive(i - 1, m) + m) % i,返回的就是下標值
        return (getLive(i - 1, m) + m - 1) % i + 1;//這個就是上述公式實現
    }
}

參考文獻:
求自然語言解釋:約瑟夫環之遞歸算法?

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