【数据结构】图文讲解神奇的单链表与双链表
单链表与双链表
- 一、前言
- 二、单链表概念讲解
- 三、单链表代码讲解
- add_head()函数
- insert()函数
- del()函数
- 四、双链表
一、前言
在平时我们的作业中,我们有可能会遇到对于数组需要进行一个数或者是一批数的插入处理。在这里我们如果采用传统暴力插入的话,每一次插入处理我们都是O(n)的时间复杂度,如果我们插入的次数一旦变多,那么很容易就会超时,在这里我们需要学习一种数据结构----链表。
本篇博客建议与博主另一篇博客一起学习理解(【图论】链式前向星)
二、单链表概念讲解
在链表中,我们依然使用数组来模拟,在数组中,我们有数字在数组中的编号与这个数字本身,如a[3]=7.编号为3,数字为7。但是我们在但链表中我们还需要对于每一个数字加一个变量next,用来储存下一个数字的编号,例如next[3]=9,就代表a[3]的下一个数不是a[4],而是a[9]。
数组:e[N],ne[N]
注意:规定当ne[x]=-1时,x是链表的最后一个元素
在单链表中,还有一个重要的变量head,它代表着当前链表最前面的数字的编号,如果要遍历整条链表,我们就需要从head开始遍历。
没有听懂?这很正常,让我们尝试着通过代码来理解链表。
三、单链表代码讲解
例题链接:Acwing 单链表
实现一个单链表,链表初始为空,支持三种操作:向链表头插入一个数;
删除第 k 个插入的数后面的数;
在第 k 个插入的数后插入一个数。
现在要对该链表进行 M 次操作,进行完所有操作后,从头到尾输出整个链表。注意:题目中第 k 个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n 个数依次为:第 1 个插入的数,第 2 个插入的数,…第 n 个插入的数。输入格式
第一行包含整数 M,表示操作次数。接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:H x,表示向链表头插入一个数 x。
D k,表示删除第 k 个插入的数后面的数(当 k 为 0 时,表示删除头结点)。
I k x,表示在第 k 个插入的数后面插入一个数 x(此操作中 k 均大于 0)。
输出格式
共一行,将整个链表从头到尾输出。数据范围
1≤M≤100000
所有操作保证合法。输入样例:
10
H 9
I 1 1
D 1
D 0
H 6
I 3 6
I 4 5
I 4 5
I 3 4
D 6
输出样例:
6 4 6 5
正确代码
//#pragma GCC optimize(2)
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<stack>
#include<set>
#include<bitset>
#include<ctime>
#include<cstring>
#include<list>
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 7;int head, e[N], ne[N], idx=1; //链表实现的数组,idx代表数的编号void add_head(int x) //把x加到链表的第一个
{ne[idx] = head;e[idx] = x;head = idx++;
}void insert(int k, int x) //在第k个元素后插入x
{e[idx] = x;ne[idx] = ne[k];ne[k] = idx;idx++;
}void del(int k) //删除第k个元素后一个元素
{ne[k] = ne[ne[k]];
}void solve()
{head = -1; //初始化链表首为-1int m;cin >> m;while (m--){char op;cin >> op;if (op == 'H'){int x;cin >> x;add_head(x);}else if (op == 'D'){int k;cin >> k;if (k == 0)head = ne[head];elsedel(k);}else{int k, x;cin >> k >> x;insert(k, x);}}for (int i = head;i!=-1;i=ne[i]){cout << e[i] << ' ';}
}int main()
{std::ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);solve();return 0;
}
通过代码还是没有看懂吗?我们来把代码中的函数挑出来解释一下吧。
add_head()函数
void add_head(int x) //把x加到链表的第一个
{ne[idx] = head;e[idx] = x;head = idx++;
}
首先我们假设head=3,idx=9,x=5。通过之前的概念介绍,我们可以知道链表的第一个元素的编号是3.
那么我们要把ne[9]设定成head,即现在的链头----3号点,代表3号点变成了9号点的下一个点。然后修改head=9,代表现在链头为9号点了!
接着设置e[9]=5,代表9号点的值为5.
insert()函数
void insert(int k, int x) //在第k个元素后插入x
{e[idx] = x;ne[idx] = ne[k];ne[k] = idx;idx++;
}
首先我们假设现在的链表为这样的,idx为即将插入的元素
在执行完ne[idx] = ne[k]; ne[k] = idx;操作后,链表就变成了这样
一定要注意上面两条代码的执行顺序,否则…你自己对着图试试看?
del()函数
理解了上面的insert函数后,del函数的理解就非常简单了
void del(int k) //删除第k个元素后一个元素
{ne[k] = ne[ne[k]];
}
画一张图来理解吧
这样实际上就达到了删除操作的效果了。
四、双链表
如果能够完全理解单链表的话,那么双链表也就不难理解了
相比单链表,双链表把next数组变成了左右两个数组L[N],R[N]。这样我们就可以查到一个数的左右两个数的编号。
在双链表初始化的过程中,我们人为设定编号为0和1的点,作为链表的最左和最右的的点方便操作。具体步骤见下方例题代码讲解
例题链接:Acwing 双链表
实现一个双链表,双链表初始为空,支持 5 种操作:在最左侧插入一个数;
在最右侧插入一个数;
将第 k 个插入的数删除;
在第 k 个插入的数左侧插入一个数;
在第 k 个插入的数右侧插入一个数
现在要对该链表进行 M 次操作,进行完所有操作后,从左到右输出整个链表。注意:题目中第 k 个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n 个数依次为:第 1 个插入的数,第 2 个插入的数,…第 n 个插入的数。输入格式
第一行包含整数 M,表示操作次数。接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:L x,表示在链表的最左端插入数 x。
R x,表示在链表的最右端插入数 x。
D k,表示将第 k 个插入的数删除。
IL k x,表示在第 k 个插入的数左侧插入一个数。
IR k x,表示在第 k 个插入的数右侧插入一个数。
输出格式
共一行,将整个链表从左到右输出。数据范围
1≤M≤100000
所有操作保证合法。输入样例:
10
R 7
D 1
L 3
IL 2 10
D 3
IL 2 7
L 8
R 9
IL 4 7
IR 2 2
输出样例:
8 7 7 3 2 9
正确代码
//#pragma GCC optimize(2)
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<stack>
#include<set>
#include<bitset>
#include<ctime>
#include<cstring>
#include<list>
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 7;int L[N], R[N], e[N], idx; //用来实现双链表void init() //初始化函数
{L[0] = 1;//右节点0R[1] = 0;//左节点1idx = 2;
}void insert(int k, int x) //在第k个点后插入x
{e[idx] = x;R[idx] =R[k];L[idx] = k;L[R[k]] = idx;R[k] = idx;idx++;
}void del(int k) //删除函数
{R[L[k]] = R[k];L[R[k]] = L[k];
}void solve()
{init();int m;cin >> m;while (m--){string op;cin >> op;if (op == "L"){int x;cin >> x;insert(1, x);}if (op == "R"){int x;cin >> x;insert(L[0], x);}if (op == "D"){int k;cin >> k;del(k+1);}if (op == "IL"){int k, x;cin >> k >> x;insert(L[k+1], x);}if (op == "IR"){int k, x;cin >> k >> x;insert(k+1, x);}}for (int i = R[1]; i != 0; i = R[i]) //链表的遍历{cout << e[i] << ' ';}
}int main()
{std::ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);solve();return 0;
}
如果代码有地方不太好理解的话,画个图看看也许是一个不错的选择哦。
作者:Avalon Demerzel,喜欢我的博客就点个赞吧,更多图论与数据结构知识点请见作者专栏《图论与数据结构》
【数据结构】图文讲解神奇的单链表与双链表相关推荐
- 数据结构之线性表----一文看懂顺序表、单链表、双链表、循环链表
线性表是数据结构中比较基础的内容,不过也是入门的所需要客服的第一个难关.因为从这里开始,就需要我们动手编程,这就对很多同学的动手能力提出了挑战.不过这些都是我们需要克服的阵痛,学习新的知识总是痛苦 ...
- 【python】数据结构和算法 + 浅谈单链表与双链表的区别
有这么一句话说"程序=数据结构+算法",也有人说"如果把编程比作做菜,那么数据结构就好比食材(菜),算法就好比厨艺(做菜的技巧)". 当然这是笼统的说法,不过也 ...
- 数据结构——线性表:顺序表、单链表、双链表(C++)
内容概要: 1.代码部分: 线性表抽象类 顺序表类 单链表类 双链表类 主函数 2.基本概念及注意事项 代码(测试环境VS2017): //线性表ADT类的定义:ADT_List.htemplate ...
- 【数据结构】线性表的链式存储-双链表
引言 单链表结点中只有一个指向其后继的指针,这使得单链表只能从头结点依次顺序地向后遍历.若要访问某个结点的前驱结点(插入.删除操作时),只能从头开始遍历 ,访问后继结点的时间复杂度为 0(1),访问前 ...
- Go 学习笔记(80)— Go 标准库 container/list(单链表、双链表)
列表是一种非连续存储的容器,由多个节点组成,节点通过一些变量记录彼此之间的关系.列表有多种实现方法,如单链表.双链表等. 在 Go 语言中,将列表使用 container/list 包来实现,内部 ...
- 《程序员代码面试指南》第二章 链表问题 在单链表和双链表中删除倒数第K个节点...
题目 在单链表和双链表中删除倒数第K个节点 java代码 /*** @Description:在单链表和双链表中删除倒数第K个节点* @Author: lizhouwei* @CreateDate: ...
- 设计链表(单链表、双链表)
707. 设计链表 设计链表的实现.您可以选择使用单链表或双链表.单链表中的节点应该具有两个属性:val 和next.val 是当前节点的值,next 是指向下一个节点的指针/引用.如果要使用双向链表 ...
- 线性表的链式存储结构及代码实现(单链表,双链表,循环链表)
在上一篇博文中介绍了线性表的顺序存储方式,它最大的缺点就是在插入和删除操作时会移动大量的元素,这显然会耗费很多时间.后来人们便想到了用链式存储方式来解决上面这一问题.链式存储线性表时,不需要使用地 ...
- python创建双链表_Python双链表原理与实现方法详解
本文实例讲述了Python双链表原理与实现方法.分享给大家供大家参考,具体如下: Python实现双链表 文章目录 Python实现双链表 单链表与双链表比较 双链表的实现 定义链表节点 初始化双链表 ...
- 数组实现链表和双链表
链表 链表是一种线性表 链表是一种物理存储单元上非连续.非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的.链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成 ...
最新文章
- 开源项目哪家强?Github年终各大排行榜超级盘点(内附开源项目学习资源)
- ThinkPHP微信实例——JSSDK图像接口多张图片上传下载并将图片流写入本地
- 笔记:Hadoop权威指南 第4章 Hadoop I/O
- ASP.NET Core官方计划路线及需要废除的一些Framework技术
- 一文攻破共用体-C语言
- ConcurrentModificationException并发修改异常
- SPOJ Python Day2: Prime Generator
- java怎么输出点,Java实现控制台输出两点间距离
- centos修改主机名的正确方法
- 【嵌入式】非操作系统下GPIO口控制器及LED灯编程
- 使用js获取页面参数
- perl Makefile.PL;make;make install 安装时报错
- 数据库索引失效与判断是否命中索引
- ie8 升级页面html,一个需要兼容到IE8的项目
- 【ENVI】利用矢量shp数据做裁剪报错及解决办法
- 三星识别文字_免费文字识别
- Android之Intent详解
- 电信移动信号测试软件,移动、联通、电信(信号强度大比拼)
- Win10使用局域网实现手机访问电脑共享文件
- python打九九乘法表上三角下三角_Python-零基础自学系列之九九乘法表、打印菱形、打印对顶三角形、打印闪电、斐波拉契数列、素数...
热门文章
- win下MySQL 8.0.11 修改密码、开启远程访问
- 浏览器的工作原理:新式网络浏览器幕后揭秘(转)
- 前端基础--jquery
- 解决Eclipse出现的Failure to transfer ... jar问题
- 本地管理表空间(LMT)
- Windows与Linux比较:相似与不同
- servlet 技术详解
- ASP.NET MVC入门教程(二)文章列表页、内容页的实现
- 提升研发效率 保障数据安全——阿里云宣布数据管理DMS企业版正式商业化 1
- 8-06. 畅通project之局部最小花费问题(35)(最小生成树_Prim)(ZJU_PAT)