约瑟夫环问题
6.4 Josephu约瑟夫问题
josephu问题为:设编号为1、2、... n 的 n 个人围坐一圈,约定编号为 k (1 <= k <= n)的人从 1 开始报数,数到 m 的那个人出列,他的下一位又从 1 开始报数,数到 m 的那个人又出列,依此类推,直到所有人出列位置,由此产生一个出队编号的序列。
假设 n = 5; k = 1; m = 2; 那么出来的队列顺序就是 2 - 4 - 1 - 5 - 3(使用单项环形链表完成,也可以使用数组取模完成)
构建一个单向环形链表
-
先创建第一个节点,让 first 指向该节点, 并形成环形
-
后面当我们每创建一个新的节点,就把该节点,加入到已有的环形链表中即可
遍历环形链表
-
先让一个辅助指针(变量),指向 first 节点
-
然后通过一个 while循环遍历,该环形链表即可,即
curBoy.next = first
根据用户的输入,生成一个小孩出圈的顺序
-
需要创建一个辅助指针
helper,事先指向环形链表的最后一个节点 -
当小孩报数时,让first和helper同时移动 m - 1 次
-
这时就可以将
first执行那个的小孩节点出圈first = first.next;helper.next = first;原来
first指向的节点就没有任何引用,就会被垃圾回收机机制回收
1 package LinckedList; 2 3 public class Josepfu { 4 public static void main(String[] args) { 5 // 测试,构建环形链表和遍历是否成功 6 CircleSingleLinkedList c1 = new CircleSingleLinkedList(); 7 c1.addBoy(5); // 加入5个小孩的节点 8 c1.showBoy(); 9 10 // 测试出圈 11 c1.countBoy(1,2,5); 12 } 13 } 14 // 创建一个环形的单向链表 15 class CircleSingleLinkedList{ 16 // 创建一个first节点,当前没有编号 17 private Boy first = new Boy(-1); 18 // 添加小孩的节点,构建一个环形的链表 19 public void addBoy(int num){ 20 // 对num做一个数据校验 21 if (num < 1){ 22 System.out.println("nums的值不正确"); 23 return; 24 } 25 Boy curBoy = null; // 辅助指针,帮助构建环形链表 26 // 使用一个for循环创建我们的环形链表 27 for (int i = 1; i <= num; i++) { 28 // 根据编号创建小孩节点 29 Boy boy = new Boy(i); 30 // 如果是第一个小孩 31 if (i == 1){ 32 first = boy; 33 first.setNext(first); // 构成环 34 curBoy = first; // 让curBoy指向第一个小孩 35 } else { 36 curBoy.setNext(boy); // 先让curBoy指向的节点的下一个指向新节点 37 boy.setNext(first); // 在让新节点的指回第一个节点,构成环 38 curBoy = boy; // 最后令curBoy后移指向boy,等待下一个新的boy节点 39 } 40 } 41 } 42 // 遍历当前环形节点 43 public void showBoy(){ 44 // 判断链表是否为空 45 if (first == null){ 46 System.out.println("NULL"); 47 return; 48 } 49 // 因为first 不能动,因此我们使用辅助指针完成遍历 50 Boy curBoy = first; 51 while (true){ 52 System.out.printf("小孩的而编号为 %d\n",curBoy.getNo()); 53 if (curBoy.getNext() == first){ 54 // 说明遍历完毕 55 break; 56 } 57 curBoy = curBoy.getNext(); // curBoy后移 58 } 59 } 60 61 // 根据用户输入,计算出小孩出圈的顺序 62 63 /** 64 * 65 * @param startNo 表示从第几个小孩开始报数 66 * @param countNum 表示数几下 67 * @param nums 表示最初有多少小孩在圈中 68 */ 69 public void countBoy(int startNo, int countNum, int nums){ 70 // 先对数据进行校验 71 if (first == null || startNo < 1 || startNo > nums){ 72 System.out.println("参数输入有误,请重新输入"); 73 return; 74 } 75 // 创建辅助指针,帮助出圈 76 Boy helper = first; 77 // 需求创建一个辅助指针helper, 事先应该指向环形链表的最后这个结点 78 while (true){ 79 if (helper.getNext() == first){ 80 break; 81 } 82 helper = helper.getNext(); 83 } 84 // 报数前先让first和helper移动 startNo(k) - 1次 85 for (int j = 0; j < startNo - 1; j++){ 86 first = first.getNext(); 87 helper = helper.getNext(); 88 } 89 // 当小孩报数时,让first和helper同时移动 m - 1 次 90 // 这里是一个循环的操作,直到圈中只有一个 91 while (true){ 92 if (helper == first){ 93 break; 94 } 95 // 让first和helper同时移动 countNum(m) - 1 次 96 for (int j = 0; j < countNum - 1; j++){ 97 first = first.getNext(); 98 helper = helper.getNext(); 99 } 100 // 这时first指向的节点,就是要出圈的小孩的节点 101 System.out.printf("小孩%d出圈\n",first.getNo()); 102 // 这时将first出圈 103 first = first.getNext(); 104 helper.setNext(first); 105 } 106 System.out.printf("最后留在圈中的小孩编号时%d\n",first.getNo()); 107 } 108 } 109 // 创建一个Boy类,表示一个节点 110 class Boy { 111 private int no; // 编号 112 private Boy next; 113 114 public Boy(int no){ 115 this.no = no; 116 } 117 118 public int getNo() { 119 return no; 120 } 121 122 public void setNo(int no) { 123 this.no = no; 124 } 125 126 public Boy getNext() { 127 return next; 128 } 129 130 public void setNext(Boy next) { 131 this.next = next; 132 } 133 }
