【数据结构和算法分析】循环链表及约瑟夫问题

循环链表及约瑟夫问题


循环链表:

                循环链表可以为单链表,也可以为双链表,但我不想把问题搞得那么复杂,姑且就做单链表的循环形式吧。

                我们在实现了链表后,必然会提出一个问题:链表能不能首尾相连?怎样实现?

                答案:能。其实实现的方法很简单,就是将表中最后一个结点的指针域指向头结点即可(P->next = head;)。这种形成环路的链表称为循环链表。

代码如下:

public class MyCircleList {
	
	Node headNode;
	Node tailNode;
	int length;
	
	public MyCircleList(Node node){
		this.headNode = node;
		this.tailNode = node;
		length = 1;
	}
	/*
	 * 默认在尾结点插入
	 */
	public void insert(Node node){
		insert(node, length);
	}
	/*
	 * @param Node node 插入的结点 , int index 结点插入的位置
	 */
	public void insert(Node node,int index){
		if (index < 0 || index > length) {
			throw new IndexOutOfBoundsException();
		}
		if (index == 0) {
			this.tailNode.next = node;
			node.next = this.headNode;
			headNode = node;
		}else if (index == length) {
			tailNode.next = node;
			tailNode = node;
			node.next = this.headNode;
		}else {
			Node otherNode = this.headNode;
			while (index > 1) {
				otherNode = otherNode.next;
				index--;
			}
			node.next = otherNode.next;
			otherNode.next = node;		
		}
		length++;
	}
	/*
	 * @param int index 删除结点的位置
	 */
	public void delete(int index){
		if (index < 0 || index >= length) {
			throw new IndexOutOfBoundsException();
		}
		if (index == 0 && length == 1) {
			tailNode = headNode = null;
		}else if (index == 0 && length != 1) {
			tailNode.next = headNode.next;
			headNode = headNode.next;
		}else {
			Node otherNode = this.headNode;
			while (index > 1) {
				otherNode = otherNode.next;
				index--;
			}
			otherNode.next = otherNode.next.next;
		}
		length--;
	}
	
	/*
	 * @param int index 查看  index 处的 data
	 */
    public Object getData(int index){
    	if (index < 0 || index >= length) {
			throw new IndexOutOfBoundsException();
		}
    	Node otherNode = headNode;
    	for (int i = 0; i < index; i++) {
			otherNode = otherNode.next;
		}
    	return otherNode.data;
    }
    /*
     * @param int index 修改的 data 的位置 ,Object data 所需修改的 data
     */
    public void setData(int index,Object data){
    	if (index < 0 || index >= length) {
			throw new IndexOutOfBoundsException();
		}
    	Node otherNode = headNode;
    	for (int i = 0; i < index; i++) {
			otherNode = otherNode.next;
		}
    	otherNode.data = data;
    }
	public void print(){
		Node otherNode = this.headNode;
		for (int i = 0; i < length; i++) {
			System.out.print(otherNode.data+" ");
			otherNode = otherNode.next;
		}
		System.out.println();
	}

	
}

class Node{
	
	/*
	 * 节点类,包括结点的值 data 和指向下一结点的指针 next,单链表适用
	 */
	
	Object data;
	Node next;
	
	public Node(){
		data = null;
		next = null;
	}
	
	public Node(Object data){
		this.data = data;
		this.next = null;
	}
	
	public Node(Object data,Node nextnode){
		this.data = data;
		this.next = nextnode;
	}
	
	/*
	 * 判断有无下一结点
	 * @return boolean 若true,则该结点有下一结点
	 */
	public boolean hasnext(){
		return this.next != null;
	}
	
	
}

约瑟夫问题:

约瑟夫问题几乎是最经典的用来讲解循环链表的案例了。为什么呢?我们来看看这个问题的描述就会明白了:

有一队由n个冒险家组成的探险队深入到热带雨林中,但他们遭遇到了食人族,食人族的游戏规则是让他们围成一圈,然后选定一个数字m,从第1个人开始报数,报到m时,这个人就要被吃掉了,然后从下一个人开始又重新从1报数,重复这个过程,直到剩下最后一个人,这个人是幸运者,可以离开而不被吃掉。那么问题是,谁是这个幸运者呢?

我们来举个例子:

假设这个探险队有6个探险家,食人族选定的数字m是5,那么在第一轮中,5号会被吃掉,剩下的就是:1, 2, 3, 4, 6总共5个人,然后从6号开始,重新从1开始报5个数:6, 1, 2, 3, 4,所以在第二轮里面被吃掉的就是4号……一直重复这个过程,按顺序应该是:5, 4, 6, 2, 3被吃掉,剩下1号活下来。

解决这个问题并不是只能用循环链表的,但使用循环链表应该是最方便的。我写的代码如下:

public class Josephus {
	
	public static final int N = 6;    //代表 6 位人数
	public static final int M = 5;    //选定的数字为 5

	public static void main(String[] args){
		
		//初始化循环链表,构造出来为 1,2,3,4,5,6的链表分别代表六个人
		MyCircleList myCircleList = new MyCircleList(new Node(1));
		for (int i = 2; i <= N; i++) {
			myCircleList.insert(new Node(i));
		}
	
		
		Node pre = null;
		Node p = myCircleList.headNode;
		for (int i = 0; i < N-1; i++) {     //需要遍历的轮次,执行 N-1 次
			for (int j = 1; j < M; j++) {    //每次遍历的报数,数 M-1 人
				pre = p;
				p = p.next;
			}
	                System.out.println("出局的是:" + p.data);   //输出
 	                pre.next = p.next;          //删除一个结点
                        p = pre.next;	
 		}
 	}
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值