前面写了一篇文章《C语言中特殊结构类型“单链表”》,既然有单链表,那就会有双链表。所以,我想来详谈一下双链表。
双向链表,顾名思义,其有别于单向链表。单向链表是有方向的,其方向是从头结点开始,直到尾结点。实质是因为单链表的指针域只存储了下一结点的地址。而双链表之所以是双向,其实质也就是每个结点的指针域不仅存储下一结点的地址,还存储上一结点的地址。
这有什么好处吗?想想看假设对于一个单向链表,某一结点的后继结点可以直接通过next指针找到,但是如果我要你找这个结点的前继结点,是不是很困难?因为这一结点并未提供前一结点的任何信息。双向链表的引入轻而易举地解决这类问题。
概念:双向链表就是链表方向是双方向的,也就是双向链表中的每一个结点的指针域有两个指针,一个指针用来指向上一个结点(Previous指针),另一个指针用指向下一个结点(Next指针)。
通过这个图,找找双向链表与单向链表的共性和差异?
共性:单向链表和双向链表均有头结点。且头结点中只有数据域和指向首元结点的next指针。此外,其他结点都有数据域。双向结点从头指针向右的方向看其实就是单向指针。且最后一个结点的指针域为空,即NULL。
差异:双向链表除头结点外,其他结点的指针域不但包含后继结点的地址,还包含前驱结点的地址。(第一个结点的指针域没有前驱指针,最后一结点没有后继指针),总而言之,双向链表结点只是比单向链表结点多了一个前驱单元指针,用于找到前驱结点。与单向链表相比,双向链表具有反向遍历的功能。
定义:
typedef struct DNode * PtrToDNode;
struct DNode //与定义单向链表的模板一同
{
ElementType Data; //存储结点数据
PtrToDNode Next; //指向下一个结点的指针
PtrToDNode previous; //指向前一个结点的指针
};
通过这个代码,找找双向链表与单向链表的共性和差异?
共性:因为都是定义结点,所以模板与单向链表一样,结构成员包括数据域和指针域。
差异:多定义一个指针,这个指针用于保存前驱结点的地址。也就是说不但有next指针,还有pre指针。
操作
双向链表有哪些操作呢?是不是和单向链表有一样的操作呢?
答:双向链表也有插入,删除,遍历的操作。
我们在考虑这些操作的时候应该考虑哪些问题呢?
首先,因为双向链表的指针域有前后两个指针,所以需要同时考虑这两个指针。
下面我们以双向链表的插入为例先来看一个简单的例子。
//假设现在有一个双向链表,要在p指针指向的a2结点后插入新结点t,应该怎么做呢?
//要插入一个结点t,大概思路是:新插入的结点的前驱指针应该指向a2,即保存a2的地址。后继指针应该指向a3,即保存a3的地址
t---->previous = p;
t---->next = p---->next; //p---->next实际上是a3的地址,把它保存到t的指针域的后继指针
p---->next---->previous = t; //p---->next为结点a3,它的前驱指针指向t
p---->next = t; //p的后继指针指向t
//在增加结点t的同时,不忘更改a2的后继指针和a3的前驱指针
通过上述代码,我们发现要在一个双向链表中增加一个结点,大致有两步:首先要在新结点的指针域增加前驱指针和后继指针;然后要修改增加结点的后一个结点的前驱指针,和前一个结点的指针域的后继指针。而且要注意上述语句要保持一定的顺序。否则得不到正确的结果。
//假设现在要删除p指向的a2结点,应该怎么做呢?
p---->previous---->next = p---->next;//把a3的地址存放到a1的后继指针域
p---->next---->previous = p---->previous;//把a1的地址赋值给a3的前继指针
free(p);
由于双向链表具有反向遍历的功能,所以它可以从尾结点开始向前遍历。
以上,就是对双向链表的浅谈。