1. 程式人生 > 實用技巧 >1.6 單向環形連結串列和約瑟夫問題

1.6 單向環形連結串列和約瑟夫問題

  • 基本介紹

    • 單向迴圈連結串列和單向連結串列基本一樣,尾節點的next指向第一個節點
    • 當連結串列中只有一個節點時,該節點的next指向本身
  • 約瑟夫問題

    • 基本介紹
      • 設編號為 1,2,… n 的n個人圍坐一圈,約定編號為 k(1<=k<=n)的人從1開始報數,數到m的那個人出列,它的下一位又從1開始報數,數到m的那個人又出列,依次類推,直到所有人出列為止,由此產生一個出隊編號的序列
      • 例如:編號為1,2,3,4,5 從1開始,數2次,則出隊編號為2,4,1,5,3
    • 實現思路
      • 採用單向迴圈連結串列,將數到那個在環形連結串列中刪除後,重新構建環形連結串列,直到剩下最後一個節點
      • 由於採用的單向連結串列,所以需要兩個指標,current指向當前的節點(也就是要出隊的節點,初始值為head節點),currentBefore指向當前節點的前一個節點(用來刪除當前節點,初始值為head節點的前一個節點)
      • 將兩個節點同時移動k-1次,第一次的報號位置,由於本身也要報號,所以k-1次(兩個指標必須相連,且current在後)
      • 開始報號,將兩個節點同時移動k-1次(原理同上)
      • 出隊current = current.next; currentBefore = current;
      • 當current == currentBefore; 佇列中就只剩下了一個節點(結束)
  • 程式碼實現

  • public class CircleSingleLinkedListDemo {
       public static void main(String[] args) {
           CircleSingleLinkedLis circleSingleLinkedLis = new CircleSingleLinkedLis();
           // 建立並顯示單向環形連結串列
           circleSingleLinkedLis.create(10);
           circleSingleLinkedLis.show();
           // 實現約瑟夫問題
           System.out.println("---約瑟夫問題---");
           circleSingleLinkedLis.josephu(5, 10, circleSingleLinkedLis.size());
       }
    }
    // 單向環形連結串列類
    class CircleSingleLinkedLis {
    
       private Children head; // 頭節點,將加入的第一個節點作為頭節點(不能動,在約瑟夫環中可以動)
    
       // 建立單向環形連結串列(引數為有幾個節點)
       public void create(int number) {
           if (number < 2) {
               System.out.println("至少兩個節點");
               return;
           }
           Children current = null; // 尾指標 指向下一個節點為頭節點的節點(方便插入,不需要每次都遍歷)
           for (int i = 1; i <= number; i++) { // i作為節點的編號
               Children children = new Children(i);
               if (null == head) { // 第一個節點 自己指向自己 單獨處理也是為了防止空指標
                   head = children;
                   children.next = head;
                   current = head;
               } else { // 先將當前節點的下一個節點指向要新增的節點,再後移當前節點,再將當前節點的下一個節點指向頭節點
                   current.next = children;
                   current = current.next;
                   children.next = head;
               }
           }
       }
    
       // 顯示單向環形連結串列
       public void show() {
           if (null == head) {
               System.out.println("單向環形連結串列為空");
           }
           Children temp = head;
           while (null != head) {
               System.out.println(temp);
               temp = temp.next; // 後移節點
               if (temp == head) {
                   break; // 到頭節點了 退出
               }
           }
       }
    
       // 獲取連結串列節點個數
       public int size() {
           int sum = 0;
           Children temp = head;
           while (null != temp ) {
               sum++;
               temp = temp.next;
               if (temp == head) {
                   break;
               }
           }
           return sum;
       }
       
       /**
        * 約瑟夫問題實現(一群小孩圍在一起,由第k個小孩報n下數,停止報數的那個小孩出圈,問小孩出圈的順序)
        * 採用兩個指標(一個指向當前報數的孩子,另一個指向報數孩子的前一個孩子,因為單鏈表刪除必須拿到當前節點的前一個節點)
        * @param start 開始報數的節點
        * @param count 報多少下
        * @param size 總共有多少個小孩(此處不能連結串列的有效個數)
        */
       public void josephu(int start, int count, int size) {
           if (null == head || start < 1 || start > size || size > this.size()) {
               System.out.println("輸入的資料不合法");
               return;
           }
           Children current = head; // 當前節點
           Children currentBefore = current; // 當前節點的前一個節點
           while (currentBefore.next != head) { // 尋找當前節點的前一個節點
               currentBefore = currentBefore.next; // 指標後移
           }
           for (int i = 0; i < start - 1; i++) { // 將兩個指標移動到起始位置 例 1 -> 3 需移動2次 則是 start - 1
               current = current.next;
               currentBefore = currentBefore.next;
           }
           while (current != currentBefore) { // 如果圈中只剩一個小孩 則兩個指標重合
               // 從起始位置報數 由於起始節點本身也要報數 則是移動 count - 1
               for (int i = 0; i < count - 1; i++) {
                   current = current.next;
                   currentBefore = currentBefore.next;
               }
               // 報數完畢 移除當前節點 並重新構建連結串列
               System.out.println(current);
               current = current.next; // 後移當前指標
               currentBefore.next = current; // 重新構建連結串列
           }
           System.out.println(current); // 輸出圈中最後一個節點的資訊
       }
    
    }
    // 連結串列節點類
    class Children {
       public int no;
       public Children next; // 指向下一個節點
    
       public Children(int no) {
           this.no = no;
       }
    
       @Override
       public String toString() {
           return "Children{" +
                   "no=" + no +
                   '}';
       }
    }