单项环形链表
思路
长度为1时
(1)新创建的boy节点是首节点。
firts=boy。
(2)自己构成环形链表。
first.next = frist。
(3)first是首节点,同时又是尾节点,辅助指针(变量)指向fisrt。
curBoy=first。
长度不为1时
(1)讲boy节点添加至环形链表最后。
curBoy.next = boy;//curBoy节点永远是环形链表的尾结点
(2)构成环形链表。
boy.next = first。
// 创建一个Boy类,表示一个节点(Boy 节点就是个普普通通的单向链表节点)
class Boy {
private int no;// 编号
private Boy next; // 指向下一个节点,默认null
(getter setter省略)
}
// 创建一个环形的单向链表
class CircleSingleLinkedList {
// 创建一个first节点,当前没有编号
private Boy first = null;// 单向循环链表的首节点,是真实存放数据的节点,不是头结点
// 添加小孩节点,构建成一个环形的链表
public void addBoy(int nums) {
// nums 做一个数据校验
if (nums < 1) {
System.out.println("nums的值不正确");
return;
}
Boy curBoy = null; // 辅助指针,帮助构建环形链表
// 使用for来创建我们的环形链表
for (int i = 1; i <= nums; i++) {
// 根据编号,创建小孩节点
Boy boy = new Boy(i);
// 如果是第一个小孩
if (i == 1) {
first = boy; // 初始化 first 节点
first.setNext(first); // 构成环
curBoy = first; // 让curBoy指向第一个小孩
} else {
curBoy.setNext(boy); // 将 boy 节点加到链表尾部
boy.setNext(first); // 构成环
curBoy = boy; // curBoy 指针后移
}
}
}
// 遍历当前的环形链表
public void showBoy() {
// 判断链表是否为空
if (first == null) {
System.out.println("没有任何小孩~~");
return;
}
// 因为first不能动,因此我们仍然使用一个辅助指针完成遍历
Boy curBoy = first;
while (true) {
System.out.printf("小孩的编号 %d \n", curBoy.getNo());
if (curBoy.getNext() == first) {// 当 curBoy 已经指向环形链表的尾节点,说明已经遍历完毕
break;
}
curBoy = curBoy.getNext(); // curBoy后移
}
}
}
解决Josephu(约瑟夫)问题
Josephu(约瑟夫, 约瑟夫环)是一个数学应用问题
n个人按编号1, 2, …n围坐一圈, 从编号为 k的人开始报数, 数到 m的那个人出列, 此时它的下一个人又从1开始报数, 数到 m的那个人再出列, 依次反复, 直到所有的人全部出列为止, 最后输出出列人的编号顺序。
解决思路
首先创建有 n个节点的不带头的单向环形链表, 然后从 k节点开始计数, 计到 m时, 删除对应当前节点, 然后再从被删节点的下一个节点开始重新计数, 依次反复, 直到最后一个节点从链表中删除算法结束。
代码示例
/** 定义人(表示节点)*/
class Person {
/** 人编号*/
private int no;
/** 指向下一个节点*/
private Person next;
public Person(int no) {
this.no = no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Person getNext() {
return next;
}
public void setNext(Person next) {
this.next = next;
}
}
/** 定义不带头的单向环形链表*/
class CircularSingleLinkedList {
/** 定义起始节点*/
private Person first;
/** 构建 n个节点的链表*/
public void addPersons(int n) {
if (n < 1) {
System.out.println("构建链表失败, 参数错误!");
return;
}
Person current = null;
for (int i = 1; i <= n; i++) {
/** 设定节点编号*/
Person person = new Person(i);
/** 定义起始节点*/
if (i == 1) {
first = person;
/** 起始节点, 指向自己(自己指向自己)*/
first.setNext(first);
/** 保存当前节点, 用于下次循环时配置下一个节点的引用*/
current = first;
} else {
/** 之前的有效环形的指向是 first, 改成当前节点(current的下一个节点)*/
current.setNext(person);
/** 当前节点的下一个节点引用起始节点*/
person.setNext(first);
current = person;
}
}
}
/** 打印所有节点*/
public void print() {
if (first == null) {
System.out.println("链表为空!");
return;
}
Person current = first;
while (true) {
System.out.printf("节点编号 %d \n", current.getNo());
if (current.getNext() == first) {
break;
}
/** 往后移动一个节点*/
current = current.getNext();
}
}
/**
* 计算后打印每个人出列的顺序编号:
* @param startNo: 表示从第几个小孩开始数数
* @param countNum: 表示数几下
* @param nums: 表示最初有多少小孩在圈中
* */
public void countPerson(int startNo, int countNum, int nums) {
if (first == null || startNo < 1 || startNo > nums) {
System.out.println("参数错误!");
return;
}
Person helper = first;
/** 将 helper指向最后节点*/
while (true) {
/** 循环后, 当前节点与起始节点相同, 则循环结束*/
if (helper.getNext() == first) {
break;
}
helper = helper.getNext();
}
/**
* 例: startNo = 1; -1 = 0时不成立跳过!, 如果 startNo大于1就会调整起始节点
* */
for(int i = 0; i < startNo - 1; i++) {
first = first.getNext();
helper = helper.getNext();
}
/** 每次计数时, 将起始节点与最后节点同时向下移动 m - 1次, 然后出列*/
while(true) {
/** 当链表中只有一个节点时结束*/
if(helper == first) {
break;
}
/** 每次循环, 将起始节点与最后节点向下移动: (计数指定次 -1)个节点, */
for(int j = 0; j < countNum - 1; j++) {
first = first.getNext();
helper = helper.getNext();
}
/** 此时起始节点就是要出列的节点*/
System.out.printf("%d号节点出列\n", first.getNo());
/** 将起始使用第二个节点覆盖*/
first = first.getNext();
/** 最后节点的下一个节点(之前的起始节点), 改成目前的起始节点*/
helper.setNext(first);
}
System.out.printf("最后剩余的节点编号 %d \n", first.getNo());
}
}
public class JosephuApp {
public static void main(String[] args) {
/** 创建单向环形链表实例*/
CircularSingleLinkedList linkedList = new CircularSingleLinkedList();
/** 构建5个节点*/
linkedList.addPersons(5);
System.out.println("输出所有节点编号:");
linkedList.print();
System.out.println("计算后打印每个人出列的顺序编号:");
linkedList.countPerson(1, 2, 5);
}
}
输出:
> 输出所有节点编号:
> 节点编号 1
> 节点编号 2
> 节点编号 3
> 节点编号 4
> 节点编号 5
> 计算后打印每个人出列的顺序编号:
> 2号节点出列
> 4号节点出列
> 1号节点出列
> 5号节点出列
> 最后剩余的节点编号 3