6、链表-单向环形链表

来源:https://www.bilibili.com/video/BV1B4411H76f?p=28

一、约瑟夫问题

n个人围成一圈,从编号为k(1 ≤ k ≤ n)的那个人开始数,数到m的那个人出列。想要按照出列的顺序把这些人依次指出。

这样的约瑟夫问题可以通过一个没有头结点的单向环形链表处理。结点数目为n,从第k个结点开始计数,按照链表顺序一直计数到m,将m对应的这个人删除。

举一个n=5 k=1 m=2的例子

二、思路

2.1 创建单向环形链表的思路

1、创建第一个结点,让first指针指向该节点,同时该节点与自身形成环

2、后面需要知道当前创建的环形链表的大小,即上述约瑟夫问题中的n。将n个新结点加入到已有的环形链表中。

2.2 遍历单向环形链表的思路

1、辅助指针指向first结点

2、利用while循环,直到【当前结点.next】=【first】

2.3 约瑟夫问题的解决思路

1、为了避免整个环形链表断掉,必须在要出去的结点前面加一个辅助结点helper,当前结点first出去之后,

【first】=【first.next】

【helper.next】=【first】

2、因为整个问题开始的时候,first指向的是第一个创建的结点的位置,所以可以把helper之前它的前面,也就是环形的最后一个结点。当知道从编号为k的那个结点开始之后,两个指针同时后移相应的位置,令first指向开始的结点,helper指向前一个。

3、开始报数之后,也是first、helper指针同时移动,直到走到要删除的结点。

三、实现

3.1 创建单向环形链表

1、新建一个对应于约瑟夫问题的结点类,编号属性代表的是第几个人,next属性指向下一个人。

 1 public class Node3 {
 2     private int no;
 3     private Node3 next;
 4 
 5     public Node3(int no) {
 6         this.no = no;
 7     }
 8 
 9     public int getNo() {
10         return no;
11     }
12 
13     public void setNo(int no) {
14         this.no = no;
15     }
16 
17     public Node3 getNext() {
18         return next;
19     }
20 
21     public void setNext(Node3 next) {
22         this.next = next;
23     }
24 }

 2、创建一个单向环形链表类CircleSingleLinkedList,类中有一个first结点,代表要创建的链表的第一个结点。在类中加入了利用输入的结点个数构建环形链表的方法,以及通过遍历的方式展示整个环形链表的方法。

 1 public class CircleSingleLinkedList {
 2     //创建第一个结点
 3     private Node3 first = new Node3(-1);
 4 
 5     //构建环形链表
 6     //为了简化,结点中只有编号这个属性,针对约瑟夫问题,编号顺序递增排列,但是需要知道有多少个人nums围成一圈
 7     public void addNode(int nums){
 8         if(nums < 1){
 9             System.out.println("参数nums错误");
10             return;
11         }
12         Node3 curNode = null;//辅助指针,指向当前结点
13         for (int i = 1; i <= nums; i++) {
14             Node3 node3 = new Node3(i);
15 
16             if(i == 1){
17                 //第一个结点,需要利用first自己形成环形
18                 //之后first就不再动了,由curNode辅助指针完成之后的操作
19                 first = node3;
20                 first.setNext(first);
21                 curNode = first;
22             }else {
23                 curNode.setNext(node3);
24                 node3.setNext(first);
25                 curNode = node3;
26             }
27         }
28     }
29 
30     //遍历,显示环形链表
31     public void show(){
32         if(first.getNo() == -1){
33             System.out.println("链表为空");
34             return;
35         }
36         Node3 curNode = first;
37         while (true){
38             System.out.printf("当前结点的编号为%d",curNode.getNo());
39             System.out.println();
40             if(curNode.getNext() == first){
41                 System.out.println("遍历完毕");
42                 break;
43             }
44             curNode = curNode.getNext();
45         }
46     }
47 }

测试,这里加入了5个结点(5个人围成一圈),构成环形链表

 1     public static void main(String[] args) {
 2         CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
 3 
 4         circleSingleLinkedList.show();
 5         System.out.println();
 6 
 7         circleSingleLinkedList.addNode(5);
 8 
 9         circleSingleLinkedList.show();
10 
11     }

结果

链表为空

当前结点的编号为1
当前结点的编号为2
当前结点的编号为3
当前结点的编号为4
当前结点的编号为5
遍历完毕

3.2 约瑟夫问题

在CircleSingleLinkedList类中加入一个统计出圈顺序的方法

 1     //约瑟夫问题,
 2     // 根据用户输入的
 3     // **围成一圈的人的数目nums,
 4     // **从编号startNo开始,
 5     // **数到m的那个人出圈
 6     // 排列出圈的顺序
 7     public void countNode(int nums, int startNo, int m){
 8         if(first.getNo() == -1 || startNo < 1 || startNo > nums){
 9             System.out.println("参数错误");
10             return;
11         }
12 
13         Node3 helper = first;//辅助指针
14         //令辅助指针指向first的前一个位置
15         while (true){
16             if(helper.getNext() == first){
17                 break;
18             }
19             helper = helper.getNext();
20         }
21 
22         //将first和helper定位到开始的位置startNo
23         for (int i = 0; i < startNo - 1; i++) {
24             first = first.getNext();
25             helper = helper.getNext();
26         }
27 
28         //开始报数
29         while (true){
30             //循环结束的标志是:只剩最后一个人了,first和helper指针都在他上面
31             if(first == helper){
32                 break;
33             }
34             //数到m
35             for (int i = 0; i < m - 1; i++) {
36                 first = first.getNext();
37                 helper = helper.getNext();
38             }
39             //first指向的第m个人要出圈
40             System.out.printf("%d出圈",first.getNo());
41             System.out.println();
42             first = first.getNext();//出圈时,first要重新定位
43             helper.setNext(first);//helper的下一个也要重新指定
44         }
45         //输出一下最后剩的那个结点
46         System.out.printf("最后一个是%d",first.getNo());
47     }

测试,在上面测试的基础上进行(已经插入了5个结点)

1         circleSingleLinkedList.countNode(5,1,2);

结果,与上面图中的顺序完全一致

2出圈
4出圈
1出圈
5出圈
最后一个是3
原文地址:https://www.cnblogs.com/zhao-xin/p/13141790.html