约瑟夫问题(单向循环链表)---Java数据结构与算法

约瑟夫问题,有时也称为约瑟夫斯置换,是一个出现在计算机科学和数学中的问题。在计算机编程的算法中,类似问题又称为约瑟夫环。又称“丢手绢问题”.
假设有num个小孩在玩丢手绢,现在从第n个小孩开始数数,数到第m个数,把这个数删除,接着从这个被删除的数开始数m个,在删除,再数…
例如:有五个孩子在玩丢手绢,从第二个小孩开始数,数2次,那么会得到一个出圈顺序:3-5-2-1-4
解决约瑟夫问题,我们正好可以用环形链表

相比于普通的单向链表,单向环形链表是把最后一个结点又指向了第一个结点。
约瑟夫问题(单向循环链表)---Java数据结构与算法_第1张图片
因此我们可以先构建一个结点类:

class Boy{
	private int no;//编号
	private Boy next;//指向下一个结点
	public Boy(int no) {
		this.no=no;
	}
	/**
	 * @return the no
	 */
	public int getNo() {
		return no;
	}
	/**
	 * @param no the no to set
	 */
	public void setNo(int no) {
		this.no = no;
	}
	/**
	 * @return the next
	 */
	public Boy getNext() {
		return next;
	}
	/**
	 * @param next the next to set
	 */
	public void setNext(Boy next) {
		this.next = next;
	}
	

向环形链表中添加结点

  1. 我们首先需要定义一个指向第一个结点的next,再定义一个辅助指针curboy
  2. 当链表内只有一个结点时,让first指向这个结点,让这个结点自己形成环,让这个辅助指针指向这个结点
    约瑟夫问题(单向循环链表)---Java数据结构与算法_第2张图片
  3. 再加入结点时,先把curboy的next指向新的结点,接着把新的结点指向first,最后把curboy指向新的结点
    约瑟夫问题(单向循环链表)---Java数据结构与算法_第3张图片
	//先创建一个first结点
	private Boy first = null;
	//添加小孩结点,构建一个环形链表
	public void addBoy(int nums) {//向连表中添加nums个结点
		//对nums做一个数据校验
		if(nums<1) {
			System.out.println("nums的值不符合情况");
			return;
		}
		Boy curboy = null;//作为辅助指针,帮助构建环形链表
		//利用循环来创建环形链表
		for(int i=1;i<=nums;i++) {
			//根据编号创建小孩结点
			Boy boy = new Boy(i);
			//如果是第一个小孩
			if(i==1) {
				first = boy;
				first.setNext(first);//构成一个环状
				curboy = first;//让curboy指向第一个小孩
			}else {
				curboy.setNext(boy);//先让curboy指向新的结点,即把当前的结点与新加入的结点连接起来
				boy.setNext(first);//再把新的结点指向first
				curboy=boy;//把curboy后移,指向这个新的结点
				
			}
			
		}
	}

遍历环形链表

  1. 因为first不能动,所以创建一个辅助指针cur
  2. 利用循环进行遍历,当cur.getNext()==first时,遍历完毕
//遍历环形链表
	public void showboy() {
		//判断链表是否为空
		if(first == null) {
			System.out.println("链表为空");
			return;
		}
		//因为first不能动,因此使用一个辅助指针cur
		Boy cur = first;
		while(true) {
			System.out.printf("小孩的编号%d\n",cur.getNo());
			if(cur.getNext()==first) {//此时已经遍历完毕
				break;
			}
			cur = cur.getNext();
		}
	}

得到一个出圈顺序

  1. 创建一个方法,传入三个参数n,m,num 其中n表示从第几个小孩开始数,m表示要数几个数,num表示共有多少个小孩
  2. 出圈即意味着删除结点,那么我们可以借用单链表删除结点的方法,定义一个辅助指针helper用于表示要删除结点的上一个结点。
  3. 初始时我们把helper指向环形链表的最后一个结点
    约瑟夫问题(单向循环链表)---Java数据结构与算法_第4张图片
  4. 在小孩报数前,我们先把helper和first移动到开始报数的位置。例如:开始报数的位置为2 那么示意图如下
    约瑟夫问题(单向循环链表)---Java数据结构与算法_第5张图片
    其中first移动到了开始报数的第二个结点,helper移动到了该结点的前一个结点1. 他们分别移动了2-1=1次.
  5. 接着小孩开始报数,如果需要报m个数,那么first和helper分别移动m-1次,移动相应次数后,把该结点出圈(即删除该结点),当helper==first时,说明只剩下一个结点,返回。例如:从第二个结点开始数,数2个数,即m=2.那么出圈顺序为3 5 2 1 4。
    先移动first和helper到相应位置,这里从第二个数开始数,数2个数,即从2开始,数2个数后(包括自己)为3.
    约瑟夫问题(单向循环链表)---Java数据结构与算法_第6张图片
    接着把first向后移,再把helper指向first,即代表原本first对应的结点位置没有加入链表,在java中会被当做垃圾回收。
    约瑟夫问题(单向循环链表)---Java数据结构与算法_第7张图片
    循环这个过程即可得到一个出圈顺序
	//根据用户的输入,返回一个出圈顺序
	public void outcircle(int m,int n,int num) {//其中m表示要数几下,n表示从第几个小孩开始数,num表示有几个小孩
		if(first == null || n<1 || n>num) {
			System.out.println("参数输入有误,请重新输入");
			return;
		}
		//创建一个辅助指针,帮助完成小孩出圈
		Boy helper = first;
		//先把helper指向环形链表的最后一个结点。(因为单链表要删除一个结点,需要指向这个结点的前一个结点,那么这里初始默认是最后一个结点)
		while(true) {
			if(helper.getNext()==first) {//说明helper指向了最后一个结点
				break;
			}
			helper = helper.getNext();
		}
		//小孩报数前,先把first与helper移动到开始报数的位置,即移动n-1次
		for(int i=0;i<n-1;i++) {
			first=first.getNext();
			helper=helper.getNext();
		}
		//小孩报数时,让first与helper指针同时移动m-1次,然后出圈
		//一个循环操作,直到圈中只要一个结点
		while(true) {
			if(helper == first) {//此时说明圈中只有一个结点
				break;
			}
			//让first与helper指针同时移动m-1次
			for(int j=0;j<m-1;j++) {
				first=first.getNext();//此时first指向的结点就是要出圈的小孩结点
				helper=helper.getNext();
			}
			System.out.printf("小孩%d出圈\n",first.getNo());
			//此时将first指向的小孩结点出圈
			first = first.getNext();
			helper.setNext(first);
		}
		System.out.printf("最后留在圈中的小孩编号是%d\n",first.getNo());
	}

总源码:

package Linkedlist;

public class Josephu {

	public static void main(String[] args) {
		CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
		circleSingleLinkedList.addBoy(5);
		circleSingleLinkedList.showboy();
		
		circleSingleLinkedList.outcircle(2, 2, 5);
	}

}

//创建一个环形的单向链表
class CircleSingleLinkedList{
	//先创建一个first结点
	private Boy first = null;
	//添加小孩结点,构建一个环形链表
	public void addBoy(int nums) {//向连表中添加nums个结点
		//对nums做一个数据校验
		if(nums<1) {
			System.out.println("nums的值不符合情况");
			return;
		}
		Boy curboy = null;//作为辅助指针,帮助构建环形链表
		//利用循环来创建环形链表
		for(int i=1;i<=nums;i++) {
			//根据编号创建小孩结点
			Boy boy = new Boy(i);
			//如果是第一个小孩
			if(i==1) {
				first = boy;
				first.setNext(first);//构成一个环状
				curboy = first;//让curboy指向第一个小孩
			}else {
				curboy.setNext(boy);//先让curboy指向新的结点,即把当前的结点与新加入的结点连接起来
				boy.setNext(first);//再把新的结点指向first
				curboy=boy;//把curboy后移,指向这个新的结点
				
			}
			
		}
	}
	
	//遍历环形链表
	public void showboy() {
		//判断链表是否为空
		if(first == null) {
			System.out.println("链表为空");
			return;
		}
		//因为first不能动,因此使用一个辅助指针cur
		Boy cur = first;
		while(true) {
			System.out.printf("小孩的编号%d\n",cur.getNo());
			if(cur.getNext()==first) {//此时已经遍历完毕
				break;
			}
			cur = cur.getNext();
		}
	}
	
	//根据用户的输入,返回一个出圈顺序
	public void outcircle(int m,int n,int num) {//其中m表示要数几下,n表示从第几个小孩开始数,num表示有几个小孩
		if(first == null || n<1 || n>num) {
			System.out.println("参数输入有误,请重新输入");
			return;
		}
		//创建一个辅助指针,帮助完成小孩出圈
		Boy helper = first;
		//先把helper指向环形链表的最后一个结点。(因为单链表要删除一个结点,需要指向这个结点的前一个结点,那么这里初始默认是最后一个结点)
		while(true) {
			if(helper.getNext()==first) {//说明helper指向了最后一个结点
				break;
			}
			helper = helper.getNext();
		}
		//小孩报数前,先把first与helper移动到开始报数的位置,即移动n-1次
		for(int i=0;i<n-1;i++) {
			first=first.getNext();
			helper=helper.getNext();
		}
		//小孩报数时,让first与helper指针同时移动m-1次,然后出圈
		//一个循环操作,直到圈中只要一个结点
		while(true) {
			if(helper == first) {//此时说明圈中只有一个结点
				break;
			}
			//让first与helper指针同时移动m-1次
			for(int j=0;j<m-1;j++) {
				first=first.getNext();//此时first指向的结点就是要出圈的小孩结点
				helper=helper.getNext();
			}
			System.out.printf("小孩%d出圈\n",first.getNo());
			//此时将first指向的小孩结点出圈
			first = first.getNext();
			helper.setNext(first);
		}
		System.out.printf("最后留在圈中的小孩编号是%d\n",first.getNo());
	}
}

//先创建一个结点
class Boy{
	private int no;//编号
	private Boy next;//指向下一个结点
	public Boy(int no) {
		this.no=no;
	}
	/**
	 * @return the no
	 */
	public int getNo() {
		return no;
	}
	/**
	 * @param no the no to set
	 */
	public void setNo(int no) {
		this.no = no;
	}
	/**
	 * @return the next
	 */
	public Boy getNext() {
		return next;
	}
	/**
	 * @param next the next to set
	 */
	public void setNext(Boy next) {
		this.next = next;
	}
	
	
}

你可能感兴趣的:(Java数据结构与算法)