本文旨在加深学习印象并留记录以供日后查看。学习视频为韩顺平图解Java数据结构和算法,以下内容来自于视频内容与自我总结。如有错误请您指正。
约瑟夫问题
问题描述
设编号为1,2,… n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
问题分析
可以使用环形链表来解决
先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。
环形链表
环形链表就是在单向链表的基础上,让最后节点的next指向first节点,让整个链表形成一个闭环。如图所示:
实现方案
创建节点
/**
1. 创建一个boy类表示节点
*/
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;
}
@Override
public String toString() {
return "Boy{" +
"no=" + no +
'}';
}
}
构建环形链表
构建单向环形链表,并实现向链表中添加节点以及环形链表的遍历
/**
2. 环形链表
*/
class CircleSingleLinkedList {
//创建first节点
private Boy first = new Boy(1);
//添加boy节点,构建环形链表
public void addBoy(int num){
if (num < 1){
System.out.println("num需要>0");
return;
}
//先把first指向自己,让自己成环
if (first.getNext() == null){
first.setNext(first);
}
//定义一个临时变量指向first
Boy temp = first;
//循环添加
//Boy(1)的节点已经创建,所以i从2开始,如果输入的num为1,不会进入循环
for (int i = 2; i <=num ; i++) {
Boy boy = new Boy(i);//创建节点i
//temp开始是指向first的,让temp的next指向boy(i),boy(i)的next指向first成环,再将temp后移
temp.setNext(boy);
boy.setNext(first);
// temp = temp.getNext();
//temp.getNext()就是boy
temp = boy;
}
}
//遍历环形链表
public void show(){
if (first.getNext() == null){
System.out.println("环形链表为空");
return;
}
//遍历环形链表 当最后一个节点的next指向first节点时,完成一圈遍历
System.out.println(first.toString());
Boy temp = first.getNext();
while (temp != first){
System.out.println(temp.toString());
temp = temp.getNext();
}
}
}
符合条件的节点出圈
- 需要创建一个辅助指针(变量) helper , 事先应该指向环形链表的最后的节点
- 先让 first 和 helper 移动 k - 1次
- 让first 和 helper 指针同时 的移动 m - 1 次
- 此时first 指向的节点即为符合条件的节点
① first = first.next
② helper.next = first - 原来first 指向的节点没有任何引用,就会被回收
假设n = 5 , 即有5个人 ,k = 1, 从第一个人开始报数,m = 2, 数2下
此时出圈的顺序为2->4->1->5->3
代码实现:
/**
* 计算boy的出圈顺序
* @param startNo 表示从第几个小孩开始数数
* @param countNum 表示数几下
* @param nums 表示最初有多少小孩在圈中
*/
public void countBoy(int startNo, int countNum, int nums){
if (first == null || startNo < 1 || countNum < 0 || startNo > nums){
System.out.println("参数不符合规定");
return;
}
//需要一个指针指向最后(next是first的节点),来帮助符合条件的boy节点出圈
Boy helper = first;
while (helper.getNext() != first){
helper = helper.getNext();
}
//此时的first指向first,helper指向最后; 需要从第startNo开始数,所以把first和helper向后移动startNo个节点
for (int i = 0; i < startNo - 1 ; i++) {
first = first.getNext();
helper = helper.getNext();
}
//此时开始数,数countNum下后(向后移动countNum-1个节点),first指向的boy节点出圈
//这里是一个循环操作,直到圈中只有一个节点
while (first != helper){
//移动
for (int i = 0; i < countNum - 1 ; i++) {
first = first.getNext();
helper = helper.getNext();
}
//出圈
System.out.println(first.toString() + "出圈");
//
first = first.getNext();
helper.setNext(first);
}
System.out.println(first.toString() + "最后出圈");
}
完整代码及测试结果
完整代码:
package com.hyf.linkedlist;
public class Josepfu {
public static void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.show();
System.out.println("-------------");
circleSingleLinkedList.addBoy(5);
System.out.println("-------------");
circleSingleLinkedList.show();
System.out.println("-------------");
circleSingleLinkedList.countBoy(1,2,5);//2-4-1-5-3
// circleSingleLinkedList.countBoy(2,2,5);//3-5-2-1-4
// 可以把addBoy放到countBoy内,nums即为add的个数
}
}
/**
* 环形链表
*/
class CircleSingleLinkedList {
//创建first节点
private Boy first = new Boy(1);
//添加boy节点,构建环形链表
public void addBoy(int num){
if (num < 1){
System.out.println("num需要>0");
return;
}
//先把first指向自己,让自己成环
if (first.getNext() == null){
first.setNext(first);
}
//定义一个临时变量指向first
Boy temp = first;
//循环添加
//Boy(1)的节点已经创建,所以i从2开始,如果输入的num为1,不会进入循环
for (int i = 2; i <=num ; i++) {
Boy boy = new Boy(i);//创建节点i
//temp开始是指向first的,让temp的next指向boy(i),boy(i)的next指向first成环,再将temp后移
temp.setNext(boy);
boy.setNext(first);
// temp = temp.getNext();
//temp.getNext()就是boy
temp = boy;
}
}
//遍历环形链表
public void show(){
if (first.getNext() == null){
System.out.println("环形链表为空");
return;
}
//遍历环形链表 当最后一个节点的next指向first节点时,完成一圈遍历
System.out.println(first.toString());
Boy temp = first.getNext();
while (temp != first){
System.out.println(temp.toString());
temp = temp.getNext();
}
}
/**
* 计算boy的出圈顺序
* @param startNo 表示从第几个小孩开始数数
* @param countNum 表示数几下
* @param nums 表示最初有多少小孩在圈中
*/
public void countBoy(int startNo, int countNum, int nums){
if (first == null || startNo < 1 || countNum < 0 || startNo > nums){
System.out.println("参数不符合规定");
return;
}
//需要一个指针指向最后(next是first的节点),来帮助符合条件的boy节点出圈
Boy helper = first;
while (helper.getNext() != first){
helper = helper.getNext();
}
//此时的first指向first,helper指向最后; 需要从第startNo开始数,所以把first和helper向后移动startNo个节点
for (int i = 0; i < startNo - 1 ; i++) {
first = first.getNext();
helper = helper.getNext();
}
//此时开始数,数countNum下后(向后移动countNum-1个节点),first指向的boy节点出圈
//这里是一个循环操作,直到圈中只有一个节点
while (first != helper){
//移动
for (int i = 0; i < countNum - 1 ; i++) {
first = first.getNext();
helper = helper.getNext();
}
//出圈
System.out.println(first.toString() + "出圈");
//
first = first.getNext();
helper.setNext(first);
}
System.out.println(first.toString() + "最后出圈");
}
}
/**
* 创建一个boy类表示节点
*/
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;
}
@Override
public String toString() {
return "Boy{" +
"no=" + no +
'}';
}
}
测试结果: