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

来源:互联网 发布:淘宝宝贝id怎么查 编辑:程序博客网 时间:2024/06/06 15:00

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

一、如何模拟?

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

每个节点的结构

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

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;elseright[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;}}
运行结果




0 0
原创粉丝点击