一、基本介绍
顾名思义,在内存中单向循环链表就是将单链表的最后一个节点的next再指向单链表的第一个节点,在逻辑结构上就是将原来的一条链变成首尾相接的一个圆环,从而实现单链表的一个循环。如下图(此处为无头节点的单向链表):
二、单向循环链表的使用
1、定义单向循环链表基本结构
class Node1 {
//这里的变量用private修饰,所以下面需要定义get、set方法
//和单链表相同,此处可以声明多个data
private int data;
private Node1 next;
//空参构造器
public Node1() {
}
//带参构造器
public Node1(int data) {
this.data = data;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public Node1 getNext() {
return next;
}
public void setNext(Node1 next) {
this.next = next;
}
}
2、同样我们再定义一个类来添加增删改查基本操作(与普通单链表相似,但要让尾节点指向首节点)
class CircleSingleLinkedList1 {
//创建一个first节点
private Node1 first;
//添加节点,这里默认data唯一,不可重复添加
public void addNode(Node1 node) {
if (first == null) {
first = node;
first.setNext(first);
return;
}
Node1 temp = first;
boolean flag = false;
while (true) {
if (temp.getNext() == first) {
break;
}
if (node.getData() == temp.getData()) {
flag = true;
break;
}
temp = temp.getNext();
}
if (flag) {
System.out.printf("节点%d已经存在,不可重复添加\n",node.getData());
} else {
temp.setNext(node);
node.setNext(first);
}
}
//按data的大小排序添加,这里默认data唯一,不可重复添加
public void addNodeByOrder(Node1 node) {
if (first == null) {
first = node;
first.setNext(first);
return;
}
Node1 temp = first;
boolean flag = false;
while (true) {
if (temp.getNext() == first) {
break;
}
if (temp.getNext().getData() > node.getData()) {
break;
} else if (temp.getNext().getData() == node.getData()) {
flag = true;
break;
}
temp = temp.getNext();
}
if (flag) {
System.out.printf("节点%d已经存在,不可重复添加\n",node.getData());
} else {
node.setNext(temp.getNext());
temp.setNext(node);
}
}
//删除节点
public void deleteN(int no) {
if (first == null) {
System.out.println("链表为空");
return;
}
Node1 temp = first;
boolean flag = false;
if (no == first.getData()) {
while (true) {
if (temp.getNext() == first) {
break;
}
temp = temp.getNext();
}
first = first.getNext();
temp.setNext(first);
}
while (true) {
if (temp.getNext() == first) {
break;
}
if (no == temp.getNext().getData()) {
flag = true;
break;
}
temp = temp.getNext();
}
if (flag) {
temp.setNext(temp.getNext().getNext());
} else {
System.out.println("未找到该节点");
}
}
//修改链表
public void update(Node1 node) {
if (first == null) {
System.out.println("链表为空");
return;
}
Node1 temp = first;
boolean flag = false;
while (true) {
if (temp.getNext() == first) {
break;
}
if (node.getData() == temp.getData()) {
flag = true;
break;
}
temp = temp.getNext();
}
if (flag) {
temp.setData1(node.getData1());
} else {
System.out.println("未找到该节点");
}
}
//遍历
public void showNode() {
if (first == null) {
System.out.println("链表为空");
return;
}
Node1 curNode = first;
while (true) {
System.out.printf("节点%d的data是%d\n", curNode.getData(),curNode.getData1());
if (curNode.getNext() == first) {
break;
}
curNode = curNode.getNext();
}
}
}
3、写一个Demo来测试一下
/**
* @author dankejun
* @create 2020-03-31 16:18
*/
public class CircleSingleLinkedListExer {
public static void main(String[] args) {
CircleSingleLinkedList1 linkedList1 = new CircleSingleLinkedList1();
Node1 node1 = new Node1(1, 10);
Node1 node2 = new Node1(2, 20);
Node1 node3 = new Node1(3, 30);
Node1 node4 = new Node1(4, 40);
//自定义顺序添加节点
// linkedList1.addNode(node1);
// linkedList1.addNode(node2);
// linkedList1.addNode(node3);
// linkedList1.addNode(node4);
//按data大小添加节点
linkedList1.addNodeByOrder(node1);
linkedList1.addNodeByOrder(node3);
linkedList1.addNodeByOrder(node2);
linkedList1.addNodeByOrder(node4);
System.out.println("原来的链表为:");
linkedList1.showNode();
System.out.println();
Node1 node5 = new Node1(3, 50);
linkedList1.update(node5);
System.out.println("修改后的链表为:");
linkedList1.showNode();
System.out.println();
linkedList1.deleteN(2);
System.out.println("删除后的链表为:");
linkedList1.showNode();
}
}
测试结果:
三、Josephu(约瑟夫)问题
有n个人,编号为1~n,从第k个人开始报数,从1开始报,报到m的人会出列,然后从第m+1个人开始,重复以上过程。在出列了n-1个人后,
问出列的序列和最后一个人的编号是?
1、问题分析
我们可以用一个不带头节点的循环链表来处理Josephu问题。先创建一个有n个节点的循环单链表,然后从k节点起从1开始计数,计到m,就把当前节点从链表中删除,然后从m+1个节点起再从1开始计数,知道链表中只剩下一个节点为止。
需要注意:
- 我们通过移动first指针,使first指针指向要出列的人,将其删除,所以需要创建一个辅助指针指向链表尾。
- 由于从k个人开始报数,所以在报数前,需要将first和helper移动k-1次。
- 当报数时,将first和helper移动m-1次。
- 将first指针所处的节点删除,再将first和helper向后移动一个节点:
first = first.next
helper.next = first
2、代码实现
1、定义一个链表结构
class Boy {
private int no;
private Boy next;
public Boy(int no) {
this.no = no;
}
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;
}
}
2、创建一个类来定义基本方法
class CircleSingleLinkedList {
//创建一个first节点
private Boy first = null;
//添加一个节点
public void addBoy(int nums) {
if (nums < 1) {
System.out.println("nums的值不正确");
return;
}
Boy curBoy = null;//辅助节点
for (int i = 1; i <= nums; i++) {
Boy boy = new Boy(i);
if (i == 1) {
first = boy;
first.setNext(first);
curBoy = first;
} else {
curBoy.setNext(boy);
boy.setNext(first);
curBoy = boy;
}
}
}
//遍历
public void showBoy() {
if (first == null) {
System.out.println("链表为空");
return;
}
Boy curBoy = first;
while (true) {
//System.out.printf("小孩的编号%d\n", curBoy.getNo());
if (curBoy.getNext() == first) {
break;
}
curBoy = curBoy.getNext();
}
}
//根据用户输入计算出出圈的顺序
/**
*
* @param startnum 表示从第几个小孩开始
* @param countnum 表示数几下
* @param nums 表示最初有几个小孩
*/
public void countBoy(int startnum, int countnum, int nums) {
if (first == null || startnum < 1 || startnum > nums) {
System.out.println("参数输入有误");
return;
}
Boy helper = first;
while (true) {
if (helper.getNext() == first) {
break;
}
helper = helper.getNext();
}
for (int i = 0; i < startnum - 1; i++) {
first = first.getNext();
helper = helper.getNext();
}
while (true) {
if (helper == first) {//只剩一个节点
break;
}
for (int i = 0; i < countnum - 1; i++) {
first = first.getNext();
helper = helper.getNext();
}
//此时first节点指向出圈的节点
System.out.printf("小孩%d出圈\n",first.getNo());
first = first.getNext();
helper.setNext(first);
}
System.out.printf("最后留在圈中的是%d\n", first.getNo());
}
}
3、写一个Demo来测试
/**
* @author dankejun
* @create 2020-03-27 17:33
*/
public class Josepfu {
public static void main(String[] args) {
CircleSingleLinkedList list = new CircleSingleLinkedList();
list.addBoy(5);
list.showBoy();
list.countBoy(1, 2, 5);
}
}
测试结果: