Java数组模拟链表解决约瑟夫问题

本篇文章参考《啊哈!算法》

一、如何模拟?

链表的每个结点中只包含一个指针域,叫做单链表,即构成链表的每个结点只有一个指向直接后继结点的指针

每个节点的结构

对于链表中的每个结点,需要完成两个部分。

1、如何存储当前节点数据。2、如何指向下一个结点

我们可以用数组来存储每个结点的数据。但是Java中没有指针,所以指向下一个结点就无法借助指针来完成,换句话说我们没有办法知道当前结点的右边的(后面的)结点是谁,这个问题该如何解决呢?经过思考之后,我们决定用一个数组right来存放每个结点的右边的结点是谁就可以。


上图当中,data数组用来存放数据,right数组用来存放当前链表中每个结点的右边的结点的数据在data中的位置。

代码:

static int[] data = new int[105];
static int[] right = new int[105];

当我们需要操作时,只需修改data和right数组的值即可。

所有的数据从下标为1开始,data[0]和right[0]表示头结点。上图的data[0]只是模拟,实际上不处理数据。

最后一个结点的right[i]的值设定为-1,表示结束。


二、插入操作

假设链表是从小到大排序的。现在需要插入一个数temp。

完成插入操作,总共分为三种情况

1、temp应该插入链表中间

遍历链表,定义一个变量t=0,当right[t]的值不为-1时,就表示t不是链表中的最后一个元素,求出t的下一个结点的值,让其与temp比较,如果大于temp,则将temp插入当前结点的后面(因为我们之前假定链表是从小到大排序的),修改对应的值,将temp的right值改为当前节点的right值,然后当前结点的right值改为temp。

<pre name="code" class="java">		int t = 0;
		while(right[t] != -1)
		{
			int nowRight = data[right[t]];

			if(nowRight > temp)
			{
				data[n + 1] = temp;
				right[n + 1] = right[t];
				right[t] = n + 1;
				break;
			}
			
			t = right[t];
		}


 

2、emp应该插入链表首元结点之前

因为我们在此使用了头结点,并且每次都是从头结点开始遍历,所以方法与情况1相同。

3、emp应该插入链表末尾

我们定义了一个flag,在遍历链表的过程中,如果找到了合适的插入位置,则修改flag的值,如果遍历结束,flag的值仍然没有被修改,则证明temp应该插在链表尾部。那我们就直接对尾元结点进行修改

		if(flag != 1)
		{
			data[n + 1] = temp;
			right[n] = n + 1;
			right[n + 1] = -1;
		}

三、遍历输出

完成插入操作后遍历链表并且输出

</pre><p>注意此时的遍历与插入数据时的遍历不一样,插入数据时每次都会寻找当前结点的下一结点,所以没有遍历到尾元结点,但是输出时是需要遍历到尾元结点的。</p><p><pre name="code" class="java">		while(t != -1)
		{
			if(t > 0)
				System.out.print(data[t] + " ");
			t = right[t];
		}

以上过程的整体代码如下:

import java.util.Scanner;

public class ArrayLinkList 
{
	static int[] data = new int[105];
	static int[] right = new int[105];
	
	public static void main(String[] args) 
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		for(int i = 1;i <= n;i++)
			data[i] = sc.nextInt();
		int temp = sc.nextInt();
		sc.close();

		for(int i = 0;i <= n;i++)
		{
			if(i != n)
				right[i] = i + 1;
			else
				right[i] = -1;
		}

		int t = 0,flag = 0;
		while(right[t] != -1)
		{
			int nowRight = data[right[t]];

			if(nowRight > temp)
			{
				data[n + 1] = temp;
				right[n + 1] = right[t];
				right[t] = n + 1;
				flag = 1;
				break;
			}
			
			t = right[t];
		}
		
		if(flag != 1)
		{
			data[n + 1] = temp;
			right[n] = n + 1;
			right[n + 1] = -1;
		}
		
		t = 0;
		
		while(t != -1)
		{
			if(t > 0)
				System.out.print(data[t] + " ");
			t = right[t];
		}
	}
}

四、解决约瑟夫问题

现在有n个人,围成一圈,从第一个人开始,轮流报数,当某个人报到数是m的倍数的时候,那个人就会被杀死。现在给出n,m的值,问最后哪个人可以活下来?

1、构造循环链表

上个问题所形成的是单链表如果要构造一个循环链表,那么将尾元结点的right值变成首元结点的位置,例如:上一个例子,将原来right[4]=-1改成right[4]=1

所以我们根据n的值,可以构造出一个循环链表

		int i,j;
		for(i = 0;i <= n;i++)
		{	
			data[i] = i;
			right[i] = i + 1;
		}
		
		right[n] = 1;

2、遍历链表并且杀死符合条件的人

定义变量temp,表示现在是第几个人在报数,index表示进行了几次游戏,因为有n个人,所以要进行n-1次

这次我们不从头结点开始遍历,因为是报数,加入头结点,不利于我们计算报数过程。

当有个人报到m的倍数的时候,我们就去寻找这个人的前一个人是谁,在链表中就是寻找左边的结点。

	private static int findLeftNode(int temp,int[] right,int n) 
	{
		for(int i = 1;i <= n;i++)
			if(right[i] == temp)
				return i;
		return -1;
	}

找到之后就修改对应结点的值,将left的right值改为temp的right值,修改temp的right和data值为-1,然后temp继续后移一个(因为原来的temp的right值已经被修改)。

			int left = findLeftNode(temp,right,n);
			right[left] = right[temp];
			data[temp] = -1;
			
			int save = temp;
			temp = right[temp];
			right[save] = -1;
每次杀死一个人,就输出一次结果

完整过程代码

import java.util.*;  

public class Main 
{  

	public static void main(String[] args) 
	{
		int[] data = new int[115];
		int[] right = new int[115];
		int n,m;
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();
		sc.close();
		int i,j;
		for(i = 0;i <= n;i++)
		{	
			data[i] = i;
			right[i] = i + 1;
		}
		
		right[n] = 1;
		int temp = 1;
		int index = 1;
		for(i = 1;index < n;i = right[i])
		{
			for(j = 1;j < m;j++)
				temp = right[temp];
			
			int left = findLeftNode(temp,right,n);
			right[left] = right[temp];
			data[temp] = -1;
			
			int save = temp;
			temp = right[temp];
			right[save] = -1;
			
			for(i = 0;i <= n;i++)
			{	
				System.out.print(data[i] + " ");
			}
			System.out.println();
			index++;
		}
	}

	private static int findLeftNode(int temp,int[] right,int n) 
	{
		for(int i = 1;i <= n;i++)
			if(right[i] == temp)
				return i;
		return -1;
	}
}
运行结果




好的,我可以为您讲解一下利用数组模拟双向链表解决约瑟夫环问题的方法。 首先,我们需要了解约瑟夫环问题的基本情况。这个问题的描述是:有n个人围成一圈,从第一个人开始报数,报到m的人出圈,然后从出圈的下一个人开始重新报数,直到所有人都出圈为止。我们需要编写程序来模拟这个过程,并输出出圈的顺序。 为了解决这个问题,我们可以使用一个数组模拟双向链表数组的每一个元素表示一个人,包含三个属性:前驱节点、后继节点和是否出圈。初始时,我们可以将数组中每个元素的前驱节点和后继节点设置成相邻的元素,形成一个环状结构。然后,我们可以依次遍历这个数组,找到下一个未出圈的人,并将当前人的后继节点指向下一个未出圈的人。当我们找到第m个未出圈的人时,我们可以将其标记为已出圈,并将其前驱节点的后继节点指向其后继节点,从而将其从链表中删除。最后,我们需要输出出圈的顺序,即每个人的编号。 下面是利用数组模拟双向链表解决约瑟夫环问题的示例代码: ```c++ #include <iostream> using namespace std; const int MAXN = 100; // 约瑟夫环的人数上限 int n, m, cnt; // n表示约瑟夫环的人数,m表示报数的次数,cnt表示当前未出圈的人数 int pre[MAXN], nxt[MAXN], out[MAXN]; // pre表示每个人的前驱节点,nxt表示每个人的后继节点,out表示每个人是否出圈 int main() { // 读入约瑟夫环的人数和报数的次数 cin >> n >> m; // 初始化数组,构建双向链表 for (int i = 1; i <= n; i++) { pre[i] = i - 1; nxt[i] = i + 1; out[i] = 0; } pre[1] = n; nxt[n] = 1; // 开始模拟约瑟夫环的过程 cnt = n; int cur = 1; // 从第一个人开始报数 while (cnt > 0) { for (int i = 1; i < m; i++) { cur = nxt[cur]; // 找到下一个未出圈的人 } out[cur] = 1; // 标记当前人已出圈 nxt[pre[cur]] = nxt[cur]; // 将当前人从链表中删除 pre[nxt[cur]] = pre[cur]; cnt--; // 未出圈的人数减1 cur = nxt[cur]; // 从下一个人开始重新报数 } // 输出出圈的顺序 for (int i = 1; i <= n; i++) { if (out[i]) { cout << i << " "; } } cout << endl; return 0; } ``` 希望这个解法能够帮助到您解决约瑟夫环问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值