环形单向链表之约瑟夫问题详解
说明:
- 约瑟夫环问题可以使用单向环形链表形象的模拟解决
- 需要构建一个单向环形链表
- 然后用打印并删除节点的方式模拟小孩出圈
- 具体逻辑思路见代码注释
源码及分析
节点类
//创建一个小孩节点
class Boy {
//小孩编号
private int no;
//指向下一个节点的指针
private Boy next;
//构造器
public Boy(int no) {
this.no = no;
}
//getter 和 setter
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;
//编写方法创建一个指定大小的单向环形链表
/**
* @param nums 指定要创建的环形链表的大小
*/
//思路分析
//1. 先创建大小为1个节点的环形链表,即让自己指向自己
//2. 然后循环向这个链表中添加节点
//3. 每添加一个节点,都让这个链表成环状
//4. 再添加完所有的节点后,这个环形链表仍然是成立的
public void addBoy(int nums) {
//先进行数据校验,判断输入的实参是否合格
if (nums < 1) {
System.out.println("数据不合格~~");
return;
}
//因为第一个节点first不能动,所以需要一个辅助指针cur,不断的移动添加
Boy cur = null;
//循环向环形链表中添加节点
for (int i = 1; i <= nums; i++) {
//创建节点
Boy boy = new Boy(i);
//考虑第一个节点,让第一个节点自己成环,即自己指向自己
if (i == 1) {
//第一个节点自己指向自己
first = boy;
first.setNext(first);
//辅助指针指向第一个节点
cur = first;
//如果是其他的节点,依次加入到环形链表中
} else {
cur.setNext(boy);
boy.setNext(first);
//指针后移
cur = cur.getNext();
}
}
}
//编写方法显示环形链表中的节点
public void showBoy() {
//先判断环形链表是否为空
if (first == null) {
System.out.println("链表是空的~~");
return;
}
//否则遍历环形链表
//因为第一个节点不能动,因此需要定义辅助指针cur
Boy cur = first;
//遍历结束的条件为:当且仅当cur.next = first
while (true) {
//先打印节点的信息
System.out.println("小孩的编号:" + cur.getNo());
//判断是否遍历结束
if (cur.getNext() == first) {
break;
}
//指针后移
cur = cur.getNext();
}
}
//编写方法实现小孩出圈
//思路分析:
//1. 因为是单向链表,所以删除节点时需要找到该节点的前一个节点
//2. 但是要打印要出圈的节点的编号,需要找到这个节点本身,因此需要定义两个节点
//3. 一个节点指向要删除节点的前一个节点,一个节点表示要删除的节点本身
//4. first表示每次开始数数的哪个节点
//5. 定义cur指针之前first指针的前一个节点
//6. 先让first指向环形链表的第一个节点,cur指向最后一个节点,即first节点的前一个节点
//7. first节点和cur节点向后移动startNo - 1次,则找到要开始数数的这个节点
//8. 再向后移动countNum - 1次,则找到要出圈的节点,即要删除的节点
/**
* @param startNo 从哪个小孩开始数数
* @param countNum 每次数几个小孩
* @param nums 环形链表的大小
*/
public void countBoy(int startNo, int countNum, int nums) {
//先进行数据校验
if (first == null || startNo < 1 || countNum < 1 || nums < 1) {
System.out.println("数据错误~~");
return;
}
//定义辅助指针cur指向第一个节点的前一个节点
Boy cur = first;
while (true) {
if (cur.getNext() == first) {
break;
}
cur = cur.getNext();
}
//循环结束后cur指向first节点的前一个节点
//根据输入的开始的小孩编号移动first和cur,使得first指向要开始的节点,cur指向前一个节点
for (int i = 0; i < startNo - 1; i++) {
first = first.getNext();
cur = cur.getNext();
}
//循环删除数到的每个节点,直到只剩下最后一个节点
while (true){
//如果只剩下一个节点,循环结束
if (cur == first){
break;
}
//将first和cur的指针向后移动countNum - 1次找到要删除的节点和其前一个节点
for (int i = 0; i < countNum - 1; i++) {
first = first.getNext();
cur = cur.getNext();
}
System.out.println("小孩 " + first.getNo() + " 出圈~~");
//first指向他的下一个节点
first = first.getNext();
//继续设置cur为first的前一个指针
cur.setNext(first);
}
//循环结束后则只剩下一个指针
System.out.println("最后一个小孩 " + cur.getNo() + " 出圈");
}
}
测试类
public static void main(String[] args) {
//环形链表测试
//创建环形链表
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.addBoy(100);
// circleSingleLinkedList.showBoy();
circleSingleLinkedList.countBoy(6,10,100);
}