一、单向环形链表
如图,链表的最后一个节点的next指向链表的第一个节点,形成一个环形链表。
二、单向环形链表使用场景-约瑟夫问题
josephu(约瑟夫、约瑟夫环)问题:
设编号为1,2,3…n的 n个人坐成一圈,编号为k(1<=k<=n )的人从1开始报数,数到m的那个人出列, 他的下一位继续从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
三、约瑟夫问题分析
用一个不带头节点的单向环形链表来实现:
-
首先生成一个 带有 n 个节点的单向环形链表;
无论几个节点,要保证当前节点创建完后,形成一个环,需要引入:cur变量和first变量。当创建第一个节点的时候,first指针和cur指针都指向这个节点,并且cur.next=first,之后每增加一个节点:
cur.next = newNode;
newNode.next = first;
cur = cur.next; -
报数前准备就绪:
由于是单向环形链表,只能通过next指针向后移动,因此,在某个节点出队时,想要要将链表继续连接成环,就需要出队列的那个节点的前一个节点作为辅助。
1)Child first = null;
Child helper = null;
创建 first 变量,指向第一个节点,遍历整个单向环形链表,找到最后一个节点,创建辅助变量,指向最后一个节点(first前面的那个节点)。
2) 由于编号为k的人第一个开始从1报数,在真正开始报数前,需要将first指针和helper指针向前移动 k-1次,让first指向编号为k的人(helper始终保持在first的前一个节点)。
-
循环遍历链表,开始报数并出队列
1)编号为k的人开始从1报数,数到m的人出队列,
first节点和helper节点均向后移动 m-1次,此时first指向要删除的节点。
first = first.next;
helper.next = first;
2)当 helper == first,说明链表中已经剩最后一个节点。
四、代码实现
package linkedlist;
import org.junit.Test;
/**
* 单向环形链表
* @author
* @create 2020-07-17 8:28 PM
*/
public class SingleCircleLinkedList {
@Test
public void testCreateLinkedList(){
int num = 5;
Josephu josephu = new Josephu();
josephu.createCircle(num);
josephu.showLinkedList();
josephu.getOutOrder(5,1,2);
}
}
class Josephu{
private Child first = null;
/**
* 创建一个含有 num 个节点的单向环形链表
* 无论几个节点,要保证当前节点创建完后,形成一个环,需要引入两个变量:cur变量和first变量。当创建第一个节点的时候,first指针和cur指针都指向这个节点,并且cur.next=first,之后每增加一个节点:
* cur.next = newNode;
* newNode.next = first;
* cur = cur.next;
* @param num
*/
public void createCircle(int num){
if(num<1){
System.out.println("num的值不正确");
return ;
}
Child curNode = null;
for(int n = 1;n<=num;n++){
Child node = new Child(n);
if(n==1){
first = node;
curNode = node; //或者 curNode = first;都行
first.setNext(first);
}else{
curNode.setNext(node);
node.setNext(first);
curNode = node;
}
}
}
/**
* 遍历链表的节点
*/
public void showLinkedList(){
if(first == null){
System.out.println("链表为空~~~");
return;
}
Child cur = first;
while(true){
System.out.println(cur);
if(cur.getNext()==first){
break;
}
cur = cur.getNext();
}
}
/**
* 计算小孩出队的顺序
* @param count 一共有几个小孩
* @param startNo 从编号为startNo 的小孩开始从 1 报数
* @param step 数到 step 的这个小孩出队,然后从他的后一个小孩开始,继续游戏。
*/
public void getOutOrder(int count,int startNo, int step){
if(first==null || startNo<1 || startNo>count){
System.out.println("参数输入有误~~~");
return;
}
Child helper = first;
//1.遍历链表,找到链表的最后一个节点,让helper指向它。
while(true){
if(helper.getNext()==first){
break;
}
helper = helper.getNext();
}
//2.开始遍历前,让first 和 helper 节点分别向后移动 startNo-1 次。让first指向第一个开始报数的人。
for(int i = 1;i<=startNo-1;i++){
first = first.getNext();
helper = helper.getNext();
}
//3.开始报数,数到step的人出队。循环整个链表,直到遍历完。
while(true){
if(first.getNext()==first){
break;
}
for(int m = 1;m<step;m++){
first = first.getNext();
helper = helper.getNext();
}
System.out.printf("此次出队的是学号为%d的学生\n",first.getNo());
first = first.getNext();
helper.setNext(first);
}
System.out.printf("最后出队的是学号为%d的学生",first.getNo());
}
}
class Child{
private int no;
private Child next;
public Child(int no) {
this.no = no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Child getNext() {
return next;
}
public void setNext(Child next) {
this.next = next;
}
@Override
public String toString() {
return "Child{" +
"no=" + no +
'}';
}
}