1. 程式人生 > 其它 >Java學習——資料結構之約瑟夫問題

Java學習——資料結構之約瑟夫問題

約瑟夫 問題描述:設編號為1、2、3... ...n的n個人圍坐一圈,約定編號為k(1<=k<=n)的人從1開始報數,數到m的那個人先出列,他的下一位又從1開始報數,數到m的那個人又出列,依次類推,直到所有人都出列為止,由此產生一個出隊編號的序列,求此序列。

演算法思路

先構成一個有n個結點的單迴圈連結串列,然後由k結點起從1開始計數,計到m時,儲存對應節點的編號,並將節點從連結串列中刪除,然後再從被刪除結點的下一個結點又從1開始計數,直到最後一個結點從連結串列刪除。

程式碼分析

1.構建環形連結串列:

  • 建立第一個結點,並讓first指向該結點,並將該節點的next域指向自己形成環形。

  • 每建立一個新節點,就把該節點加入到已有的環形連結串列中

2.遍歷環形連結串列

  • 定義一個輔助變數curBoy ,指向first節點

  • 通過while迴圈找到最後一個加入環形連結串列的結點,並將curBoy指向這個節點,即:curBoy.next==first

  • (新增這個輔助變數的目的是儲存first的前趨,便於刪除報數完畢後first指向的結點)

3.報數

  • 每一輪報數時,使用for迴圈m次,每迴圈一次,first和curBoy同時往下一位報數的人的方向移動一次。

  • 當迴圈完畢m次時,儲存first的編號,然後刪除first結點:

    • int n=first.no

    • first=first.next;

    • curBoy.next=first

  • 每一輪報數結束,使用陣列儲存出列的編號,當圈中只剩下一個人時,直接將其編號新增至陣列,最後依次列印陣列的值即為出隊編號的序列。

程式碼實現

結點:

//建立一個Boy類,表示一個節點
class Boy{
    private int no;  //編號
    private Boy next; //指向下一個結點,預設null

    public Boy(int no) {
        this.no = no;
    }

    public int getNo() {
        
return no; } public Boy getNext() { return next; } public void setNext(Boy next) { this.next = next; } }

連結串列:

//建立一個環形的單向連結串列
class CircleLinkedList{
    //建立一個first結點 當前沒有編號
    private Boy first = null;
    //新增小孩結點 構建一個環形的連結串列
    public void addBoy(int nums){
        //nums代表加入小孩的個數
        if(nums<1){
            System.out.println("nums的值不正確,新增失敗");
            return;
        }
        Boy curBoy = null; //輔助變數,幫助建立環形連結串列
        //使用for迴圈來建立環形連結串列
        for(int i=1;i<=nums;i++)
        {
            Boy boy = new Boy(i);
            //如果是第一個小孩
            if(i==1){
                first=boy;
                first.setNext(first); //一個小孩也可以構成一個環狀
                curBoy=first;
            }else {
                curBoy.setNext(boy);
                curBoy=boy;
                boy.setNext(first);
            }
        }

    }
    //遍歷當前的環形連結串列
    public void showBoy(){
        //判斷連結串列是否為空
        if(first==null) {
            System.out.println("連結串列為空,沒有任何小孩!");
            return;
        }
        Boy curBoy = first;
        while (true){
            System.out.printf("小孩的編號:%d\n",curBoy.getNo());
            if(curBoy.getNext()==first){
                //說明已經遍歷完畢
                break;
            }
            curBoy=curBoy.getNext(); //curBoy後移直到其next指向first
        }
    }

    //根據使用者的輸入,計算出小孩的出圈順序
    /*
        startNo 開始的小孩的編號
        countNo 表示數幾下
        nums    表示最初的小孩的個數
     */
    public int[] countBoy(int startNo,int countNo,int nums){
        Boy boyFirst = first;
        for (int i=1;i<startNo;i++)
            boyFirst=boyFirst.getNext();  //遍歷到開始報數的小孩
        Boy pre=boyFirst; //記錄每一輪開始報數的前一個小孩,也就是每一圈的最後一個小孩
        for (int i=1;i<nums;i++)
            pre = pre.getNext();

        int[] boys = new int[nums]; //記錄出圈的編號序列
        for (int j=0;j<nums;j++) {
            for (int i = 0; i < countNo - 1; i++) {                               //下一圈的開始和最後一起後移,一直移到出圈的小孩
                pre = pre.getNext();
                boyFirst = boyFirst.getNext();
            }

            boys[j] = boyFirst.getNo();//記錄出圈的小孩編號

            boyFirst=boyFirst.getNext();//刪除出圈小孩的結點
            pre.setNext(boyFirst);
        }
        return boys;

    }
}