单向环形链表:约瑟夫环问题
问题描述:
设编号为 1,2,… n 的 n 个人围坐一圈,约定编号为 k(1<=k<=n)的人从 1 开始报数,数到 m 的那个人出列,它的下一位又从 1 开始报数,数到 m 的那个人又出列,依次类推,直到所有人出列为止,由 此产生一个出队编号的序列。
解决方案:
用一个不带头结点的循环链表来处理 Joseph 问题:先构成一个有 n 个结点的单循环链表,然后由 k 结点起从 1 开始计数,计到 m 时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从 1 开始计数,直到最后一个结点从链表中删除算法结束。
解决思路:
1、构建一个单向环形链表
1. 先创建第一个节点, 让 first 指向该节点,并形成环形
2. 后面当我们每创建一个新的节点,就把该节点,加入到已有的环形链表中即可.
2、遍历环形链表
- 先让一个辅助指针(变量) curBoy,指向first节点
- 然后通过一个while循环遍历该环形链表即可 , curBoy.next == first 时结束
3、约瑟夫环出圈的实现☆
- 需要创建一个辅助指针(变量) helper , 事先应该指向环形链表的最后这个节点.
- 小孩报数前,先让 first 和 helper 移动 k - 1次
- 当小孩报数时,让first 和 helper 指针同时 的移动 m - 1 次
- 这时就可以将first 指向的小孩节点出圈:
- first = first .next
- helper.next = first
- 原来first 指向的节点就没有任何引用,就会被回收
4、举例:
根据用户的输入,生成一个结点出圈的顺序
n = 5 , 即有5个结点
k = 1, 从第一个结点开始数
m = 2, 数2下
出圈的顺序
2->4->1->5->3
代码实现:
1. // 创建一个环形的单向链表 2. class circlelinkedlist { 3. boy first = null;// 创建一个 first 节点,当前没有编号 4. 5. //添加节点,构建成一个环形的链表 6. public void addByNums(int nums) { 7. // nums 做一个数据校验 8. if (nums < 0) { 9. System.out.println("addByNum():传入参数有误。。。"); 10. return; 11. } 12. boy temp = null; // 辅助指针,帮助构建环形链表 13. for (int i = 1; i <= nums; i++) { 14. boy boy = new boy(i);// 根据编号,创建小孩节点 15. if (i == 1) {// 如果是第一个小孩 16. first = boy; 17. first.setNext(first);//构成环 18. temp = first; 19. } else { 20. temp.setNext(boy); 21. boy.setNext(first);//构成环 22. temp = boy; 23. } 24. 25. } 26. } 27. 28. //遍历当前的环形链表 29. public void print() { 30. if (first == null) { 31. System.out.println("print():循环链表为空。。。。"); 32. return; 33. } 34. boy temp = first;// 因为 first 不能动,因此我们仍然使用一个辅助指针完成遍历 35. while (true) { 36. int id = temp.getId(); 37. System.out.println(id); 38. temp = temp.getNext(); 39. if (temp == first) { // 说明已经遍历完毕 40. break; 41. } 42. } 43. } 44. 45. //josephu: 根据用户的输入,计算出小孩出圈的顺序 46. /** 47. * @param startNum 开始的编号(从第几个开始数) 48. * @param countNum 数几下 49. * @param nums 一共多少数(环形链表中一共有几个节点) 50. */ 51. public void josephu(int startNum, int countNum, int nums) { 52. // 先对数据进行校验 53. if (first == null) { 54. System.out.println("Josephu():链表为空。。。"); 55. return; 56. } 57. if (startNum <= 0 || startNum > nums || countNum == 0) { 58. System.out.println("输入参数有误"); 59. } 60. // 创建要给辅助指针,帮助完成出圈 61. boy temp = first;// 创建一个辅助指针(变量) temp 62. // 事先应该将其指向环形链表的最后这个节点 63. while (true) { 64. if (temp.getNext() == first) {// 说明 temp 指向最后一个节点 65. break; 66. } 67. temp = temp.getNext(); 68. } 69. 70. //将first指向开始数的编号(初始在第一个节点),temp也相应移动(移动startNum-1次) 71. for (int i = 0; i < startNum - 1; i++) { 72. first = first.getNext(); 73. temp = temp.getNext(); 74. } 75. //当小孩报数时,让 first和temp指针同时的移动 count- 1 次, 然后出圈 76. //这里是一个循环操作,直到圈中只有一个节点 77. while (true) { 78. if (temp == first) {//说明圈中只有一个节点 79. break; 80. } 81. //让 first 和 helper 指针同时 的移动( countNum - 1)次 82. for (int i = 0; i < countNum - 1; i++) { 83. first = first.getNext(); 84. temp = temp.getNext(); 85. } 86. //这时 first 指向的节点,就是要出圈的节点 87. System.out.println(first.getId()); 88. first = first.getNext();//将 first 指向出圈节点的下一个节点 89. temp.setNext(first);//将temp的next指向现在的first,则出圈的节点无引用,被GC 90. } 91. System.out.println(temp.getId());//最后一个节点输出 92. } 93. }