欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【Java数据结构】Java环形链表实现约瑟夫(Josephus)问题

程序员文章站 2022-05-26 12:01:08
...

约瑟夫问题

约瑟夫问题(有时也称为约瑟夫斯置换,是一个出现在计算机科学和数学中的问题。在计算机编程的算法中,类似问题又称为约瑟夫环。又称“丢手绢问题”.)详见百度百科

“丢手绢”游戏模拟约瑟夫问题

这里用小孩子们玩的“丢手绢”游戏来说明和演示约瑟夫问题:n个小孩围成一圈,从第k个小孩开始从1报数,数到m的小孩时将手绢丢给他,拿到手绢的小孩自动出列,然后再让出列的小孩的下一个小孩开始从1报数,数到m的小孩,将手绢丢给他,这个小孩自动出列,以此类推,直到最后剩下的一个小孩成为赢家。例如k = 4,n=6,m=2,出列的顺序是:5,1,3,6,4,剩下的小孩为2。
分析:
(1)将小孩子们组成的圈作为一个环形链表;
(2)小孩出列的动作看作是从链表中删掉一个节点;
(3)模拟出列过程,直到链表中剩下一个节点为止。

小孩节点实体类
package com.jpc.linkedlist.four;

public class Boy {
	
	private int id;
	private Boy next;
	
	public Boy(int id) {
		this.id = id;
	}

	//此处省略get/set方法

	@Override
	public String toString() {
		return "小孩编号 " + this.getId();
	}
	
}

环形链表

环形链表即首尾相连的链表,最后一个节点的next域指向第一个节点,该链表可以是单向链表也可以是双向链表,由于其基本思路大体一致,这里以单向环形链表来说明。

增:环形链表添加数据(按添加顺序存储)

1、如果是第一个节点,就将该节点赋值给first变量(用于表示第一个添加进链表的节点数据),并且将first赋值给first自身的next变量;
2、定义一个变量temp指向链表的第一个节点first;
3、对于非首个节点,将该节点赋值给temp的next变量,并且将first节点的内存地址赋值给该节点的next变量,以形成首尾相连。

删:按照节点的编号删除

1、定义一个temp变量指向first节点;
2、定义一个标记flag用于标记是否找到了指定编号的节点;
3、用temp遍历链表节点,每次将temp指向下一个节点,直到找到temp.next.id与指定编号相同的节点或遍历结束没有找到,如果找到就将flag变为true;
4、如果flag为true,就判断temp.next是否与first相等,如果相等就表明要删除的节点是第一个,因此把temp.next.next赋值给first,然后将first赋值给temp的next变量,保证首尾相连;
5、如果temp.next与first不相等,就表明不是要删除第一个,那么此时只需要将temp.next.next赋值给temp.next即可;
6、为了解决约瑟夫问题,本例的删除方法将返回一个被删除节点的下一个节点数据,即temp.next。

改:可以参考上一篇博客上上一篇博客

查:根据编号查找

1、定义一个temp变量指向first节点;
2、定义一个标记flag用于标记是否找到了指定编号的节点;
3、用temp遍历链表节点,每次将temp指向下一个节点,直到找到temp.next.id与指定编号相同的节点或遍历结束没有找到,如果找到就将flag变为true;
4、如果为true就返回temp.next。

查:遍历

1、定义一个temp变量指向first节点;
2、每次将temp输出后,将其指向下一个节点,如果下一个节点是first就结束遍历。

代码实现单向环形链表模拟“丢手绢”游戏(outList():约瑟夫问题)
package com.jpc.linkedlist.four;

public class CircleSingleLinkedList {
	
	private Boy first;

	//一次性创建多个小孩节点,形成单向环形链表
	public void addBoys(int nums) {
		//校验用户输入数据的合法性
		if(nums < 1) {
			System.out.println("输入的数据'" + nums + "'非法");
			return;
		}
		Boy temp = null;
		for(int i = 1; i <= nums; i ++) {
			Boy boy = new Boy(i);
			if(i == 1) {
				first = boy;
				first.setNext(first);
				temp = first;
			} else {
				temp.setNext(boy);
				boy.setNext(first);
				temp = temp.getNext();
			}
		}
	}
	
	//遍历环形链表
	public void list() {
		if(first == null) {
			System.out.println("链表为空!");
			return;
		}
		Boy temp = first;
		while(true) {
			System.out.println(temp);
			if(temp.getNext() == first) {
				break;
			}
			temp = temp.getNext();
		}
	}
	
	//根据编号,查找对应Boy
	public Boy select(int id) {
		//校验用户输入数据的合法性
		if(id>length() || id<1) {
			System.out.println("输入的数据非法!");
			return null;
		}
		Boy boy = null;
		Boy temp = first;
		do{
			if(temp.getId() == id) {
				boy = temp;
				break;
			}
			temp = temp.getNext();
		}while(temp != first);
		return boy;
	}
	
	//查询节点个数
	public int length() {
		int sum = 0;
		Boy temp = first;
		if(temp==null) {
			System.out.println("链表为空!");
			return sum;
		}
		do{
			sum ++;
			temp = temp.getNext();
		}while(temp != first);
		return sum;
	}
	
	//根据用户输入,生成一个出队顺序(模拟约瑟夫问题的核心)
	public void outList(int startId, int step) {
		//校验用户输入数据的合法性
		if(startId>length() || startId<1 || step<1) {
			System.out.println("输入的数据非法!");
			return;
		}
		//从用户指定位置的小孩节点开始报数
		Boy temp = select(startId);
		int length = length();
		//循环节点长度-1次,最后要留下一个小孩作为胜利者存在
		for (int j = 1; j < length; j ++) {
			//开始报数,找到报数为指定step的小孩节点的上一个小孩节点temp
			for (int i = 1; i < step - 1; i++) {
				temp = temp.getNext();
			}
			//打印出将要出列的小孩节点
			System.out.println(temp.getNext());
			//将目标节点删除,并且返回目标节点的下一个节点
			temp = delete(temp.getNext().getId());
		}
		System.out.println("胜利的小孩为:"+first);
	}
	
	//删除节点数据
	public Boy delete(int id) {
		Boy temp = this.first;
		boolean flag = false;
		//本例情况特殊,不存在找不到id的情况,因此这里不再考虑
		while(true) {
			if(temp.getNext().getId() == id) {
				flag = true;
				break;
			}
			temp = temp.getNext();
		}
		//如果找到了指定的小孩节点,要分情况处理
		if(flag) {
			if(temp.getNext() == first) {
				first = temp.getNext().getNext();
				temp.setNext(first);
			}else {
				temp.setNext(temp.getNext().getNext());				
			}
		}else {
			System.out.println("要删除的小孩" + id + "不存在!");
		}
		return temp.getNext();
	}
}

测试类

package com.jpc.linkedlist.four;

public class Test {

	public static void main(String[] args) {
		
		CircleSingleLinkedList list = new CircleSingleLinkedList();
		list.addBoys(6);
		System.out.println("参加游戏的小孩有以下几个:");
		list.list();
		System.out.println("出列顺序与胜利者如下:");
		list.outList(4, 2);
	}
}

运行测试类结果如下
【Java数据结构】Java环形链表实现约瑟夫(Josephus)问题