Java单向环形链表解决约瑟夫环问题

单向环形链表解决约瑟夫环问题

约瑟夫环问题

  • 有1~n个人围坐成一个圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出圈,出圈的那个人的后一个人又重新从1开始报数,下一次数到m的那个人出圈,以此类推,直到所有的人都出圈为止,由此产生一个出圈的编号序列。

问题解决

思路

  • 用不带头节点的单向链表处理,构造一个有n个节点的单链表,然后让第n个节点的next指向第一个节点,从而构成一个单向循环链表。然后由第k个节点起从1开始计数,数到m时,对应的节点从链表中删除,然后再从被删除节点的下一个节点又开始从1计数,直到最后一个节点从链表中删除。
  • 创建环形单链表需要辅助变量first和current,创建环形单链表时,first恒定指向第一个节点,用于帮助最后一个节点指向first,以形成环,current则指向环的最后一个节点随着新节点插入环,current则指向新节点,保持着始终指向环形单链表的最后一个节点的状态。
  • 如创建好第一个节点时,就通过first让其next指向自己
    在这里插入图片描述
  • 创建第2个节点时,通过current令第一个节点的next指向第2个节点,然后令current指向第2个节点,通过current令第2个节点的next指向第一个节点,这样就完成了第2个节点的插入,并构成环。
    在这里插入图片描述
  • 后续的节点插入同理,直到插入完要求的节点数量。
  • 节点出圈除了辅助变量first,还要创建一个辅助变量helper帮助节点出圈。
  • 准备工作 helper在开始出圈前就指向环形单链表的最后一个节点,然后在报数出圈之前,先让first和helper移动k-1次,以便让first和helper分别指向目标节点和目标节点的前一个结点。
  • 开始报数时,就让first和helper同时移动m-1次,此时first指向的节点就是要出圈的节点,通过first得到当前节点的下一个节点,然后令first指向该节点,然后通过helper令出圈节点的前一个位置的节点的next指向first,这样就让出圈节点脱离了环形单链表,该出圈节点因为没有引用会被垃圾回收机制回收
  • 图示(以n=5,k=1,m=2的环形链表举例,展示出圈一个节点的步骤)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

代码

public class Node {
	private int no;
	private String data;
	private Node next;
	
	public Node(int no,String data) {
		this.no=no;
		this.data=data;
	}
	
	public Node() {
		
	}
	
	public int getNo() {
		return no;
	}

	public void setNo(int no) {
		this.no = no;
	}

	public String getData() {
		return data;
	}

	public void setData(String data) {
		this.data = data;
	}

	public Node getNext() {
		return next;
	}

	public void setNext(Node next) {
		this.next = next;
	}

	public String toString() {
		return "no:"+no+" data:"+data;
	}
}
public class Josephu {

	private Node first=null;//永远指向第一个节点,用于标识第一个节点,不能移动
	
	public void createCircle(int num) {
		if(num<2) {
			System.out.println("num请大于等于2");
			return;
		}
		Node current=null;//设置辅助变量用于增加节点,可移动
		for(int i=1;i<=num;i++) {
			Node node =new Node(i, ""+i);
			if(i==1) {//如果i==1说明是第一个节点,令辅助变量first指向该节点,并设置其下一个节点为其本身,形成环
				first=node;
				first.setNext(first);
				current=first;//令辅助变量current指向第一个节点
				//此时的环形单链表只有一个节点,因此自己指向自己
				//而current永远处于指向环形链表的最后一个节点,所以current指向第一个节点
			}else {
				current.setNext(node);//让当前节点的next指向新节点
				node.setNext(first);//让新节点的next指向第一个节点,形成环
				current=node;//令current变量指向当前最新的节点
			}
		}
	}
	
	public void show() {
		Node current=null;
		if(first==null) {
			System.out.println("链表为空");
			return;
		}
		while(true) {
			if(current==null) {//判断是否从第一个节点开始遍历
				current=first;//是,则令current指向第一个节点
			}
			System.out.println(current);//输出每个current目前指向的节点
			if(current.getNext()==first) {//如果当前节点的next指向了第一个节点,说明环形单链表到头了
				break;//跳出循环
			}
			current=current.getNext();//继续向下遍历
			
		}
	}
	
	public void count(int startNum,int countNum,int nodeNum) {
		if(first==null) {
			System.out.println("链表为空");
		}
		if(startNum<1||startNum>nodeNum) {
			System.out.println("参数错误请重新输入");
		}
		Node helper=first;
		while(true) {//让helper指向环形链表的最后一个节点
			if(helper.getNext()==first) {
				break;
			}
			helper=helper.getNext();
		}
		
		//让first和helper移动startNum-1次
		for(int i=0;i<startNum-1;i++) {
			first=first.getNext();
			helper=helper.getNext();
			
		}
		
		while(true) {
			for(int i=0;i<countNum-1;i++) {//让helper和first移动countNum-1次
				first=first.getNext();
				helper=helper.getNext();
			}
			System.out.printf("出圈的节点是%d \n",first.getNo());
			//输出出圈节点的序号
			first=first.getNext();//令first指向出圈节点的下一个位置的节点
			helper.setNext(first);
			//通过helper令出圈节点的前一个位置的节点的next指向
			//出圈节点的下一个位置的节点
			if(first==helper) {
				break;
			}
			//如果first和helper指向同一个节点,说明环形单链表只剩下一个节点了
		}
		System.out.printf("最后一个节点是%d\n",first.getNo());
		
	}
	
	
	public static void main(String[] args) {
		Josephu josephu=new Josephu();
		josephu.createCircle(5);
		josephu.show();
		
		josephu.count(1, 2, 5);
	}

}

运行结果

no:1 data:1
no:2 data:2
no:3 data:3
no:4 data:4
no:5 data:5
出圈的节点是2 
出圈的节点是4 
出圈的节点是1 
出圈的节点是5 
最后一个节点是3
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值