本篇文章参考《啊哈!算法》
一、如何模拟?
链表的每个结点中只包含一个指针域,叫做单链表,即构成链表的每个结点只有一个指向直接后继结点的指针
每个节点的结构
对于链表中的每个结点,需要完成两个部分。
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;
}
}
运行结果