3.2 双向链表
1.简介
前面3.1的单链表在操作过程中有一个缺点,就是后面的节点无法直接找到前面的节点,这使很多操作都得从头到尾去搜寻节点,算法效率变得非常低,解决这个问题的方法就是重新定义链表的节点使每个节点有两个指针,一个指向前驱一个指向后驱,这就是双向链表。



节点定义
template<class T>
class DLLNode {
public:
DLLNode() {
next = prev = 0;
}
DLLNode(const T& el, DLLNode<T> *n = 0, DLLNode<T> *p = 0) {
info = el; next = n; prev = p;
}
T info;
DLLNode<T> *next, *prev;
};
3.1节中所定义方法的局限性是链表只能存储整数. 如果希望链表存储浮点数点数或者数组.就必须重新写一套类似的代码。然而,更好的做法是只对这样的类声明一次,并且不提前确定其中存储什么数据类型,在 C++中,用模板就能很方便地做到这一点 。 为了说明模板在链表处理中的用法, 从本节开始使用模版来定义链表,但链表操作的示例仍然采用存储整数的链表。
2.双向链表的操作
DoublyLinkedList类定义了双向链表的操作
template<class T>
class DoublyLinkedList {
public:
DoublyLinkedList() {
head = tail = 0;
}
void addToDLLTail(const T&);//
T deleteFromDLLTail();//
~DoublyLinkedList() {
clear();
}
bool isEmpty() const {
return head == 0;
}
void clear();
void setToNull() {
head = tail = 0;
}
void addToDLLHead(const T&);
T deleteFromDLLHead();
T& firstEl();
T* find(const T&) const;
protected:
DLLNode<T> *head, *tail;
friend ostream& operator<<(ostream& out, const DoublyLinkedList<T>& dll) {
for (DLLNode<T> *tmp = dll.head; tmp != 0; tmp = tmp->next)
out << tmp->info << ' ';
return out;
}
};
这里只讨论两种操作方法:插入节点到双向链表结尾和删除末尾的节点,其余操作方法读者可以自行参考代码。
(1)插入节点到双向链表结尾

具体分为下面几个步骤:
- 创建一个新的节点并初始化三个数据成员(info初始化为el,next初始化为null)
- 将prev的值设置为tail
- 将tail指向新加入的节点
- 前驱节点的next指向新加入的节点
template<class T>
void DoublyLinkedList<T>::addToDLLTail(const T& el) {
if (tail != 0) {
tail = new DLLNode<T>(el,0,tail);
tail->prev->next = tail;
}
else head = tail = new DLLNode<T>(el);
}
(2)删除末尾的节点

双向链表删除一个末尾节点比较简单,因为不需要通过循环来查找待删除节点的前驱节点,将tail指向待删除节点的前驱,
然后将待删除节点的next设置为null。但是如果待操作的链表是空链表可能会引起程序崩溃,因此使用者在删除末尾节点之前要先检查链表是否为空,检查链表是否为空,代码中也提供了。
if (!list.isEmpty())
n = list.deleteFromDLLTail();
else do not delete;
另一种特殊情况是链表只有一个节点,要将head和tail设置为空。
因为可以直接访问最后一个节点,因此这两个方法执行的时间复杂度为O(1)。
附件列表
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· 本地部署DeepSeek后,没有好看的交互界面怎么行!
· 趁着过年的时候手搓了一个低代码框架
· 推荐一个DeepSeek 大模型的免费 API 项目!兼容OpenAI接口!