双链表的基本实现与讲解(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网上随便搜,有过后端经验的人应该很多都 ...
随机推荐
- 关于Ubuntu中snap安装软件太慢解决办法
两种方法,一是下载好包手动安装,二设置snap的代理. 下载安装包方式 到 https://uappexplorer.com/snaps 搜索需要的 snap 包,然后下载 下载的时候选择对应的平台. ...
- 重写mybatis的字符串类型处理器
1.简介 无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型. St ...
- hive(2)数据类型和文件格式
基本的数据类型 Hive支持关系型数据中大多数基本的数据类型,同时也支持关系型数据库中很少出现的三种集合数据类型. 集合数据类型 Hive中的列支持使用struct.map.array集合数据类型,下 ...
- kubernetes 1.14安装部署helm插件
简单介绍: Helm其实就是一个基于Kubernetes的程序包(资源包)管理器,它将一个应用的相关资源组织成为Charts,并通过Charts管理程序包.再简单点说,可以当做RHEL/CentOS系 ...
- Nginx 所使用的 epoll 模型是什么?
对于 Nginx,相信有过 Web 服务部署经验的同学都不陌生,它有以下特点: 是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器. Nginx 相较于 A ...
- python判断字符串包含关系
转自---http://blog.csdn.net/yl2isoft/article/details/52079960 1.使用成员操作符 in >>> s='nihao,shiji ...
- Perl关于分椰子的趣味问题
话说某天一艘海盗船被天下砸下来的一头牛给击中了,5个倒霉的家伙只好逃难到一个孤岛,发现岛上孤零零的,幸好有有棵椰子树,还有一只猴子! 大家把椰子全部采摘下来放在一起,但是天已经很晚了,所以就睡觉先晚上 ...
- centos安装jdk1.8的三种方法
一.手动解压安装包: 1.在user目录下新建java文件夹: # cd /usr/ # mkdir java # cd java 2.下载jdk1.8,进入http://www.orac ...
- IdHTTPServer开发https服务器
IdHTTPServer开发https服务器 该篇经验同样适用于DATASNAP和UNIGUI,因为它们都基于INDY10. 1)需要TIdServerIOHandlerSSLOpenSSL控件 2) ...
- CheatEngine查看PE header
先打开进程,炉石传说. 然后选择MemoryViewer 在MemoryViewer界面,Tools菜单,然后选择Dissect PE headers 然后查看mono.dll的信息 0x357A0是 ...