1. 程式人生 > 其它 >單向迴圈連結串列與約瑟夫問題求解

單向迴圈連結串列與約瑟夫問題求解

技術標籤:資料結構與演算法單鏈表java資料結構

單向環形連結串列

將單鏈表中中斷結點的指標端有空指標改為指向頭結點,就使整個單鏈表形成一個環,這種頭尾詳解的單鏈表稱為單迴圈連結串列,簡稱迴圈連結串列;

示意圖:
在這裡插入圖片描述
在這裡插入圖片描述
注意:
  ①迴圈連結串列中沒有NULL指標。涉及遍歷操作時,其終止條件就不再是像非迴圈連結串列那樣判別p或p->next是否為空,而是判別它們是否等於某一指定指標,如頭指標或尾指標等。
  ②在單鏈表中,從一已知結點出發,只能訪問到該結點及其後續結點,無法找到該結點之前的其它結點。而在單迴圈連結串列中,從任一結點出發都可訪問到表中所有結點,這一優點使某些運算在單迴圈連結串列上易於實現。

Josephu 問題

Josephu 問題為:設編號為1,2,… n的n個人圍坐一圈,約定編號為k(1<=k<=n)的人從1開始報數,數到m 的那個人出列,它的下一位又從1開始報數,數到m的那個人又出列,依次類推,直到所有人出列為止,由此產生一個出隊編號的序列。

提示:
用一個不帶頭結點的迴圈連結串列來處理Josephu 問題:先構成一個有n個結點的單迴圈連結串列,然後由k結點起從1開始計數,計到m時,對應結點從連結串列中刪除,然後再從被刪除結點的下一個結點又從1開始計數,直到最後一個結點從連結串列中刪除演算法結束。

程式碼實現:

定義連結串列節點的結構如下:

// 建立一個Boy類,表示一個節點
class Boy { private int no;// 編號 private Boy next; // 指向下一個節點,預設null public Boy(int no) { this.no = no; } public int getNo() { return no; } public void setNo(int no) { this.no = no; } public Boy getNext() { return next; } public void setNext(Boy next) { this.next = next; } }

構建成一個環形的連結串列
輸入nums,構成一個nums個節點的環形連結串列

 // 新增小孩節點,構建成一個環形的連結串列
    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; // 讓curBoy指向第一個小孩
            } else {
                curBoy.setNext(boy);//
                boy.setNext(first);//
                curBoy = boy;
            }
        }
    }

根據使用者的輸入,計算出小孩出圈的順序

 /**
     *
     * @param startNo
     *            表示從第幾個小孩開始數數
     * @param countNum
     *            表示數幾下
     * @param nums
     *            表示最初有多少小孩在圈中
     */
    public void countBoy(int startNo, int countNum, int nums) {
        // 先對資料進行校驗
        if (first == null || startNo < 1 || startNo > nums) {
            System.out.println("引數輸入有誤, 請重新輸入");
            return;
        }
        // 建立要給輔助指標,幫助完成小孩出圈
        Boy helper = first;
        // 需求建立一個輔助指標(變數) helper , 事先應該指向環形連結串列的最後這個節點
        while (true) {
            if (helper.getNext() == first) { // 說明helper指向最後小孩節點
                break;
            }
            helper = helper.getNext();
        }
        //小孩報數前,先讓 first 和  helper 移動 k - 1次
        for(int j = 0; j < startNo - 1; j++) {
            first = first.getNext();
            helper = helper.getNext();
        }
        //當小孩報數時,讓first 和 helper 指標同時 的移動  m  - 1 次, 然後出圈
        //這裡是一個迴圈操作,知道圈中只有一個節點
        while(true) {
            if(helper == first) { //說明圈中只有一個節點
                break;
            }
            //讓 first 和 helper 指標同時 的移動 countNum - 1
            for(int j = 0; j < countNum - 1; j++) {
                first = first.getNext();
                helper = helper.getNext();
            }
            //這時first指向的節點,就是要出圈的小孩節點
            System.out.printf("小孩%d出圈\n", first.getNo());
            //這時將first指向的小孩節點出圈
            first = first.getNext();
            helper.setNext(first); //

        }
        System.out.printf("最後留在圈中的小孩編號%d \n", first.getNo());

    }

完整程式碼

public class Josephu {

    public static void main(String[] args) {
        // 測試一把看看構建環形連結串列,和遍歷是否ok
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        //circleSingleLinkedList.addBoys(5);// 加入5個小孩節點
        Boy b1 = new Boy(1);
        Boy b2 = new Boy(2);
        Boy b3 = new Boy(3);
        Boy b4 = new Boy(4);
        Boy b5 = new Boy(5);
        circleSingleLinkedList.addBoy(b2);
        circleSingleLinkedList.addBoy(b1);
        circleSingleLinkedList.addBoy(b5);
        circleSingleLinkedList.addBoy(b4);
        circleSingleLinkedList.addBoy(b3);
        circleSingleLinkedList.showBoy();
        circleSingleLinkedList.delete(1);
        System.out.println("刪除節點1");
        circleSingleLinkedList.showBoy();
        circleSingleLinkedList.delete(3);
        System.out.println("刪除節點3");
        circleSingleLinkedList.showBoy();
        circleSingleLinkedList.delete(5);
        System.out.println("刪除節點5");
        circleSingleLinkedList.showBoy();
        //測試一把小孩出圈是否正確
       // circleSingleLinkedList.countBoy(1, 2, 5); // 2->4->1->5->3
        //String str = "7*2*2-5+1-5+3-3";
    }
}


// 建立一個環形的單向連結串列
class CircleSingleLinkedList {
    // 建立一個first節點,當前沒有編號
    private Boy first = null;

    // 新增小孩節點,構建成一個環形的連結串列
    public void addBoys(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; // 讓curBoy指向第一個小孩
            } else {
                curBoy.setNext(boy);//
                boy.setNext(first);//
                curBoy = boy;
            }
        }
    }

    //新增單個節點 按遞增序插入
    public void addBoy( Boy boy){

        if(first ==null){//如果第一個節點為空,即連結串列為空
            first = boy;
            first.setNext(first);
            return;
        }
        else{
            boolean flag = false; // flag標誌新增的編號是否存在,預設為false
            Boy curBoy = first; // 輔助指標,
            while (true){

                if (first.getNo() > boy.getNo()){//插入到左端,即插入到first的前面

                    boy.setNext(curBoy);
                    curBoy.setNext(boy);
                    first = boy;
                    break;//插入完成直接返回
                }
                if (curBoy !=first && curBoy.getNext() == first){//說明連結串列已經遍歷完,則新節點插入到原理連結串列的最後
                    curBoy.setNext(boy);
                    boy.setNext(first);
                    break;
                }
                if(curBoy.getNext().getNo() > boy.getNo()){//找到待插入的位置
                    boy.setNext(curBoy.getNext());//插入方式與普通單鏈表的方式相同
                    curBoy.setNext(boy);
                    break;
                }
                if (curBoy.getNext().getNo() == boy.getNo()){
                    System.out.println("待插入結點已經存在,不能重複插入");
                    break;
                }
                curBoy = curBoy.getNext();//後移
            }
        }
    }

    //移除環形連結串列中的某個節點
    public void delete(int no){
        Boy curBoy = first;
        if (first.getNo() == no){//刪除的是第一個節點
            //如果連結串列中只有一個節點
            if(first.getNext() ==first){
                first =null;//將first置為空即可

            }
            else {
                //找到連結串列的最後一個節點
                curBoy = curBoy.getNext();
                while (curBoy.getNext() != first){
                    curBoy = curBoy.getNext();
                }
                curBoy.setNext(first.getNext());//將最後一個節點指向first的下一個節點
                first = first.getNext();//將first後移一位
            }
            return;
        }
        //遍歷連結串列,找到待刪除的點
        boolean flag = false;//判斷是否找到了待刪除節點
        while (true){
            if (curBoy.getNext().getNo() == no){//找到
                //判斷待刪除節點是不是連結串列的最後一個節點
                if (curBoy.getNext().getNext() ==first){//是
                    curBoy.setNext(first);
                }else {//否
                    curBoy.setNext(curBoy.getNext().getNext());
                }
                flag=true;
                break;
            }
            if (curBoy.getNext()==first){//遍歷完
                break;
            }
            curBoy = curBoy.getNext();
        }
        if (flag ==false){
            System.out.printf("未找到待刪除節點  %d",no);
        }

    }

    // 遍歷當前的環形連結串列
    public void showBoy() {
        // 判斷連結串列是否為空
        if (first == null) {
            System.out.println("沒有任何小孩~~");
            return;
        }
        // 因為first不能動,因此我們仍然使用一個輔助指標完成遍歷
        Boy curBoy = first;
        while (true) {
            System.out.printf("小孩的編號 %d \n", curBoy.getNo());
            if (curBoy.getNext() == first) {// 說明已經遍歷完畢
                break;
            }
            curBoy = curBoy.getNext(); // curBoy後移
        }
    }

    // 根據使用者的輸入,計算出小孩出圈的順序
    /**
     *
     * @param startNo
     *            表示從第幾個小孩開始數數
     * @param countNum
     *            表示數幾下
     * @param nums
     *            表示最初有多少小孩在圈中
     */
    public void countBoy(int startNo, int countNum, int nums) {
        // 先對資料進行校驗
        if (first == null || startNo < 1 || startNo > nums) {
            System.out.println("引數輸入有誤, 請重新輸入");
            return;
        }
        // 建立要給輔助指標,幫助完成小孩出圈
        Boy helper = first;
        // 需求建立一個輔助指標(變數) helper , 事先應該指向環形連結串列的最後這個節點
        while (true) {
            if (helper.getNext() == first) { // 說明helper指向最後小孩節點
                break;
            }
            helper = helper.getNext();
        }
        //小孩報數前,先讓 first 和  helper 移動 k - 1次
        for(int j = 0; j < startNo - 1; j++) {
            first = first.getNext();
            helper = helper.getNext();
        }
        //當小孩報數時,讓first 和 helper 指標同時 的移動  m  - 1 次, 然後出圈
        //這裡是一個迴圈操作,知道圈中只有一個節點
        while(true) {
            if(helper == first) { //說明圈中只有一個節點
                break;
            }
            //讓 first 和 helper 指標同時 的移動 countNum - 1
            for(int j = 0; j < countNum - 1; j++) {
                first = first.getNext();
                helper = helper.getNext();
            }
            //這時first指向的節點,就是要出圈的小孩節點
            System.out.printf("小孩%d出圈\n", first.getNo());
            //這時將first指向的小孩節點出圈
            first = first.getNext();
            helper.setNext(first); //

        }
        System.out.printf("最後留在圈中的小孩編號%d \n", first.getNo());

    }
}

// 建立一個Boy類,表示一個節點
class Boy {
    private int no;// 編號
    private Boy next; // 指向下一個節點,預設null
    public Boy(int no) { this.no = no; }
    public int getNo() { return no; }
    public void setNo(int no) { this.no = no; }
    public Boy getNext() { return next; }
    public void setNext(Boy next) { this.next = next; }
}