一. 概念介紹:
什麼是約瑟夫問題?
josephu(約瑟夫) 問題爲:設編號爲1,2,3....n的n個人圍坐一圈,約定編號爲k(1<=k<=n)的人從1開始報數,數到m的那個人出列,他的下一位又從1開始報數,數到m的那個人又出列,以此類推,直到所有人出列爲止,由此產生一個出隊編號的序列。
約瑟夫問題又稱丟手帕問題:假設一羣(5個)小孩圍坐一圈,從第1個小孩開始報數,每次數2下,數到2的小孩退出遊戲,他的下一位又從1開始報數,以此類推,直到所有小孩退出遊戲爲止,這樣就會產生一個小孩退出遊戲的序列。
下面用畫圖來說明約瑟夫問題:
五個小孩圍圈坐在一起
從小孩1開始報數,小孩1報數 “1”,小孩2報數 “2” ,因爲報數到2,所以根據遊戲規則,小孩2退出遊戲。
此時退出序列:小孩2 --->
小孩3報數 “1” ,小孩4報數 “2” , 因爲報數到2,所以根據遊戲規則,小孩4退出遊戲。
此時退出序列:小孩2 ---> 小孩4 --->
小孩5報數 “1” ,小孩1報數 “2” , 因爲報數到2,所以根據遊戲規則,小孩1退出遊戲。
此時退出序列:小孩2 ---> 小孩4 ---> 小孩1 --->
小孩3報數 “1” ,小孩5報數 “2” , 因爲報數到2,所以根據遊戲規則,小孩5退出遊戲。
此時退出序列:小孩2 ---> 小孩4 ---> 小孩1 ---> 小孩5 --->
最後小孩3自己跟自己報數,小孩3報數 “1” ,小孩3報數 “2” , 因爲報數到2,所以根據遊戲規則,小孩3退出遊戲。
此時退出序列:小孩2 ---> 小孩4 ---> 小孩1 ---> 小孩5 ---> 小孩3
二. 構建一個單向的環形鏈表
單向環形鏈表構建思路如下:
當只有一個節點的時候,節點1的next指向自己,形成自己環形鏈表。
當新添加一個節點進來時,將節點1的next指向節點2 ,節點2的next指向節點1,形成環形鏈表。
當再添加一個節點過來時,將節點2的next指向節點3 ,節點3的next指向節點1,形成環形鏈表。
當第4個,第5個.....第n個節點添加進來時,重複上面的過程。
以下用代碼構建一個單向環形鏈表:
class Boy{
private int no;
private Boy next;
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;
}
}
//創建一個環形的單向鏈表
class CircleSingleLinkedList{
private Boy first = null;
//添加小孩節點,構建一個環形的鏈表
public void addBoy(int 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);
boy.setNext(first);
curBoy = boy;
}
}
}
//遍歷當前的環形鏈表
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();
}
}
}
測試:
public class Josephu {
public static void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.addBoy(5);//加入5個小孩節點
circleSingleLinkedList.showBoy();
}
}
結果:
在類 CircleSingleLinkedList添加方法countBoy,實現小孩丟手帕:
/**
*
* @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.getNext();
helper.setNext(first);
}
System.out.printf("最後留在圈中的小孩編號%d \n", first.getNo());
}
測試:
public static void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.addBoy(5);//加入5個小孩節點
circleSingleLinkedList.showBoy();
System.out.println("測試一把......");
circleSingleLinkedList.countBoy(1, 2, 5); // 2->4->1->5->3
}
結果: