数据结构算法入门--链表

640?wx_fmt=jpeg

2019 年第 76 篇文章,总第 100 篇文章

本文大约 3200 字,阅读大约需要 10 分钟

数据结构算法系列:

  1.  
  2.  

数据结构算法入门系列第三篇--链表,链表也是非常常见的数据结构,面试过程中也会经常考到相关的题目。

本文首先介绍链表的基本情况,比如单向链表、双向链表等,然后会介绍一些技巧。

今日推荐阅读:

 


链表也是一个非常常见的数据结构,和数组相比,它不需要连续的内存空间,对内存的要求不高。

链表结构非常多,这里介绍常见的三种结构:单链表、双向链表和循环链表。

单链表

链表是通过指针将一组零散的内存块串联在一起,其中,内存块称为链表的 "结点",如下图所示,结点分为两个部分,存储数据以及记录下一个结点的地址的指针,这个指针也称为后继指针 next

640?wx_fmt=png

从图中可以看到有两个结点比较特殊,头结点和尾结点。其中,头结点保存链表的基地址,而尾结点的指针指向一个空地址 NULL

链表也是支持查找、插入和删除操作的,对于链表的插入和删除操作,可以如下图所示,插入和删除操作,其实仅仅需要改变相邻结点的指针,对应的时间复杂度是 O(1)

640?wx_fmt=png

当然了,有利就有弊,和数组可以实现快速随机访问操作相反,链表这操作需要 O(n) 的时间复杂度。因为链表因为不是连续的内存块,所以不能根据首地址和下标,通过寻址公式计算得到目标位置的内存,只能从首结点开始遍历每个结点,直到找到目标结点。

循环链表

介绍完单链表,第二个介绍的就是升级版--循环链表。

循环链表,和单链表的区别主要是**尾结点指向的是链表的头结点,**如下图所示,因此循环链表构成一个环,因此称为循环链表。

640?wx_fmt=png

循环链表的优点就是从链尾到链头比较方便。它适合解决具有环型结构特点的数据,比如著名的约瑟夫问题[^1]。

双向链表

第三个升级版--双向链表,也是比较常用的一种链表结构。它的特点就是每个结点包含两个指针,分别指向前一个结点和后一个结点,也就是两个方向,所以称为双向链表,如下图所示:

640?wx_fmt=png

相比于单链表,因为多一个指针,所以双向链表会占用更多的内存空间,但它也更加灵活,此外,它可以在 O(1) 时间复杂度的情况下找到前驱结点,在某些情况下,插入、删除等操作会比单链表更加简单和高效。

尽管介绍单链表的插入和删除操作,提到其时间复杂度是 O(1) ,但这里是有一个前提的,就是仅仅插入或者删除操作,并没有考虑查找的时间,如果结合查找到结点并插入或者删除操作,那么时间复杂度应该是 O(n) 。

而双向链表可以在需要查找结点的前驱结点时候,比单链表更加高效。

这也是一个重要的设计思想--空间换时间。当内存空间充足的时候,如果追求速度,可以采用一些时间复杂度相对比较低,但空间复杂度相对比较高的算法或者数据结构;当然如果内存空间不足,那就反向考虑,时间换空间的设计思路。

比较经典的例子就是缓存,事先将数据加载在内存中,尽管会比较耗费内存空间,但查找速度就大大提高了。

总结一下,对于执行较慢的程序,可以采用空间换时间的思路优化;而消耗过多内存的程序,可以通过时间换空间的思路来优化。

双向链表还可以和循环链表结合--双向循环链表,如下图所示:

640?wx_fmt=png

链表 vs 数组性能大比拼

和数组进行对比,两者在插入、删除、随机访问操作的时间复杂度是正好相反的,如下表所示:

时间复杂度数组链表
插入&删除O(n)O(1)
随机访问O(1)O(n)

当然,并不能仅仅通过时间复杂度来对比数组和链表,实际应用需要考虑更多的因素。

数组的优缺点:

  • 优点:简单易用,采用连续的内存空间,可以借助 CPU 的缓冲机制,预读数组中的数据,访问效率更高

  • 缺点:大小固定,一经声明就需要占用整块连续内存空间,占用空间过大和过小都有各自的问题。

而链表的优缺点其实刚好相反:

  • 优点:没有限制大小,天然支持动态扩容;

  • 缺点:占用的内存并不是连续存储,对 CPU 缓存不友好,无法有效预读,访问效率不高。

链表技巧

1. 理解指针或引用的含义

有些语言,比如 C 语言,有指针的概念;但有些语言没有指针,取而代之的是“引用”的概念,比如 Python。不过,这两者表示的意思都一样,都是存储所指对象的内存地址

实际上,对于指针或者引用的理解,只需要记住这句话:

将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,也可以说,指针存储了这个变量的内存地址,指向了这个变量,可以通过指针来找到这个变量。

用代码举例说明,比如:p->next= q ,这段代码表示 p 结点的 next指针指向了 q 结点的内存地址。

更复杂点的例子,p->next=p->next->next ,这段代码表示 p 结点的 next 指针存储了 p 结点的下下一个结点的内存地址。

2. 警惕指针丢失和内存泄露

对于链表,最重要的是确保指针指向正确的结点,一旦写错,就会导致丢失指针,那么这种情况一般是怎么发生的呢,下面给出一个单链表插入操作的例子,如下图所示:

640?wx_fmt=png

 

上述例子是希望在结点 a 和 b 之间插入新的结点 x,假设当前指针 p 指向结点 a,如果采用下面的代码,那么就会发生指针丢失和内存泄露。

p->next = x;  // 将 p 的 next 指针指向 x 结点;	
x->next = p->next;  // 将 x 的结点的 next 指针指向 b 结点;

这段代码是这样执行的:

  1. 首先指针 p->next 会指向结点 x;

  2. 接着,x->next 执行 p->next,也就是说 x->next 指向结点 x,也就是结点 x 自己构成一个闭环了,那么后续结点都无法访问了。

正确的代码,其实是将上述代码交换执行顺序,即先让 x->next 指向 p->next,也就是结点 b,然后再将 x 赋值给 p->next。

所以,插入结点,需要注意操作的顺序;而删除结点,对于没有内存管理的编程语言,需要手动释放内存空间。

3. 利用哨兵简化实现难度

正常的单链表的插入操作,代码实现如下所示,

new_node->next = p->next	
p->next = new_node

但如果是在链表首部插入新的结点,则必须单独处理:

if head == null:	head = new_node

同理对于删除结点操作,代码如下所示,也就是对链表最后一个结点需要特殊处理。

if head->next == null:	head = null	
p->next = p->next->next

那么是否有办法可以不用单独处理呢,这里就可以采用哨兵,即引入哨兵结点,在任何时候,不管链表是否为空,head 指针都会一直指向这个哨兵结点。这种带有哨兵结点的链表叫做带头链表,没有带的则是不带头链表。如下所示:

640?wx_fmt=png

4. 重点留意边界条件处理

通常在边界或者异常情况下,最容易产生 Bug。对于链表,也不例外,在写代码过程和写完后,都需要检查代码添加是否考虑全面,以及代码在边界条件下能否正常运行。

通常用于检查链表代码是否正确的边界条件有这几个:

  • 如果链表为空,是否能正常工作?

  • 如果链表只有一个结点,是否可以正常工作?

  • 如果链表包含两个结点,是否可以正常工作?

  • 代码逻辑在处理头结点和尾结点的时候,是否可以正常工作?

当然,边界条件并不局限上述这些,不同场景,还有特点的边界条件。

5. 举例画图,辅助思考

对于复杂的链表操作,比如单链表反转,可以采用举例法画图法进行辅助。

比如,对于链表插入的操作,如下图给出不同情况下,插入前后的链表变化:

640?wx_fmt=png

通过举例和画图,会非常直观形象的了解应该如何用代码实现相应的操作。

6. 多写多练,没有捷径

最重要的还是多写多练,不断总结错误。


参考:

  • 极客时间的数据结构与算法之美课程

欢迎关注我的微信公众号--算法猿的成长,或者扫描下方的二维码,大家一起交流,学习和进步!

640?wx_fmt=png

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/408479.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

react学习(3)----不能在该位置用setstate

this.setState({ pageIndex: 1, pageSize: 10, });

后台命名查询sql查某几个字段传到前台

dwr调用查出数据库字段 传给前台显示 前台只能接受list 后台数据放进list传入前台 name-quary中 <!-- 定义sql 通过单位id 查单位名称 --> <sql-query name"getStationName"> <![CDATA[ select STATION_ID,STATION_NAME from MF_STATION wher…

带你少走弯路:强烈推荐的Keras快速入门资料和翻译(可下载)

上次写了TensorFlow和PyTorch的快速入门资料&#xff0c;受到很多好评&#xff0c;读者强烈建议我再出一个keras的快速入门路线&#xff0c;经过翻译和搜索网上资源&#xff0c;我推荐4份入门资料&#xff0c;希望对大家有所帮助。备注&#xff1a;另外两个入门资料很负责任地说…

hadoop遇到的问题及处理

1:杀掉hadoop作业 列出作业 ./hadoop job -list杀掉 ./hadoop job -kill job_id1&#xff1a;某些节点出现running asprocess XXX. Stop it first 这是由于各节点登录用户为root&#xff0c;在启动hadoop前&#xff0c;务必将各节点用户切换至普通用户hadoop下&#xff0c;切换…

Mac 下安装配置 Python 开发环境

图片来源&#xff1a;Unsplash&#xff0c;作者 Markus Spiske 2019 年第 77 篇文章&#xff0c;总第 101 篇文章前言记录下 Mac 电脑的开发环境安装配置&#xff0c;主要包括&#xff1a;安装&使用Homebrew安装使用 git安装 anaconda&#xff0c;配置 python3 环境安装 ju…

react学习(5)----通过设置初始值控制页面render渲染

boothActivityCode: this.props.location.query.code || ,

【Android源代码下载】收集整理android界面UI效果源码

在Android开发中&#xff0c;Android界面UI效果设计一直都是很多童鞋关注的问题&#xff0c;今天给大家分享下大神收集整理的多个android界面UI效果&#xff0c;都是源码&#xff0c;都是干货&#xff0c;贡献给各位网友&#xff01; 话不多说&#xff0c;直接上效果图&#xf…

一文了解类别型特征的编码方法

来源&#xff1a;Unsplash&#xff0c;作者&#xff1a;an Rizzari2019 年第 78 篇文章&#xff0c;总第 102 篇文章目录&#xff1a;问题描述数据准备标签编码自定义二分类one-hot 编码总结问题描述一般特征可以分为两类特征&#xff0c;连续型和离散型特征&#xff0c;而离散…

UNIX网络编程--读书笔记

会集中这段时间写UNIX网络编程这本书的读书笔记&#xff0c;准备读三本&#xff0c;这一系类的文章会不断更新&#xff0c;一直会持续一个月多&#xff0c;每篇的前半部分是书中讲述的内容&#xff0c;每篇文章的后半部分是自己的心得体会&#xff0c;文章中的红色内容是很重要…

react学习(6)----react样式多用内联

<div style{{ textAlign: center }}><Button type"primary" style{{ marginRight: 10px }}>保存</Button><Button>取消</Button><Button type"primary" style{{ marginLeft: 10px }}>发布</Button></div>…

如何用栈实现浏览器的前进和后退?

2019 年第 79 篇文章&#xff0c;总第 103 篇文章数据结构与算法系列的第四篇文章&#xff0c;前三篇文章&#xff1a;前言浏览器的前进和后退功能怎么用栈来实现呢&#xff1f;这里先介绍一下栈的定义和实现&#xff0c;并介绍它的一些常用的应用&#xff0c;最后再简单实现一…

iOS开发Objective-C基础之──多态

Objective-C语言是面向对象的高级编程语言&#xff0c;因此&#xff0c;它具有面向对象编程所具有的一些特性&#xff0c;即&#xff1a;封装性、继承性和多态性。 今天介绍一下Objective-C中的多态性。 一、什么是多态 多态&#xff1a;不同对象以自己的方式响应相同的消息的能…

react学习(7)----react转换值同render

{title: 状态,dataIndex: status,render: (text, row) > {let arr [, 未开始, 进行中, 已结束, 已作废];return <span>{arr[text]}</span>;},},

数据科学家令人惊叹的排序技巧

2019 年第 80 篇文章&#xff0c;总第 104 篇文章本文大约 7800 字&#xff0c;阅读大约需要20分钟原题 | Surprising Sorting Tips for Data Scientists作者 | Jeff Hale原文 | https://towardsdatascience.com/surprising-sorting-tips-for-data-scientists-9c360776d7e译者 …

删除Autorun.inf的方法

你的电脑的每个分区根目录都有一个autorun.inf的文件夹&#xff0c;查看属性是只读隐藏&#xff0c;且无法删除、无法取得权限!点进去&#xff0c;却显示的是控制面板的内容? 其实这个不是病毒&#xff0c;而是用来防病毒&#xff0c;一些系统封装工具本身就自带。下面教你删…

react学习(8)----数组方法fliter简介

filter() 方法创建一个新的数组&#xff0c;新数组中的元素是通过检查指定数组中符合条件的所有元素。 注意&#xff1a; filter() 不会对空数组进行检测。 注意&#xff1a; filter() 不会改变原始数组。

几个有趣的python技巧

2019 年第 82 篇文章&#xff0c;总第 106 篇文章标题 | python-is-cool作者 | chiphuyen原文 | https://github.com/chiphuyen/python-is-cool译者 | kbsc13("算法猿的成长"公众号作者)声明 | 翻译是出于交流学习的目的&#xff0c;欢迎转载&#xff0c;但请保留本文…

react学习(9)----react生命周期

react生命周期1.1.constructor() constructor()中完成了React数据的初始化&#xff0c;它接受两个参数 &#xff1a;props和context&#xff0c;当想在函数内部使用这两个参数时 &#xff0c;需使用super()传入这两个参数。 注意&#xff1a;只要使用了constructor()就必须写su…

TOP 命令

转自&#xff1a;top命令.http://www.cnblogs.com/wangkangluo1/archive/2012/04/18/2454993.html#stat.2013-05-25 top命令是Linux下常用的性能分析工具&#xff0c;能够实时显示系统中各个进程的资源占用状况&#xff0c;类似于Windows的任务管理器。下面详细介绍它的使用方法…