前面介绍的单链表允许从一个结点直接访问它的后继结点,所以, 找直接后继结点的时间复杂度是O(1)。但是,要找某个结点的直接前驱结点,只能从表的头引用开始遍历各结点。如果某个结点的Next等于该结点,那么,这个结点就是该结点的直接前驱结点。也就是说,找直接前驱结点的时间复杂度是O(n),n是单链表的长度。当然,我们也可以在结点的引用域中保存直接前驱结点的地址而不是直接后继结点的地址。这样,找直接前驱结点的时间复杂度只有O(1),但找直接后继结点的时间复杂度是O(n)。如果希望找直接前驱结点和直接后继结点的时间复杂度都是O(1),那么,需要在结点中设两个引用域,一个保存直接前驱结点的地址,叫prev,一个直接后继结点的地址,叫next,这样的链表就是双向链表(Doubly Linked List)
双向链表的结点结构示意图
双向链表结点的定义与单链表的结点的定义很相似,,只是双向链表多了一个字段prev。
public class DbNode<T>
{
private T data; //数据域
private DbNode<T> prev; //前驱引用域
private DbNode<T> next; //后继引用域
//构造器
public DbNode(T val, DbNode<T> p)
{
data = val;
next = p;
}
//构造器
public DbNode(DbNode<T> p)
{
next = p;
}
//构造器
public DbNode(T val)
{
data = val;
next = null;
}
//构造器
public DbNode()
{
data = default(T);
next = null;
}
//数据域属性
public T Data
{
get
{
return data;
}
set
{
data = value;
}
}
//前驱引用域属性
public DbNode<T> Prev
{
get
{
return prev;
}
set
{
prev = value;
}
}
//后继引用域属性
public DbNode<T> Next
{
get
{
return next;
}
set
{
next = value;
}
}
}
由于双向链表的结点有两个引用,所以,在双向链表中插入和删除结点比单链表要复杂。双向链表中结点的插入分为在结点之前插入和在结点之后插入,插入操作要对四个引用进行操作.
设p是指向双向链表中的某一结点,即p存储的是该结点的地址,现要将一个结点s插入到结点p的后面,
插入过程如图
操作如下:
➀ p.Next.Prev = s;
➁ s.Prev = p;
➂ s.Next = p.Next;
➃ p.Next = s;
引用域值的操作的顺序不是唯一的,但也不是任意的,操作➂必须放到操作➃的前面完成,否则p直接后继结点的就找不到了.
双向链表中结点的删除
以在结点之后删除为例来说明在双向链表中删除结点的情况。设p是指向双向链表中的某一结点,即p存储的是该结点的地址,现要将一个结点P删除
操作如下:
➀ p.Prev.Next = P.Next;
➁ p.Next.Prev = p.Prev;
循环链表
有些应用不需要链表中有明显的头尾结点。在这种情况下,可能需要方便地从最后一个结点访问到第一个结点。此时,最后一个结点的引用域不是空引用,而是保存的第一个结点的地址(如果该链表带结点,则保存的是头结点的地址),也就是头引用的值。带头结点的循环链表