为什么要用双向链表?
- 因为单链表中的结点有指示后继结点的指针域,找后继结点方便。即,查找某结点后继结点的复杂度为 O ( 1 ) O(1) O(1)。
- 但是在单链表中,没有指示前驱结点的指针域,找前驱结点难,要从表头出发查找。即,查找某结点前驱结点的复杂度为 O ( n ) O(n) O(n)。
而双向链表可以克服这种缺点。
双向链表的定义与特点
1.定义
双向链表:在单链表中的每个结点里在增加一个指向其前驱结点的指针域prior,这样链表就形成了有两个方向不同的链。
可定义如下,
typedef struct DuLNode
{
ElemType data;
struct DuLNode *prior, *next;
}DuLNode, *DuLinkList;
双向循环链表:让头结点的前驱指针指向链表的最后一个结点,让最后一个结点的后继指针指向头结点。
2.特点
对称性:当前结点的前驱结点的next域和当前结点的后继结点的prior域都是指向当前结点的指针。
即,p->prior->next = p = p->next->prior
双向链表中的某些操作,如求表长,查找某个元素等,这些操作只涉及一个方向上的指针,所以它们的算法与单链表同。但插入、删除时,则需要同时修改两个方向上的指针,两者的复杂度均为
O
(
n
)
O(n)
O(n)。
双向链表的插入操作
如下图,在结点p之前插入值为x的结点s,
-
具体步骤
- 1.找到第i个结点p(查找算法与单链表同),并且新建结点s,将s的数据域赋x。
- 2.将a所在结点变成x的前驱结点。即,为结点x的prior域赋a结点的地址(
p->prior
)。 - 3.结点a的next域赋结点x的地址,结点a的next域表示为
p->prior->next
。
- 4.结点x的next域赋结点b的地址。
- 5.结点p的priort域赋结点x的地址。
-
算法描述
void ListInsert_DuL(DuLinkList& L, int i, ElemType e)
{
if (!p = GetElemP_DuL(L, i))
return ERROR;
s = new DuLNode;
s->data = e;
s->prior = p->prior;
p->prior->next = s;
s->next = p;
p->prior = s;
return OK;
}
双向链表的删除操作
如下图,要删除链表中的b结点,当前指针p指向要删除的结点b
-
具体步骤
- 1.结点c变成结点a的后继,即结点的a的next域指向c结点。
结点a的next域为:p->prior->next
c结点的地址:p->next
- 2.结点a变成结点c的前驱,即结点的c的prior域指向a结点。
结点c的prior域为:p->next->prior
a结点的地址:p->prior
- 3.删除结点b。
- 1.结点c变成结点a的后继,即结点的a的next域指向c结点。
-
算法描述
void ListDelete_DuL(DuLinkList& L, int i, ElemType& e)
{
if (!p = GetElemP_DuL(L, i))
return ERROR;
e = p->data;
p->prior->next = p->next;
p->next->prior = p->prior;
delete p;
return OK;
}