来源:https://www.bilibili.com/video/BV1B4411H76f?p=28
一、约瑟夫问题
n个人围成一圈,从编号为k(1 ≤ k ≤ n)的那个人开始数,数到m的那个人出列。想要按照出列的顺序把这些人依次指出。
这样的约瑟夫问题可以通过一个没有头结点的单向环形链表处理。结点数目为n,从第k个结点开始计数,按照链表顺序一直计数到m,将m对应的这个人删除。
举一个n=5 k=1 m=2的例子
二、思路
2.1 创建单向环形链表的思路
1、创建第一个结点,让first指针指向该节点,同时该节点与自身形成环
2、后面需要知道当前创建的环形链表的大小,即上述约瑟夫问题中的n。将n个新结点加入到已有的环形链表中。
2.2 遍历单向环形链表的思路
1、辅助指针指向first结点
2、利用while循环,直到【当前结点.next】=【first】
2.3 约瑟夫问题的解决思路
1、为了避免整个环形链表断掉,必须在要出去的结点前面加一个辅助结点helper,当前结点first出去之后,
【first】=【first.next】
【helper.next】=【first】
2、因为整个问题开始的时候,first指向的是第一个创建的结点的位置,所以可以把helper之前它的前面,也就是环形的最后一个结点。当知道从编号为k的那个结点开始之后,两个指针同时后移相应的位置,令first指向开始的结点,helper指向前一个。
3、开始报数之后,也是first、helper指针同时移动,直到走到要删除的结点。
三、实现
3.1 创建单向环形链表
1、新建一个对应于约瑟夫问题的结点类,编号属性代表的是第几个人,next属性指向下一个人。
1 public class Node3 { 2 private int no; 3 private Node3 next; 4 5 public Node3(int no) { 6 this.no = no; 7 } 8 9 public int getNo() { 10 return no; 11 } 12 13 public void setNo(int no) { 14 this.no = no; 15 } 16 17 public Node3 getNext() { 18 return next; 19 } 20 21 public void setNext(Node3 next) { 22 this.next = next; 23 } 24 }
2、创建一个单向环形链表类CircleSingleLinkedList,类中有一个first结点,代表要创建的链表的第一个结点。在类中加入了利用输入的结点个数构建环形链表的方法,以及通过遍历的方式展示整个环形链表的方法。
1 public class CircleSingleLinkedList { 2 //创建第一个结点 3 private Node3 first = new Node3(-1); 4 5 //构建环形链表 6 //为了简化,结点中只有编号这个属性,针对约瑟夫问题,编号顺序递增排列,但是需要知道有多少个人nums围成一圈 7 public void addNode(int nums){ 8 if(nums < 1){ 9 System.out.println("参数nums错误"); 10 return; 11 } 12 Node3 curNode = null;//辅助指针,指向当前结点 13 for (int i = 1; i <= nums; i++) { 14 Node3 node3 = new Node3(i); 15 16 if(i == 1){ 17 //第一个结点,需要利用first自己形成环形 18 //之后first就不再动了,由curNode辅助指针完成之后的操作 19 first = node3; 20 first.setNext(first); 21 curNode = first; 22 }else { 23 curNode.setNext(node3); 24 node3.setNext(first); 25 curNode = node3; 26 } 27 } 28 } 29 30 //遍历,显示环形链表 31 public void show(){ 32 if(first.getNo() == -1){ 33 System.out.println("链表为空"); 34 return; 35 } 36 Node3 curNode = first; 37 while (true){ 38 System.out.printf("当前结点的编号为%d",curNode.getNo()); 39 System.out.println(); 40 if(curNode.getNext() == first){ 41 System.out.println("遍历完毕"); 42 break; 43 } 44 curNode = curNode.getNext(); 45 } 46 } 47 }
测试,这里加入了5个结点(5个人围成一圈),构成环形链表
1 public static void main(String[] args) { 2 CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList(); 3 4 circleSingleLinkedList.show(); 5 System.out.println(); 6 7 circleSingleLinkedList.addNode(5); 8 9 circleSingleLinkedList.show(); 10 11 }
结果
链表为空
当前结点的编号为1
当前结点的编号为2
当前结点的编号为3
当前结点的编号为4
当前结点的编号为5
遍历完毕
3.2 约瑟夫问题
在CircleSingleLinkedList类中加入一个统计出圈顺序的方法
1 //约瑟夫问题, 2 // 根据用户输入的 3 // **围成一圈的人的数目nums, 4 // **从编号startNo开始, 5 // **数到m的那个人出圈 6 // 排列出圈的顺序 7 public void countNode(int nums, int startNo, int m){ 8 if(first.getNo() == -1 || startNo < 1 || startNo > nums){ 9 System.out.println("参数错误"); 10 return; 11 } 12 13 Node3 helper = first;//辅助指针 14 //令辅助指针指向first的前一个位置 15 while (true){ 16 if(helper.getNext() == first){ 17 break; 18 } 19 helper = helper.getNext(); 20 } 21 22 //将first和helper定位到开始的位置startNo 23 for (int i = 0; i < startNo - 1; i++) { 24 first = first.getNext(); 25 helper = helper.getNext(); 26 } 27 28 //开始报数 29 while (true){ 30 //循环结束的标志是:只剩最后一个人了,first和helper指针都在他上面 31 if(first == helper){ 32 break; 33 } 34 //数到m 35 for (int i = 0; i < m - 1; i++) { 36 first = first.getNext(); 37 helper = helper.getNext(); 38 } 39 //first指向的第m个人要出圈 40 System.out.printf("%d出圈",first.getNo()); 41 System.out.println(); 42 first = first.getNext();//出圈时,first要重新定位 43 helper.setNext(first);//helper的下一个也要重新指定 44 } 45 //输出一下最后剩的那个结点 46 System.out.printf("最后一个是%d",first.getNo()); 47 }
测试,在上面测试的基础上进行(已经插入了5个结点)
1 circleSingleLinkedList.countNode(5,1,2);
结果,与上面图中的顺序完全一致
2出圈
4出圈
1出圈
5出圈
最后一个是3