Josephu问题与环形链表
Josephu问题:设编号为1,2,…,n的n个人围坐一圈,约定编号为 k ( 1 ≤ k ≤ n ) k(1\le k\le n) k(1≤k≤n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,报到m的那个人又出列,以此类推,直到所有人出列为止,由此产生一个出队编号的序列.
用一个不带头节点的循环链表来处理Josephu问题,先构成一个有n个节点的单循环链表,然后由k节点起从1开始计数,计到m时,对应节点从链表中删除,然后再从被删除节点的下一个节点又从1开始计数,直到最后一个节点从链表中删除,算法结束.
1. 单向环形链表求解
和单链表一样,删除某个特定的节点的操作是—先找到它之前的节点,然后通过cur.next = cur.next.next;
语句来删除.所以在一开始,我们先将光标cur
移动至"节点1"前一个节点"节点5".接下来进行以下操作.
- 然后光标向前移动m-1次(因为报数报m次相当于只移动了m-1次位置).
- 打印cur节点后一个节点(cur.next).
- 删除cur后一位节点
cur.next = cur.next.next;
- 循环执行前3条,直至链表中只剩下一个节点(
cur.next = cur
),打印最后的节点
完整代码:
public class CircleSingleLinkedList
{
//属性:first节点
public Node first;
//创建1-n的链表序列在构造方法中体现
public CircleSingleLinkedList(int n)
{
if(n < 1) //n小于1直接抛出异常
{
throw new RuntimeException("请输入有效的数字!");
}
else if(n == 1) //只有一个元素
{
first = new Node(1);
first.next = first; //自己连接自己
return;
}
//有不止1个节点,将"节点1"作为first节点
first = new Node(1);
Node cur = first;
Node temp = null;
//持续添加2-n的节点
for (int i = 2; i <= n; i++)
{
temp = new Node(i);
cur.next = temp;
cur = cur.next;
}
//将最后一个节点连接至first
temp.next = first;
}
//Josephu问题求解
public void josephu(int m)
{
if(m < 2)
{
System.out.println("请输入合理的m值!");
return;
}
Node cur = first;
//移动至"起点"处---节点"1"的前1位
while(true)
{
if(cur.next == first)
{
break;
}
cur = cur.next;
}
//4. 只剩一个节点时退出循环
while(cur.next != cur)
{
//1. 光标移动m-1次
for (int i = 0; i < m - 1; i++)
{
cur = cur.next;
}
//2. 打印后一节点
System.out.print(cur.next + " ");
//3. 删除节点
cur.next = cur.next.next;
}
System.out.println(cur);
}
}
//定义节点类
class Node
{
int id;
Node next;
public Node(int id)
{
this.id = id;
}
@Override
public String toString()
{
return "id=" + id;
}
}
2. 双向循环链表求解
双向循环链表在删除的时候会提供极大的便利,即利用光标所在之处,可以直接删除当前的节点,因此和单向循环链表比起来,其光标无需再转一圈移动至"节点1"的前一个节点处,直接定在first处即可.Josephu问题利用双向循环链表求解如下:
-
光标cur移动m-1次
-
打印当前节点
-
删除当前节点
cur.prev.next = cur.next; cur.next.prev = cur.prev;
-
光标cur向后移动一次(因为光标此时还停留在被删除的节点上,且其next还连接着下一个节点,因此直接
cur = cur.next
操作即可) -
重复前4项操作,直至链表中只剩下1个节点,将其打印,求解结束
完整代码:
public class CircleDoubleLinkedList
{
Node first;
public CircleDoubleLinkedList(int n)
{
if(n < 1)
{
throw new RuntimeException("请输入有效的数字!");
}
else if(n == 1)
{
first = new Node(1);
first.next = first;
first.prev = first;
return;
}
//有不止1个节点
first = new Node(1);
Node cur = first;
Node temp = null;
for (int i = 2; i <= n; i++)
{
temp = new Node(i);
//注意:双向链表两个节点之间需要连接两次
cur.next = temp;
temp.prev = cur;
cur = cur.next;
}
temp.next = first;
first.prev = temp;
}
//Josephu问题求解
public void josephu(int m)
{
//光标只需定位在first处
Node cur = first;
//5. 重复前4项操作,直至链表中只剩下1个节点
while(cur.next != cur)
{
//1. 光标cur移动m-1次
for (int i = 0; i < m - 1; i++)
{
cur = cur.next;
}
//2. 打印当前节点
System.out.print(cur + " ");
//3. 删除节点
cur.prev.next = cur.next;
cur.next.prev = cur.prev;
//4.光标cur向后移动一次
cur = cur.next;
}
//打印最后的节点
System.out.println(cur);
}
}
class Node
{
int id;
Node next;
Node prev;
public Node(int id)
{
this.id = id;
next = null;
prev = null;
}
@Override
public String toString()
{
return "id=" + id;
}
}