Java 约瑟夫 作者:哇塞大嘴好帥(哇塞大嘴好帅)
作者:哇塞大嘴好帥(哇塞大嘴好帅)
0.Josephu(约瑟夫) 分析
设编号为1,2,… n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,倒数m的那个人出列,他的下一位又从1开始报数,疏导m那个人又出列
假设:
n = 6 有6个人
n = 1 从第一个人开始报数
m = 2 数两下
环形链表思路
1.创建第一个节点,让first指向当前节点,形成环形链表
2.创建一个新的节点,就把该节点加入到已有的环形链表中即可。
遍历思路
1.先让一个复制指针(变量)curBoy,指向first节点。
2.通过while循环遍历该链表 ,如果curBoy.next == first 就代表遍历结束
1.先创建一个类表示一个节点
/**
* 节点
*/
class Node{
private int no;//编号
@Override
public String toString() {
return "Node{" +
"no=" + no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
public Node(int no) {
this.no = no;
}
private Node next;//指向下一个节点 默认为NULL
}
2.约瑟夫 - 增
//创建头节点 永远指向第一个节点
private Node head = new Node(-1);
//创建临时指针,帮助实现环形链表 temp永远指向最新添加的一个节点
Node temp = null;
//添加节点
public void add(Node node){
//如果是第一个节点
if (head.getNext() == null){
head = node;
head.setNext(head);
temp = head;
}else{
//System.out.println(temp);
temp.setNext(node);
node.setNext(head);
temp = node;
}
}
首先创建一个头节点,这个头节点会一直指向第一个节点,接下来创建一个临时指针(变量) ,这个临时变量永远在链表的最新添加的一个节点的位置,
接下来判断添加的位置是否为第一个节点,如果头节点的下一个节点为空,就代表头节点没有数据,我们只需要把第一个节点赋值给头节点即可,头节点的下一个节点等于头节点,temp永远在新加入的节点。
如果不是第一个节点就 链表中的最后一个节点的下一个节点指向新插入的节点,新插入的节点的下一个指向头节点,然后在把新的节点赋值给temp,因为temp要一直指向链表中新添加的节点。
3.约瑟夫 - 查
//创建头节点 永远指向第一个节点
private Node head = new Node(-1);
//创建临时指针,帮助实现环形链表 temp永远指向最后一个节点
Node temp = null;
//遍历节点
public void selectNode(){
//如果链表没有节点
if (head == null){
System.out.println("链表没有数据");
return;
}
//创建临时指针(变量)
temp = head;
//while循环遍历
while (true){
System.out.println(temp);
if (temp.getNext() == head){
System.out.println("遍历结束");
break;
}
//指针下移
temp = temp.getNext();
}
}
首先判断链表中有没有节点,如果头节点的下一个节点 == null 就代表当前链表中没有元素,接下来创建一个临时变量,通过while循环遍历,如果当前节点的下一个节点等于头节点就代表遍历结束。
接来来打印数据,指针下移, 指针下移就是指向当前元素的下一个。
4.约瑟夫-依次出列
/**
* 出列
* @param start 开始出列的位置
* @param count 步数 隔着几个节点出列
**/
public void getMember(int start,int count) throws InterruptedException {
// 有多少个节点在圈中
int nums = validmenber();
/**
* 对数据进行校验
* 1. 如果head == null 就说明链表没有数据
* 2. start < 0 因为只能从1个以上的开始数第几个,不能数第-1个
* 3. start > nums 如果链表有5个节点 你不能从第6个开始数
*/
if (head == null || start < 0 || start > nums){
System.out.println("参数有错误,请重新输入 ");
}
//创建辅助指针,帮助节点出列. 指向最后一个节点
Node foot = head;
//让foot指向链表最后一个节点
while(true){
if (foot.getNext() == head){
break;
}else{
foot = foot.getNext();
}
}
//在列出队列前先让head和foot移动k-1次
for (int i = 0; i < start - 1 ; i++) {
head = head.getNext();
foot = foot.getNext();
}
//开始列出数组
while (true){
//当前链表就剩下一个数据
if (foot == head){
System.out.println("弹出的数据"+head);
head = null;
return;
}
//找到隔着的第N个节点
for (int i = 0; i < count - 1 ; i++) {
head = head.getNext();
foot = foot.getNext();
}
System.out.println("弹出的数据"+head);
head = head.getNext();
foot.setNext(head);
}
}
//查询有效数据
public int validmenber(){
//如果链表没有节点
if (head == null){
System.out.println("链表没有数据");
return 0;
}
//创建临时变量 遍历遍历
Node temp = head;
//记录有效数据个数
int conut = 1;
while (true){
if (temp.getNext() == head){
System.out.println("有效数据个数"+conut);
return conut;
}
conut++;
temp = temp.getNext();
}
}
首先看 int nums = validmenber();获取当前链表的有效节点个数,在看validmenber()方法,原理跟约瑟夫 - 查 一致,就是做了一些修改,在没有遍历之间创建了一个conut用于记录数据个数。如果当前节点的下一个是第一个节点(第一个节点指的是head),就代表遍历结束。
接下里对数据进行校验
- 如果head == null 就说明链表没有数据
- start < 0 因为只能从1个以上的开始数第几个,不能数第-1个
- start > nums 如果链表有5个节点 你不能从第6个开始数
接下来创建一个临时遍历(指针) foot,这个指针(遍历)永远指向最后一个数据,通过While循环将foot指向了最后一个节点。因为如果当前节点的下一个节点等于第一个节点就代表链表后没有数据了。
接下来要让temp还有foot全部后移动 start - 1 次,因为我们要从第start节点开始报数。关于为什么是 start - 1 是因为自己也要报数,自己也算一个人,详情在下方图片
经过以上图片对比我们可以发现 start - 1是从自身开始数2个节点。start是不算自身开始数2个。我们想要的肯定是从自身开始数,因为他自己也算个人,所以要后移start - 1
接下来进入while循环,如果foot == head就代表链表已经遍历完毕,进入此循环打印最后一个节点,接下来再把节点设置为NULL,结束循环,详细会在后面解图显示。
接下来查找与temp隔着count个位置的节点,此步骤原理和头节点尾节点后移原理一样,此步骤移动temp和foot节点,因为要从start - 1后的count - 1开始数进行出列操作。
接下来我们要做出列工作(将此节点在链表删除) 首先我们要让头节点下移动一位,在将foot节点的next 指向头节点,这样就可以删除当前节点了。