双链表的基本实现与讲解(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网上随便搜,有过后端经验的人应该很多都 ...
随机推荐
- Sklearn多元线性回归
Sklearn多元线性回归 1 正文 2 参考资料 Sklearn多元线性回归
- 2018-2019 20165226 Exp9 Web安全基础
2018-2019 20165226 Exp9 Web安全基础 目录 一.实验内容说明及基础问题回答 二.实验过程 Webgoat准备 XSS攻击 ① Phishing with XSS 跨站脚本钓鱼 ...
- gisoracle做windows界面
import tkinter as tk from tkinter import messagebox # 设置窗口居中 def window_info(): ws = window.winfo_sc ...
- nodejs五子棋online游戏开发视频教程,客户端cocos creator js
开发的游戏是五子棋online,网络版的,服务端部分和客户端部分都在这个教程里面,可以看一下目录! 服务器nodejs游戏开发教程 使用Nodejs开发网络服务器 游戏服务端 ,cocos creat ...
- Java RMI实践
Java远程方法调用,即Java RMI(Java Remote Method Invocation).一种用于实现远程过程调用的应用程序编程接口.客户机上运行的程序可以调用服务器上的对象. 缺点:只 ...
- TP5 分页数据加锚点
TP5 分页数据加锚点跳转到相应位置 有这样一个需求,就是加载评论后,点下一页的时候回到相应的位置. $comment = Db('comment')->order('addtime' ...
- sublime text3中如何使用PHP编译系统
[WinError 2] 系统找不到指定的文件 编译错误原因,是因为编译器没有配置 第一步: 添加php可执行程序所在目录到系统环境变量(具体方法此处省略,使用本文下面的说明中的方法,此步骤可以省略 ...
- 【mybatis源码学习】与spring整合Mapper接口执行原理
一.重要的接口 org.mybatis.spring.mapper.MapperFactoryBean MapperScannerConfigurer会向spring中注册该bean,一个mapper ...
- fork 可能导致subprocess崩溃
https://docs.python.org/zh-cn/3/library/multiprocessing.html 在 3.8 版更改: 对于 macOS,spawn 启动方式是默认方式. 因为 ...
- 深入浅出ES6教程『async函数』
大家好,本人名叫苏日俪格,大家叫我 (格格) 就好,在上一章节中我们学到了Symbol & generator的用法,下面我们一起来继续学习async函数: async [ə'zɪŋk]:这个 ...