单向环形链表
- 可以带头节点,也可以不带头节点。
- 虽然环形链表是一个环,但在实现过程中,需要确定第一个数据节点,在单向链表的基础上,使最后一个节点指向第一个数据节点,就构成了单向环形链表。
约瑟夫问题
Josephu(约瑟夫)问题:
设编号为1、2、…、n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
代码实现
- 链表实现
(1)CircleSingleLinkedList()
:空的单向环形链表构造方法。
(2)CircleSingleLinkedList(int num)
:构造一条节点个数为num的单向环形链表。链表的第一个节点编号(no)为1,按照节点指向编号不断增加1,最后一个节点的编号为num。
(3)void printList()
:打印环形链表。
(4)void add(Node3 node)
:向环形链表最后添加一个节点。环形链表中 相对于第一个节点而言的最后一个节点 后面添加一个节点。 - 约瑟夫问题解决思路(方法一)
(1)创建一个辅助指针(变量)helper,事先让其指向环形链表的最后一个节点。
(2)开始报数前,让firstNode(指向链表中第一个节点的变量)和helper向后移动k-1次,使firstNode指向第一个报数的节点(把节点当作报数的人)。
(3)模拟报数过程,让firstNode和helper向后移动m-1次。此时firstNode指向的节点就是应该出列的节点。
(4)将firstNode指向的节点从链表中删除。
firstNode = firstNode.next;helper.next = firstNode;
(5)然后转到第(3)步,循环执行,知道链表中只剩下一个节点。 - 约瑟夫问题解决思路(方法二)
定位到要出列的节点的前一个节点,让此节点指向出列节点的下一个节点,这样出列节点就被从链表中删除了。 - 代码
/**
* 实现环形单链表,并使用环形单链表解决约瑟夫问题
* @author dxt
*
*/
public class CircleSingleLinkedListDemo {
public static void main(String[] args) {
CircleSingleLinkedList csll = new CircleSingleLinkedList(5);
csll.printList();
csll.josepfu(1, 2, 5);
System.out.println("----------哈哈分割线-----------");
CircleSingleLinkedList csll1 = new CircleSingleLinkedList(5);
csll1.josephu2(1, 2);
System.out.println("***********美丽分割线**********");
CircleSingleLinkedList csll2 = new CircleSingleLinkedList();
Node3 n1 = new Node3(1);
Node3 n2 = new Node3(2);
Node3 n3 = new Node3(3);
csll2.add(n1);
csll2.add(n2);
csll2.add(n3);
csll2.printList();
}
}
/**
* 节点类
* @author dxt
*
*/
class Node3{
private int no; //节点编号
private Node3 next; //指向下一节点的指针
//构造器
public Node3(int no) {
this.no = no;
}
//javaBean, id用不到,就不再写了
public void setNo(int no) {
this.no = no;
}
public int getNo() {
return this.no;
}
public void setNext(Node3 node) {
this.next = node;
}
public Node3 getNext() {
return this.next;
}
}
/**
* 环状单链表实现类,没有头节点
* @author dxt
*
*/
class CircleSingleLinkedList{
private Node3 firstNode = null; //虽然是环状链表,但依然将第一个加入到链表中的节点作为第一个节点
//构造器
public CircleSingleLinkedList(){
}
/**
* 构造一条节点数为num个的环状单向链表,其中第一个节点id为1,之后依次递加,最后一个节点为num
* @param num
*/
public CircleSingleLinkedList(int num) {
//1. 先对num做一个校验,链表中至少有一个节点
if(num < 1) {
System.out.println("节点数至少为1个。输入错误,无法创建链表。");
return;
}
//2. 创建链表
Node3 curNode = null; //辅助节点,定位到链表中最后一个节点
for(int i=1; i<=num; i++) {
Node3 tempNode = new Node3(i); //声明一个序号为i的节点
if(i == 1) { //是链表中第一个节点
firstNode = tempNode;
firstNode.setNext(firstNode); //此时环形链表中只有一个节点,自己构成一个环
curNode = firstNode; //定位到链表中最后一个节点
}else {
curNode.setNext(tempNode); //链接到新的节点
tempNode.setNext(firstNode); //构成换
curNode = tempNode; //定位到链表中最后一个节点
}
}
}
/**
* 遍历输出环形链表
*/
public void printList() {
//1. 判断链表是否为空
if(firstNode == null) {
System.out.println("链表为空。");
return;
}
//2. 遍历
Node3 temp = firstNode;
while(true) {
System.out.println("节点的编号为:" + temp.getNo());
if(temp.getNext() == firstNode) { //遍历完成,退出
break;
}
temp = temp.getNext();
}
}
/**
* 向链表尾添加一个节点
* @param no
*/
public void add(Node3 node) {
//当链表为空时
if(firstNode == null) {
firstNode = node;
firstNode.setNext(firstNode);
}else { //当链表不为空时
Node3 temp = firstNode;
while(true) {
if(temp.getNext() == firstNode) {
break;
}
temp = temp.getNext();
}
node.setNext(firstNode); //添加到最后,那么它肯定指向第一个节点
temp.setNext(node); //将节点添加到链表中
}
}
/**
* 约瑟夫问题,解决方法是使用两个指针
* @param startNo 表示从第几个小孩开始数数,startNo为开始数数小孩的id(也是默认的排列序号)
* @param countNum 表示数几下
* @param nums 表示最初有多少个小孩在圈中
*/
public void josepfu(int startNo, int countNum, int nums) {
//1. 先进行数据校验
if(firstNode == null || startNo < 1 || startNo > nums) {
System.out.println("参数输入错误,请重新输入。");
return;
}
//2. 创建一个辅助指针,帮助完成节点出圈
Node3 helper = firstNode;
//2.1 先使helper指针指向最后一个节点
while(true) {
if(helper.getNext() == firstNode) {
break;
}
helper = helper.getNext();
}
//2.2 定位到第一个报数的节点, 移动startNo-1次(第二个节点开始报,则只需firstNode向后移动一次即可)
for(int i=0; i<startNo-1; i++) {
firstNode = firstNode.getNext(); //此方法会改变链表结构,所以直接使用firstNode节点
helper = helper.getNext();
}
//2.3 报数时,helper和firstNode节点都向后移动countNum-1次,然后从链表中离开,知道链表中只有一个节点
while(true) {
if(helper == firstNode) { //圈中只剩一个节点
System.out.println("出圈:"+firstNode.getNo()); //最后一个节点出链表
break;
}
//helper和firstNode节点都向后移动countNum-1次
for(int i=0; i<countNum-1; i++) {
firstNode = firstNode.getNext();
helper = helper.getNext();
}
//打印要出圈的节点
System.out.println("出圈:" + firstNode.getNo());
//从链表中删除 出圈的 节点(即firstNode指向的节点)
firstNode = firstNode.getNext();
helper.setNext(firstNode);
}
}
/**
* 第二种约瑟夫问题解决方法,感觉 效率要比第一种约瑟夫问题解决方法要高。
* @param startNo
* @param countNum
*/
public void josephu2(int startNo, int countNum) {
//1. 先进行数据校验
if(firstNode == null || startNo < 1) {
System.out.println("参数输入错误,请重新输入。");
return;
}
//2. 使firstNode定位到开始报数的节点
for(int i=0; i<startNo-1; i++) {
firstNode = firstNode.getNext();
}
//3. 模拟报数
//3.1 当countNum=1时
if(countNum == 1) { //如果countNum=1,则直接报数,不从链表中删除报数的节点
Node3 temp = firstNode;
while(true) {
System.out.println("出圈:" + temp.getNo());
if(temp.getNext() == firstNode) {
break;
}
temp = temp.getNext(); //只是后移,不需要从链表中删除
}
return; //返回
}
//3.2 当countNum>1时,此时需要从链表中删除节点
while(true) {
//当链表中只剩一个节点
if(firstNode.getNext() == firstNode) { //链表中只剩一个节点
System.out.println("出圈:" + firstNode.getNo()); //打印最后一个节点编号
break;
}
//模拟报数
for(int i=0; i<countNum-2; i++) {
firstNode = firstNode.getNext();
}
//打印firstNode.next节点的no
System.out.println("出圈:"+firstNode.getNext().getNo());
//删除firstNode指向的下一个元素
firstNode.setNext(firstNode.getNext().getNext());
firstNode = firstNode.getNext();
}
}
}