链表与数组和类存在着明显的不同。对于数组和类来说,我们是把数据项放到内存中,然后再通过名字或下标来引用它;对于链表来说,存储信息的方式使我们不易访问而是更易于重新排列这些信息。
处理以链表方式组织的数据称为链表处理,以下常用的规则给出了基本的链表处理操作的实现:
1。永不为空的循环链表
第一次插入:head.next=head;
在x后插入t:t.next=x.next;x.next=t;
移走x之后的结点:x.next=x.next.next;
循环遍历:t=head;
do{t=t.next;}while(t!=head)
检查是否只有一个数据项:if(head.next=head)
2。头指针,空尾指针
初始化:head=null;
在x后插入t:if(x==null){head=t;head.next=null;}
else{t.next=x.next;x.next=t;}
移走x之后的结点: t=x.next;x.next=t.next;
循环遍历:for(t=head;t!=null;t=t.next)
检查链表是否为空:if(head==null)
以下给出的循环链表类把程序从乏味的低级操作中解放出来
... {
static class Node
...{
int val;Node next;
Node(int v)...{val=v;}
}
Node next(Node x)
...{
return x.next;
}
int val(Node x)
...{
return x.val;
}
Node insert(Node x,int v)
...{
Node t=new Node(v);
if(x==null)t.next=t;
else...{
t.next=x.next;x.next=t;}
return t;
}
}
void remove(Node x)
... {
x.next=x.next.next;
}
}
我们看看这个循环链表类怎么在约瑟芬问题中使用的
... {
public static void main(String[] args)
...{
int N=Integer.parseInt(args[0]);
int M=Integer.parseInt(args[1]);
CircularList L=new CircularList();
CircularList.Node x=null;
for(int i=1;i<=N;i++)
x=L.insert(x.i);
while(x!=L.next(x))
...{
for(int i=1;i<M;i++)
x=L.next(x);
L.remove(x);
}
System.out.println("Survivor is "+ L.val(x));
}
}
我们了解了链表的基本处理方法后,我们可以将它们全部封装在单个的类中。这样可以让客户程序工作在更高的抽象层次上,并且可以考虑关键的操作的各种不同实现方法并方便的测试它们的效率。
最后,我们可以在链表中加入更多的指针以增加从链表尾向链表头遍历的能力,也就是我们通常所说的双向链表。
双向链表就是我们为每个结点设置两个指针:一个指针(prev)指向前面的数据项,另一个指针(next)指向后面的数据项。若使用哑结点或循环链表我们可以保证x,x.next.prev,x.prev.next是同一结点。
双向链表的插入、删除操作要比单向链表复杂一点:
在双向链表中,对一个结点的引用就可以保证我们移走此结点:
t.next.prev=t.prev;t.prev.next=t.next;
要在双向链表中插入一个结点,需要设置4个引用。要把结点t插入x之后:
t.next=x.next;x.next.prev=t;x.next=t;t.prev=x;
在处理双向链表时,需要的空间和操作都增加了一倍,所以一般情况下不适用双向链表,除非有特殊的要求。