2.3 单向环形链表实现及约瑟夫问题

2.3 单向环形链表实现及约瑟夫问题

单向环形链表

  1. 可以带头节点,也可以不带头节点。
  2. 虽然环形链表是一个环,但在实现过程中,需要确定第一个数据节点,在单向链表的基础上,使最后一个节点指向第一个数据节点,就构成了单向环形链表。

约瑟夫问题

Josephu(约瑟夫)问题:
      设编号为1、2、…、n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。

代码实现

  1. 链表实现
    (1)CircleSingleLinkedList():空的单向环形链表构造方法。
    (2)CircleSingleLinkedList(int num):构造一条节点个数为num的单向环形链表。链表的第一个节点编号(no)为1,按照节点指向编号不断增加1,最后一个节点的编号为num。
    (3)void printList():打印环形链表。
    (4)void add(Node3 node):向环形链表最后添加一个节点。环形链表中 相对于第一个节点而言的最后一个节点 后面添加一个节点。
  2. 约瑟夫问题解决思路(方法一)
    (1)创建一个辅助指针(变量)helper,事先让其指向环形链表的最后一个节点。
    (2)开始报数前,让firstNode(指向链表中第一个节点的变量)和helper向后移动k-1次,使firstNode指向第一个报数的节点(把节点当作报数的人)。
    (3)模拟报数过程,让firstNode和helper向后移动m-1次。此时firstNode指向的节点就是应该出列的节点。
    (4)将firstNode指向的节点从链表中删除。
    firstNode = firstNode.next;helper.next = firstNode;
    (5)然后转到第(3)步,循环执行,知道链表中只剩下一个节点。
  3. 约瑟夫问题解决思路(方法二)
    定位到要出列的节点的前一个节点,让此节点指向出列节点的下一个节点,这样出列节点就被从链表中删除了。
  4. 代码
/**
 * 实现环形单链表,并使用环形单链表解决约瑟夫问题
 * @author dxt
 *
 */
public class CircleSingleLinkedListDemo {
	public static void main(String[] args) {
		CircleSingleLinkedList csll = new CircleSingleLinkedList(5);
		csll.printList();
		csll.josepfu(1, 2, 5);
		System.out.println("----------哈哈分割线-----------");
		CircleSingleLinkedList csll1 = new CircleSingleLinkedList(5);
		csll1.josephu2(1, 2);
		System.out.println("***********美丽分割线**********");
		CircleSingleLinkedList csll2 = new CircleSingleLinkedList();
		Node3 n1 = new Node3(1);
		Node3 n2 = new Node3(2);
		Node3 n3 = new Node3(3);
		csll2.add(n1);
		csll2.add(n2);
		csll2.add(n3);
		csll2.printList();
	}
}
/**
 * 节点类
 * @author dxt
 *
 */
class Node3{
	private int no;	//节点编号
	private Node3 next;	//指向下一节点的指针
	
	//构造器
	public Node3(int no) {
		this.no = no;
	}
	//javaBean, id用不到,就不再写了
	public void setNo(int no) {
		this.no = no;
	}
	public int getNo() {
		return this.no;
	}
	public void setNext(Node3 node) {
		this.next = node;
	}
	public Node3 getNext() {
		return this.next;
	}
}
/**
 * 环状单链表实现类,没有头节点
 * @author dxt
 *
 */
class CircleSingleLinkedList{
	private Node3 firstNode = null;	//虽然是环状链表,但依然将第一个加入到链表中的节点作为第一个节点
	//构造器
	public CircleSingleLinkedList(){
	}
	/**
	 * 构造一条节点数为num个的环状单向链表,其中第一个节点id为1,之后依次递加,最后一个节点为num
	 * @param num
	 */
	public CircleSingleLinkedList(int num) {
		//1. 先对num做一个校验,链表中至少有一个节点
		if(num < 1) {
			System.out.println("节点数至少为1个。输入错误,无法创建链表。");
			return;
		}
		//2. 创建链表
		Node3 curNode = null;	//辅助节点,定位到链表中最后一个节点
		for(int i=1; i<=num; i++) {
			Node3 tempNode = new Node3(i);	//声明一个序号为i的节点
			if(i == 1) {	//是链表中第一个节点
				firstNode = tempNode;
				firstNode.setNext(firstNode); 	//此时环形链表中只有一个节点,自己构成一个环
				curNode = firstNode;	//定位到链表中最后一个节点
			}else {
				curNode.setNext(tempNode);	//链接到新的节点
				tempNode.setNext(firstNode);	//构成换
				curNode = tempNode;	//定位到链表中最后一个节点
			}
		}
	}
	/**
	 * 遍历输出环形链表
	 */
	public void printList() {
		//1. 判断链表是否为空
		if(firstNode == null) {
			System.out.println("链表为空。");
			return;
		}
		//2. 遍历
		Node3 temp = firstNode;
		while(true) {
			System.out.println("节点的编号为:" + temp.getNo());
			if(temp.getNext() == firstNode) {	//遍历完成,退出
				break;
			}
			temp = temp.getNext();
		}
	}
	/**
	 * 向链表尾添加一个节点
	 * @param no
	 */
	public void add(Node3 node) {
		//当链表为空时
		if(firstNode == null) {
			firstNode = node;
			firstNode.setNext(firstNode);
			
		}else {	//当链表不为空时
			Node3 temp = firstNode;
			while(true) {
				if(temp.getNext() == firstNode) {
					break;
				}
				temp = temp.getNext();
			}
			node.setNext(firstNode); 	//添加到最后,那么它肯定指向第一个节点
			temp.setNext(node);		//将节点添加到链表中
		}
	}
	/**
	 * 约瑟夫问题,解决方法是使用两个指针
	 * @param startNo	表示从第几个小孩开始数数,startNo为开始数数小孩的id(也是默认的排列序号)
	 * @param countNum	表示数几下
	 * @param nums	表示最初有多少个小孩在圈中
	 */
	public void josepfu(int startNo, int countNum, int nums) {
		//1. 先进行数据校验
		if(firstNode == null || startNo < 1 || startNo > nums) {
			System.out.println("参数输入错误,请重新输入。");
			return;
		}
		//2. 创建一个辅助指针,帮助完成节点出圈
		Node3 helper = firstNode;
		//2.1 先使helper指针指向最后一个节点
		while(true) {
			if(helper.getNext() == firstNode) {
				break;
			}
			helper = helper.getNext();
		}
		//2.2 定位到第一个报数的节点, 移动startNo-1次(第二个节点开始报,则只需firstNode向后移动一次即可)
		for(int i=0; i<startNo-1; i++) {
			firstNode = firstNode.getNext();	//此方法会改变链表结构,所以直接使用firstNode节点
			helper = helper.getNext();
		}
		//2.3 报数时,helper和firstNode节点都向后移动countNum-1次,然后从链表中离开,知道链表中只有一个节点
		while(true) {
			if(helper == firstNode) {	//圈中只剩一个节点
				System.out.println("出圈:"+firstNode.getNo());	//最后一个节点出链表
				break;
			}
			//helper和firstNode节点都向后移动countNum-1次
			for(int i=0; i<countNum-1; i++) {
				firstNode = firstNode.getNext();
				helper = helper.getNext();
			}
			//打印要出圈的节点
			System.out.println("出圈:" + firstNode.getNo());
			//从链表中删除 出圈的 节点(即firstNode指向的节点)
			firstNode = firstNode.getNext();
			helper.setNext(firstNode);
		}
	}
	/**
	 * 第二种约瑟夫问题解决方法,感觉 效率要比第一种约瑟夫问题解决方法要高。
	 * @param startNo
	 * @param countNum
	 */
	public void josephu2(int startNo, int countNum) {
		//1. 先进行数据校验
		if(firstNode == null || startNo < 1) {
			System.out.println("参数输入错误,请重新输入。");
			return;
		}		
		//2. 使firstNode定位到开始报数的节点
		for(int i=0; i<startNo-1; i++) {
			firstNode = firstNode.getNext();
		}
		//3. 模拟报数
		//3.1 当countNum=1时
		if(countNum == 1) {	//如果countNum=1,则直接报数,不从链表中删除报数的节点
			Node3 temp = firstNode;
			while(true) {
				System.out.println("出圈:" + temp.getNo());
				if(temp.getNext() == firstNode) {
					break;
				}
				temp = temp.getNext();	//只是后移,不需要从链表中删除
			}
			return;	//返回
		}
		//3.2 当countNum>1时,此时需要从链表中删除节点
		while(true) {
			//当链表中只剩一个节点
			if(firstNode.getNext() == firstNode) {	//链表中只剩一个节点
				System.out.println("出圈:" + firstNode.getNo());	//打印最后一个节点编号
				break;
			}
			//模拟报数
			for(int i=0; i<countNum-2; i++) {
				firstNode = firstNode.getNext();
			}
			//打印firstNode.next节点的no
			System.out.println("出圈:"+firstNode.getNext().getNo());
			//删除firstNode指向的下一个元素
			firstNode.setNext(firstNode.getNext().getNext());
			firstNode = firstNode.getNext();
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值