双链表的基本实现与讲解(C++描述)
双链表

双链表的意义
单链表相对于顺序表,确实在某些场景下解决了一些重要的问题,例如在需要插入或者删除大量元素的时候,它并不需要像顺序表一样移动很多元素,只需要修改指针的指向就可以了,其时间复杂度为 O(1) 但是这可是有前提的,那就是这一切都基于确定节点后,纯粹考虑删除和插入的情况下,但是如果我们仍未确定节点的位置,那么单链表就会出现一些问题了,例如我们来看一下删除这个操作
删除操作
单链表:
对应图中的节点,想要删除第2个节点 a1 只需要 将首元结点的指针指向到第三个节点的地址去

但是问题就在于我们如何得到待删除节点的前驱,也就是我们图中的首元结点,我们给出两种方法
- A:定位待删除节点的同时,一直顺便保存当前节点的前驱
- B:删除节点后,重新回到单链表表头,定位到其指定前驱
但是无论我们选择哪一种方法,指针的总移动数都会是 2n 次,而双链表却在这一类型问题上做出了很好的处理
双链表:

单链表中之所以出现问题,就是因为各个节点只有一个指向后继的指针域 next,只能向后移动查找,一旦我们想要查询前一节点,就变得很麻烦,所以双链表就在每个节点前面增加一个指向前驱的指针域 prior,这样我们就可以直接定位到我们的前一个节点了,这也就是双链表
注意:为了统一运算,避免特殊情况的出现,我们也常常在尾部设置一个 “尾部头结点” 其 next 指针域为空
线性表的抽象数据类型定义
我们在给出双链表的定义之前我们还是需要先引入我们线性表的抽象数据类型定义
#ifndef _LIST_H_
#define _LIST_H_
#include<iostream>
using namespace std;
class outOfRange{};
class badSize{};
template<class T>
class List {
public:
// 清空线性表
virtual void clear()=0;
// 判空,表空返回true,非空返回false
virtual bool empty()const=0;
// 求线性表的长度
virtual int size()const=0;
// 在线性表中,位序为i[0..n]的位置插入元素value
virtual void insert(int i,const T &value)=0;
// 在线性表中,位序为i[0..n-1]的位置删除元素
virtual void remove(int i)=0;
// 在线性表中,查找值为value的元素第一次出现的位序
virtual int search(const T&value)const=0;
// 在线性表中,查找位序为i的元素并返回其值
virtual T visit(int i)const=0;
// 遍历线性表
virtual void traverse()const=0;
// 逆置线性表
virtual void inverse()=0;
virtual ~List(){};
};
/*自定义异常处理类*/
class outOfRange :public exception { //用于检查范围的有效性
public:
const char* what() const throw() {
return "ERROR! OUT OF RANGE.\n";
}
};
class badSize :public exception { //用于检查长度的有效性
public:
const char* what() const throw() {
return "ERROR! BAD SIZE.\n";
}
};
#endif
双链表类型的定义
#ifndef _SEQLIST_H_
#define _SEQLIST_H_
#include "List.h"
#include<iostream>
using namespace std;
template<class elemType>
//elemType为双链表存储元素类型
class doubleLinkList:public List<elemType> {
private:
//节点类型定义
struct Node {
//节点的数据域
elemType data;
//节点的两个指针域
Node *prior, *next;
//两个构造函数
Node(const elemType &value, Node *p = NULL, Node *n = NULL) {
data = value;
prior = p;
next = n;
}
Node():next(NULL), prior(NULL) {}
~Node(){}
};
//单链表的头指针
Node *head;
//单链表的尾指针
Node *tail;
//单链表的当前长度
int curLength;
//返回指向位序为i的节点的指针
Node *getPosition(int i)const;
public:
doubleLinkList();
~doubleLinkList();
//清空单链表,使其成为空表
void clear();
//带头结点的单链表,判空
bool empty()const {return head -> next == NULL;}
//返回单链表的当前实际长度
int size()const {return curLength;}
//在位序i处插入值为value的节点表长增1
void insert(int i, const elemType &value);
//删除位序为i的节点的值,表长减1
void remove(int i);
//查找值为value的节点的第一次出现的位置
int search(const elemType &value)const;
//查找值为value的节点的前驱的位序
int prior(const elemType&value)const;
//访问位序为i的节点的值,0定位到首元结点
elemType visit(int i)const;
//遍历单链表
void traverse()const;
//逆置单链表
void inverse();
//合并单链表
};
双链表基本运算的实现
(一) 构造与析构函数
template <class elemType>
doubleLinkList<elemType>::doubleLinkList() {
//头尾节点分别指向 头结点和尾部头结点
head = new Node;
tail = new Node;
head -> next = tail;
tail -> prior = head;
}
template <class elemType>
doubleLinkList<elemType>::~doubleLinkList() {
Node *p = head -> next, *tmp;
//头结点的后继是尾部头结点
head -> next = tail;
//尾部头结点的前驱是头结点
tail -> prior = tail;
while(p != tail) {
tmp = p -> next;
delete p;
p = tmp;
}
curLength = 0;
}
(二) 查找位序为i的节点的地址
template <class elemType>
typename doubleLinkList<elemType>::Node *doubleLinkList<elemType>::getPosition(int i) const {
Node *p = head;
int count = 0;
if(i < -1 || i > curLength)
return NULL;
while(count <= -1) {
p = p -> next;
count++;
}
return p;
}
(三) 查找值为value的节点的位序
template <class elemType>
int doubleLinkList<elemType>::search(const elemType &value) const {
Node *p = head -> next;
int i = 0;
while(p != tail && p -> data != value) {
p = p -> next;
i++;
}
if(p == tail)
return -1;
else
return i;
}
(四) 插入元素
template <class elemType>
void doubleLinkList<elemType>::insert(int i, const elemType &value) {
Node *p, * tmp;
if(i < 0 || i > curLength)
throw outOfRange();
p = getPosition(i);
tmp = new Node(value, p -> prior, p);
//p原先的前驱的后继指向tmp
p -> prior -> next = tmp;
//修改p的前驱为tmp
p -> prior = tmp;
++curLength;
}
(五) 删除位序为i的节点
template <class elemType>
void doubleLinkList<elemType>::remove(int i) {
Node *p;
if(i < 0 || i > curLength)
throw outOfRange();
p = getPosition(i);
p -> prior -> next = p -> next;
p -> next -> prior = p -> prior;
delete p;
--curLength;
}
(六) 访问位序为 i的节点的值
template <class elemType>
elemType doubleLinkList<elemType>::visit(int i) const {
//visit 不嫩直接用getPosition判断范围是否合法,因为其范围为[-1,curLength]
if(i < 0 || i > curLength -1)
throw outOfRange();
//合法以后
Node *p = getPosition(i);
return p -> data;
}
(七) 遍历双链表
template <class elemType>
void doubleLinkList<elemType>::traverse() const {
Node *p = head -> next;
cout << "traverse: ";
while(p != tail) {
cout << p -> data << " ";
p = p -> next;
}
cout << endl;
}
(八) 遍历双链表
template <class elemType>
void doubleLinkList<elemType>::inverse() {
Node *tmp, *p = head -> next;
//构成双空链表
head -> next = tail;
tail -> prior = head;
while(p != tail) {
tmp = p -> next;
p -> next = head -> next;
p -> prior = head;
head -> next -> prior = p;
head -> next = p;
p = tmp;
}
}
结尾:
如果文章中有什么不足,或者错误的地方,欢迎大家留言分享想法,感谢朋友们的支持!
如果能帮到你的话,那就来关注我吧!如果您更喜欢微信文章的阅读方式,可以关注我的公众号
在这里的我们素不相识,却都在为了自己的梦而努力 ❤
一个坚持推送原创开发技术文章的公众号:理想二旬不止

双链表的基本实现与讲解(C++描述)的更多相关文章
- 数据结构图文解析之:数组、单链表、双链表介绍及C++模板实现
0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...
- linux内核的双链表list_head、散列表hlist_head
一.双链表list_head 1.基本概念 linux内核提供的标准链表可用于将任何类型的数据结构彼此链接起来. 不是数据内嵌到链表中,而是把链表内嵌到数据对象中. 即:加入链表的数据结构必须包含一个 ...
- JAVA 链表操作:单链表和双链表
主要讲述几点: 一.链表的简介 二.链表实现原理和必要性 三.单链表示例 四.双链表示例 一.链表的简介 链表是一种比较常用的数据结构,链表虽然保存比较复杂,但是在查询时候比较便捷,在多种计算机语言都 ...
- java实现双链表(差点没写吐系列...)
刚才把单链表写完了,现在又把双链表写了,双链表和单链表的区别就是每个节点有prior和next两个指针,不同于单链表的一个next指针,而且,正是因为有这两个指针,所以双链表可以前后两个方向去移动指针 ...
- C和指针 第十二章 使用结构和指针 双链表和语句提炼
双链表中每个节点包含指向当前和之后节点的指针,插入节点到双链表中需要考虑四种情况: 1.插入到链表头部 2.插入到链表尾部 3.插入到空链表中 4.插入到链表内部 #include <stdio ...
- [C++11][数据结构]自己的双链表实现
这个双链表,是我模仿stl的list制作的,只实现了一些基本功能,像merge,transfer这些就没有实现,用户可以用基本操作来自己做外部实现. 我没有选用stl的[begin,end)迭代器模式 ...
- C#双链表
单链表允许从一个结点直接访问它的后继结点,所以, 找直接后继结点的时间复杂度是 O(1).但是,要找某个结点的直接前驱结点,只能从表的头引用开始遍历各结点.如果某个结点的 Next 等于该结点,那么, ...
- Linux 底下使用C语言的 单链表 ,双链表,二叉树 读取文件,并排序
直接上代码 单链表Linux读文件排序: 双链表Linux读取文件排序: 二叉树LinuX读取文件并排序:
- 再谈LRU双链表内存管理
N年前我写了个双链表也发了博客,还添了代码.但是那个代码不但复杂,而且还有有问题的,一直懒得整理,放在空间误导别人.最近在写服务端,今天抽点空补一篇. 关于LRU网上随便搜,有过后端经验的人应该很多都 ...
随机推荐
- [mysql8]新坑哈 更改Mysql 表的大小转换设置lower_case_table_names=1
在安装了8.0.14之后,初始化的时候在my.cnf里设置了lower_case_table_names=1,安装好了之后,启动报错: 1 2 3 4 5 2019-01-28T13:24:24.91 ...
- git添加公钥后报错sign_and_send_pubkey: signing failed: agent refused operation的解决办法
在服务器添加完公钥后报错 sign_and_send_pubkey: signing failed: agent refused operation 这个时候我们只要执行下 eval "$( ...
- 记一次SpringContextHolder.getBean出现异常NoClassDefFoundError: Could not initialize class
代码如下: public class TestUtils { private static UserDao logDao = SpringContextHolder.getBean(UserDao.c ...
- vue+elementui搭建后台管理界面(2首页)
1 会话存储 使用html5的 sessionStorage 对象临时保存会话 // 保存会话 sessionStorage.setItem('user', username) // 删除会话 ses ...
- AAR文件简介
假如我们希望提供一个带有资源文件的第三方库给别人使用,总不能直接把源代码给别人,但是我们知道eclipse打包的时候不能包含res的资源文件,于是Android在发布Android studio的时候 ...
- 肿瘤免疫疗法 | 细胞治疗和PD1/PDL1 | Tumor immunotherapy | cell therapy
人类肿瘤治疗史上的里程碑无疑一定有一座是肿瘤免疫疗法的. 而肿瘤免疫疗法的主要两大领域,细胞治疗以及以PD1/PDL1为代表的免疫检查点抑制剂都在飞速发展. 目前,已经有5种抗PD1/PDL1抗体药物 ...
- 枚举(Enum)
enum是一个全新的“类”. 枚举(Enum): 我们所定义的每个枚举类型都继承自java.lang.Enum类.枚举中的每个成员都是public static final的. 当您使用“enum”定 ...
- (二)SpringMVC+mybatis实践
一.需求 以案例作为驱动 springmvc和mybatis使用一个案例(商品订单管理系统) 功能需求:商品的列表查询 二.环境准备 数据库环境 Java环境 jdk1.8 eclipse sprin ...
- https://docs.huihoo.com/infoq/qconbeijing/2016/day1/工程效率提升专题/2-2-支持百度万人开发的工具装备及百度工程能力建设-夏仲璞.pdf
https://docs.huihoo.com/infoq/qconbeijing/2016/day1/工程效率提升专题/2-2-支持百度万人开发的工具装备及百度工程能力建设-夏仲璞.pdf http ...
- To avoid slowing down lookups on a near-full table, we resize the table when it's USABLE_FRACTION (currently two-thirds) full.
https://github.com/python/cpython/blob/3.8/Objects/dictobject.c