双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。
怎样理解双向链表,用生活中的一个例子来解释一下:最开始的火车是只有一个火车头的,当一辆火车从A->B->C->D->E->F->G->H->J->K->L这条路线进行行驶,假设或者运行到H站点要运送一批货物到G站点,只能通过H->J->K->L->A->B->C->D->F->G这个路线来进行,相当于是单向循环链表的形式。这个时候大家可以看到很费时和费力,严重的浪费时间和资源,这个时候人们就想了,如果火车可以倒着走那不就很方便吗,于是火车慢慢地开始有了两个火车头,这样可以往后走,直接后退一个站点,这样就可以轻松实现K->G的运输工作。这个小例子就类似于双向链表,但是真正意义上的双向链表跟这个又是有一点的区别,那么这个时候相对于单链表而言。双向链表就可以定义如下的结构体:
typedef struct DualNode
{
ElemType data;
struct DualNode *prior; //前驱结点
struct DualNode *next; //后继结点
}DualNode, *DuLinkList;
如下图1,双向链表的结点结构示意图以及空链表的结构示意图
图1 双向链表结点结构图
在图1中可以看到,结点结构比之前的单链表多了一个结点,多一个头指针prior,存放着前驱结点的指针,当双向链表的两个结点都为空的时候则为空链表。
图2 非空的双向循环链表
对于非空双向链表中的一个结点p,它的后继结点的前驱结点是什么? 当然就是它自己。
2,双向链表的插入操作
图3 可以看到双向循环链表的插入操作
在图3中可以看到具体的操作步骤,具体代码实现如下:
s->next = p;
s->prior = p->prior;
p->prior->next = s;
p->prior = s;
这四步分别对应着图3中的四步操作,首先将s结点的后继指针指向p;接着将s的前驱结点赋值为p的前驱指针;p的前驱结点的后继结点赋值为s;最后将p的前驱结点赋值为s。
3,双向链表的删除操作
图4 删除操作
在图4中可以看到,直接将前一个元素的后继指针指向后一个元素的前驱结点,同时将后一个元素的前驱结点指向前一个元素的前驱结点。代码即为:
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);
4,与单链表的区别
(1)从任意一个结点开始,可以查找链表中的其他任意结点。
(2)既可以依照后继的方向(向后)遍历,也可以依照前驱的方向(向前)遍历。
(3)每个指针域中都增加了一个存储指针的空间,降低了存储密度。
(4)可以在当前结点前面或者后面插入,可以删除前趋和后继(包括结点自己)。 单链表只能在结点后面插入和删除。
(5)双向链表通过增加一定的空间复杂度,降低了向前遍历的时间复杂度。