环形链表的情况就更为特殊了,它是一个环状的,所以就跟我们之前的基本操作就不太一样了
这里讲的是单向约瑟夫环
丢手绢大家都知道吧,这就是约瑟夫环,本文以此为例
要求:
一个 n 个小朋友
从第k个小朋友开始报数,(开始的小朋友报1,下一个小朋友报2)
比如报m个数,那么 报的数为m的小朋友出列
给出小朋友出队的顺序。
例子🌰:
一共5个小朋友
从第一个小朋友开始数,数2个数就可以了,那么它的出队顺序为 2,4,1,5,3
如图:
思路:
需要两个辅助节点,first 和 helper
first 是开始报数的小朋友
helper是它前边的一个节点,代表一个环里最后的一个小朋友。
在报数的时候,first = first.next, helper = helper.next,两个节点都要后移
在让小朋友出对的时候(删除节点),first 要再后移一次,first = first.next
然后 helper.next = first
约瑟夫环节点结构
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;}@Overridepublic String toString() {return "Boy [no=" + no + "]";}}
基本操作
添加
添加的要注意一点,当只有一个节点时,它的next域指向自己,也形成一个环
如果不止一个节点时,next指向下一个,然后最后一个节点的next指向第一个
思路:
first 节点用来指向 第一个小孩节点
一个辅助接点 curBoy 用来指向遍历后的最后一个节点
如果是第一个节点时,first.next = first,此时 curBoy 也为 first
其余的时候新加节点 boy
最后一个节点next指向新节点
curBoy.next = boy
新节点next 指向第一个节点
boy.next = first;
curBoy 也后移
curBoy = boy
//第一个小孩节点private Boy first = null;//添加小孩,构成环形链表public void add(int nums) {//如果nums < 1 不允许,至少要有一个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指向第一个小孩curBoy = first;} else {//curBoy 指向新节点curBoy.setNext(boy);boy.setNext(first);//curBoy 后移一下curBoy = boy;}}}
遍历
遍历的时候,只需要判断 curBoy.next 是否与 frist 相当,如果相等,说明,已经遍历了一次环
//遍历当前的环形链表public void list() {if (first == null) {System.out.println("链表为空");return;}Boy curBoy = first;while(true) {System.out.println(curBoy);if( curBoy.getNext() == first) {break;}//curBoy 后移curBoy = curBoy.getNext();}}
获得出队顺序
思路:
startNo 开始报数的人,count 报数到第几个数,nums 一共有多少个小朋友
需要两个辅助节点,first 和 helper
first 是开始报数的小朋友
helper是它前边的一个节点,代表一个环里最后的一个小朋友。
首先要先让helper 指向环中最后一个节点
然后让 first 指向 startNo这个开始报数的小朋友,
(Ps: 其实这两步可以合二为一,先让 helper = first,然后再让 first = first.next 可以达到同样的效果,不然时间复杂度太高了,但是注意,从第一个小朋友开始的时候不适用,可以单独判断一下)
然后开始报数,也就是遍历 count -1 次,因为开始的小朋友也报数,
在报数的时候,first = first.next, helper = helper.next,两个节点都要后移
在让小朋友出对的时候(删除节点),first 要再后移一次,first = first.next
然后 helper.next = first
最后当 first == helper的时候,说明这是环中的最后一个节点
直接输出就行了
//获取出队顺序//startNo 开始报数的人,count 报数到第几个数,nums 一共有多少个小朋友public void popNums(int startNo, int count, int nums) {if (first == null || startNo < 1 || startNo > nums || nums < 1) {System.out.println("参数有误");return;}//指向最后一个节点Boy helper = first;if (startNo == 1) {while(true) {if( helper.getNext() == first) {break;}//curBoy 后移helper = helper.getNext();}}//移动开始报数的那个小孩的位置for(int i = 0; i < startNo -1; i++) {helper = first;first = first.getNext();}while(true) {//说明圈中只有一个节点if(helper == first) {break;}for(int i = 0; i < count - 1; i++) {first = first.getNext();helper = helper.getNext();}System.out.println(first+": 出圈");//出圈后,删除这个节点first = first.getNext();helper.setNext(first);}System.out.println(first+": 最后出圈");}
