约瑟夫问题
具体内容:
设编号为1到n的n个人围坐成一圈,约定编号为k(k是1到n之间任何一个数)的人开始报数,数到m的那个人出列,他的下一位又从1开始报数,数到m那个人又出列,依此类推,直到所有人出列,产生一个出列编号的序列
解决方法:
- 数组取模的环形运算
- 单向环形链表
单向环形链表
单链表与循环链表的区别
- 循环链表是头尾相接的单链表,单链表的终端结点指针端由空指针指向头结点
- 遍历的判断条件上:单链表的判断条件是p.next是否等于null,循环链表的判断条件是p.next是否等于头结点
循环链表解决约瑟夫问题
构建单向循环链表
- 创建结点的类,包含编号和next指针
- 选择初始结点为头结点first,头结点不动
- 定义辅助变量curBoy来添加新的结点
- 从单向循环的空链表开始构建
代码实现:
我的代码:
package circularlinkedlist;
public class CirLinkedlist {
public static void main(String[] args) {
CLL c1 = new CLL();
c1.add(5);
c1.show();
}
}
class CLL{
private Boy first = null;
public void add(int nums){
//传入约瑟夫环的人数,先要校验一下认数
Boy curBoy = null;
if(nums < 1){
System.out.println("输入人数有误");
// return;
//加一个return,结束程序,加了return就没有必要再加上else
}else{
for(int i = 1;i <= nums ; i ++){
if(i == 1){
first = new Boy(1);
first.setNext(first);
curBoy = first;
}else{
Boy boy = new Boy(i);
curBoy. setNext(boy);
boy.setNext(first);
curBoy = boy;
}
}
}
}
public void show(){
Boy curBoy = first;
while (true){
System.out.println(curBoy);
curBoy = curBoy.getNext();
if(curBoy == first){
break;
}
}
}
}
class Boy{
private int no;
private Boy next;
public int getNo() {
return no;
}
public Boy getNext() {
return next;
}
public Boy(int no) {
this.no = no;
}
public void setNo(int no) {
this.no = no;
}
public void setNext(Boy next) {
this.next = next;
}
@Override
public String toString() {
return "Boy{" +
"no=" + no +
'}';
}
教程代码:
class CLL{
private Boy first = null;
public void add(int nums){
//传入约瑟夫环的人数,先要校验一下认数
Boy curBoy = null;
if(nums < 1){
System.out.println("输入人数有误");
// return;
//加一个return,结束程序,加了return就没有必要再加上else
}else{
for(int i = 1;i <= nums ; i ++){
Boy boy = new Boy(i);
if(i == 1){
first = boy;
first.setNext(first);
curBoy = first;
//让辅助指针指向头结点
}else{
curBoy. setNext(boy);
boy.setNext(first);
curBoy = boy;
//辅助指针后移
}
}
}
}
public void show(){
if(first == null){
System.out.println("空链表");
return;
}
Boy curBoy = first;
while (true){
System.out.println(curBoy);
if(curBoy == first){
break;
}
curBoy = curBoy.getNext();
}
}
对比:
- 对于if条件句他用return要较多,而不是else,确实较少了嵌套
- 遍历时判断循环链表是否为空,直接看头指针是否为空
产生一个出圈的数列
思路分析 :
- 出圈意味着删除,单向链表的删除必须要找到待删除结点的前一个结点,故而设置一个辅助指针helper
- 解决报数问题,first开始报数,根据k的值first向后移,同时helper跟着first移动等待删除对应的指针
- 输出的同时删除对应的结点,first = first.next,helper.next = first
代码实现:
我的代码:
public void run(int k){
if(k < 0 || k > nums){
System.out.println("输入的数字有问题");
return;
}
Boy helper = new Boy();
helper.setNext(first);
while (true){
for(int i = 1; i < k; i++){
first = first.getNext();
helper = helper.getNext();
//helper = setNext(first);
}
System.out.println(first);
first = first.getNext();
helper.setNext(first);
if (first == null){
System.out.println(first);
break;
}
}
}
运行异常:
问题分析:陷入死循环,而且仅仅重负输出三号,说明迭代没有成功,把helper = helper.getNext()改为helper.setNext(first),让helper始终以first为后继指针,而不是仅仅自己去循环。
问题:两种方式效果不是相同的吗?
问题:就算我不把curBoy设置成尾结点,但是我设置成头结点前一个指针仍旧可以开始循环?
出现如下的错误:
说明输出的顺序没有问题,但是终止条件有问题,不能跳出循环,说明first永远不会为空。就说明没有有效的删除相应的结点。好吧!对了很久,也没想通为什么没删除!确实没删除,你就是完全按照单数输出的。
问题解决
出现未删除结点的情况,是因为helper.setNext(first);你的helper从来没有进过循环,是空的指针,要使其真正删除结点,必须把helper迭代进入循环才可以
教程代码:
public void run(int startNo,int countNum,int nums){
if(first == null||countNum < 1 || countNum > nums){
System.out.println("输入的数字有问题");
return;
}
//关于参数校验,想到了可能数的数有问题,但是没想到回事空链表
Boy helper = first;
//需求创建一个辅助指针helper,实现指向环形链表的最后的结点
//helper.setNext(first);我对单向链表的理解还是有问题的
while (true){
if(helper.getNext() == first){
break;
}
helper = helper.getNext();
}
//必须而且只能够通过这样的方式才能够获得最后一个指针的地址
//通过头指针只能向后看,而且必须看一圈,才能够看清最后一个是啥
//小孩报数前,先让first和helper移动到对应的位置
for(int j = 0;j < startNo - 1;j ++){
first = first.getNext();
helper = helper.getNext();
}
//当开始报数时,让first和helper指针同时移动m-1次,然后出圈
while (true){
if (helper == first){
break;
}
//判断条件说明仅仅只有一个结点,helper == first
for(int i = 0; i < countNum - 1; i++){
first = first.getNext();
helper = helper.getNext();
}
System.out.println(first);
first = first.getNext();
helper.setNext(first);
}
System.out.println(first);
}
}
总结与对比:
- 对于单向链表有一个错误的理解,仅仅把helper.next定位first找不到尾节点,只能一步一步遍历一遍才行
- 关于数字的校验,没想到会是空链表,仅仅想到了数字的范围
- 对于循环链表的理解不够,他不可能把自己变成空的,循环结束条件应该为两个指针重合
- 最重要的时对于题目的分析,真的是没有任何条理性和步骤性
兄弟水太浅,还是要多练!!!
代码改良:
public void run(int startNo, int countNum, int nums) {
//校验输入的数字
if(first == null || countNum < 0 || countNum > nums){
System.out.println("输入的数字有问题");
return;
}
Boy helper = new Boy();
helper.setNext(first);
for(int j = 0;j < startNo - 1;j ++){
first = first.getNext();
helper = helper.getNext();
}
while (true){
for(int i = 1; i < countNum; i++){
first = first.getNext();
helper = helper.getNext();
}
System.out.println(first);
first = first.getNext();
helper.setNext(first);
if (first == helper){
System.out.println(first);
break;
}
}
}
没必要让helper一开始就是尾结点,让他的下一个指针是头结点就行,随着第一次循环的开始,他就会进入循环链表,跟在first后面开始循环。如果出现未删除的指针,说明你的辅助指针没有进入循环链表,单个first是删除不了的。所以先把循环放在第一步。