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 + '}'; } }