0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

以后再也不怕别人问「单链表」的问题啦

算法与数据结构 来源:未知 作者:李倩 2018-11-23 11:30 次阅读

写在之前

在程序设计里,我们经常需要将同为某个类型的一组数据元素作为一个整体来使用,需要创建这种元素组,用变量来记录它们或者传入函数等等等等,「线性表」就是这样一组元素的抽象,它是某类元素的集合并且记录着元素之间一种顺序关系,是最基本的数据结构之一,在实际程序中运用非常广泛,比如 Python 中的 list 和 tuple 都可以看作是线性表的实现。

基于各种实际操作等方面的综合考虑,我们提出了两种实现线性表的形式:「顺序表」和「链表」。

「顺序表」是将表中的元素顺序存放在一大块连续的存储区间里,所以在这里元素间的顺序是由它们的存储顺序来表示的。「链表」则是将表中元素存放在一系列的结点中(结点的存储位置可以是连续的,可以是不连续的,也就意味着它们可以存在任何内存未被占用的位置),这些结点通过连接构造起来,结点分为「数据域」和「指针域」。这次我们要学习的「单链表」就是「链表」的一种实现形式,「数据域」保存着作为表元素的数据项,「指针域」保存同一个表里的下一个结点的标识。

在正式说「单链表」之前,我先来说一下很多人在学习链表之初都傻傻分不清的两个东西:「头结点」和「头指针」。

「头结点」的设立是为了操作的统一和方便,是放在第一个元素的节点之前,它的数据域一般没有意义,并且它本身也不是链表必须要带的。那设立头节点的目的是什么呢?其实就是为了在某些时候可以更方便的对链表进行操作,有了头结点,我们在对第一个元素前插入或者删除结点的时候,它的操作与其它结点的操作就统一了。

「头指针」顾名思义,是指向链表第一个结点的指针,如果有头结点的话,那么就是指向头结点的指针。它是链表的必备元素且无论链表是否为空,头指针都不能为空,因为在访问链表的时候你总得知道它在什么位置,这样才能通过它的指针域找到下一个结点的位置,也就是说知道了头指针,整个链表的元素我们都是可以访问的,所以它必须要存在,这也就是我们常说的「标识」,这也就是为什么我们一般用头指针来表示链表。

单链表

n 个结点链接成一个链表,这也就是平时书上所说的「链式存储结构」,因为这个链表中的每个结点中只包含一个指针域,所以又叫「单链表」。单链表正是通过每个结点的指针域将线性表的数据元素按其逻辑次序链接在一起。单链表的第一个结点的存储位置叫做「头指针」,最后一个结点的指针为「空」,一般用 “^” 表示。

上图是不带头结点的单链表,下面我们来看一下带头结点的单链表:

还有一种是空链表:

通过上面 3 个图我们发现无论单链表是否为空,是否有头结点,头指针都是存在的,这就很好的印证了之前我们所说的「头指针是链表的必备元素且无论链表是否为空,头指针都不能为空」。

为了方便后续的操作,我们一般会先定义一个简单的结点类:

class Node(object): def __init__(self,data): self.data = data self.next = None

单链表的基本操作

首先我们先来创建一个链表类:

class LinkList(object): def __init__(self): self.head = Node(None) # 判断链表是否为空 def IsEmpty(self): p = self.head # 头指针 if p.next == None: print("List is Empty") return True return False # 打印链表 def PrintList(self): if self.IsEmpty(): return False p = self.head while p: print(p.data,end=' ') p = p.next

1.创建单链表

创建单链表的过程其实就是一个动态生成链表的过程,说简单点就是从一个「空链表」开始,依次建立各个元素的结点,并把它们逐个插入链表,时间复杂度为 O(n):

def InitList(self,data): self.head = Node(data[0]) # 头结点 p = self.head # 头指针 for i in data[1:]: node = Node(i) p.next = node p = p.next

下面我们来测试一下:

# testlst = LinkList()data = [1, 4, 5, 8, 2, 3]lst.InitList(data)lst.PrintList()

输出结果如下:

1 4 5 8 2 3

2.计算单链表的长度

在使用链表的时候,经常需要求表的长度,为此我们可以创建一个球表长的函数,这个函数就是从左到右扫描,遍历表中的所有结点并完成计数,时间复杂度为 O(n):

def LengthList(self): if self.IsEmpty(): return 0 p = self.head cnt = 0 while p: cnt += 1 p = p.next return cnt

下面我们来测试一下:

# testlst = LinkList()data = [1, 4, 5, 8, 2, 3]lst.InitList(data)print(lst.LengthList())

输出的结果如下:

6

3.单链表的插入

假设我们要将结点 s 插入到 结点 p 的后面,只需要将结点 s 插入到结点 p 和 结点 p.next 之间即可,说起来简单,那么到底如何插入呢?请看下图:

由上图我们可以看到,单链表结点的插入根本不需要惊动其它结点,只需要让 s.next 和 p.next 的指针稍作改变即可。让 p 的后继结点改为 s 的后继结点,再把 s 的后继结点变成 p 的后继结点。这里一定要切记,插入操作的顺序不能改变,至于为什么,你可以拿起纸笔手动的画一下,结果一下子就会出来(对于单链表的表头和表尾的特殊情况,操作是相同的)。

# 单链表的插入(在第 s 个结点后面插入 data)def InsertList(self,s,data): if self.IsEmpty() or s < 0 or s > self.LengthList(): print("Insert failed!") return p = self.head index = 1 while index < s:        p = p.next        index += 1    node = Node(data)    node.next = p.next    p.next = node

下面我们来测试一下:

# testlst = LinkList()data = [1, 4, 5, 8, 2, 3]lst.InitList(data)lst.InsertList(0,666)lst.PrintList()

输出的结果如下:

1 666 4 5 8 2 3

4.单链表删除

看完插入,我们现在再来看看单链表的删除。假设我们想要删除一个结点 q,其实就是将它的前继结点 p 的指针绕过 q,直接指向 q 的后继结点即可,具体操作如下图所示:

由上图可以看出,我们只需要一步就可以实现删除操作,那就是让 p.next 直接为 p 的 next 的 next,p 的 next 为 q,所以也就是 p.next = q.next,时间复杂度为 O(n)。

# 单链表的删除(删除第 s 个结点)def DeleteList(self, s): if self.IsEmpty() or s < 0 or s > self.LengthList(): print("Delete failed! ") return p = self.head index = 1 while index < s:        pre = p        index += 1        p = p.next    pre.next = p.next    p = None

由 p = None 可以看出,在 Python 中,只需要简单的将指针赋值为 None,就抛弃了链表原有的结点,Python 解释器的存储管理系统会自动回收不用的存储。

下面我们来测试一下:

# testlst = LinkList()data = [1, 4, 5, 8, 2, 3]lst.InitList(data)lst.DeleteList(3)lst.PrintList()

输出的结果如下:

1 4 8 2 3

5.单链表的读取

在顺序结构中,我们想要获取任意一个元素的存储位置是很容易的,但是在单链表中,第 i 个元素到底在哪我们一开始没办法知道,只能傻傻的从头开始找,所以在对于单链表获取第 i 个元素的操作,算法上相对麻烦一些。

# 单链表的读取(获取第 s 个结点的值)def GetList(self, s): if self.IsEmpty() or s < 0 or s > self.LengthList(): print("Read failed! ") return p = self.head index = 1 while index < s:        index += 1        p = p.next    print("第 {} 个值为 {}".format(s, p.data))

从上面的代码我们可以很清楚的看出,单链表获取第 i 个元素就是从头开始找,知道第 i 个元素为止,所以我们可以很容易的估算出它的时间复杂度是 O(n)。任何事物都不是完美的,有好的地方就有坏的地方,元素的读取就是单链表美中不足的地方之一。

写在之后

单链表的操作其实还有不少,我只是写了其中常用的几种,希望大家能自己动手尝试一下,把这几个搞懂搞透。碰到这样的问题从哪个方面去思考,如何去做才是最重要的,只有学会了这些,你在日后碰到相关问题的时候就知道如何去下手。

我在上面每个操作的讲解中大多数给出了图,通过图来看解法题目了然。算法这个东西其实就是这样,多动手实现以下,想不明白了就动手画一下,画着画着思路就出来了。

最后我们就来总结一下链表操作的时间复杂度,如果你还不会估算算法的时间复杂度,请看我的循序渐进带你学习时间复杂度和空间复杂度。

创建空表 O(1)。

创建单链表 O(n)

插入元素:首端插入为 O(1);尾端插入为 O(n),因为还要找到表的最后结点;定位插入 为O(n)。

删除元素:首端删除为 O(1);尾端删除为 O(n),理由如上;定位删除为 O(n)。

以下是上述所有操作的代码汇总:

# 结点类class Node(object): def __init__(self,data): self.data = data self.next = None# 链表类class LinkList(object): def __init__(self): self.head = Node(None) # 判断链表是否为空 def IsEmpty(self): p = self.head # 头指针 if p.next == None: print("List is Empty") return True return False # 打印链表 def PrintList(self): if self.IsEmpty(): return False p = self.head while p: print(p.data,end= ' ') p = p.next # 创建单链表 def InitList(self,data): self.head = Node(data[0]) # 头结点 p = self.head # 头指针 for i in data[1:]: node = Node(i) p.next = node p = p.next # 单链表的长度 def LengthList(self): if self.IsEmpty(): return 0 p = self.head cnt = 0 while p: cnt += 1 p = p.next return cnt # 单链表的插入(在第 s 个结点后面插入 data) def InsertList(self,s,data): if self.IsEmpty() or s < 0 or s > self.LengthList(): print("Insert failed!") return p = self.head index = 1 while index < s:            p = p.next            index += 1        node = Node(data)        node.next = p.next        p.next = node    # 单链表的删除(删除第 s 个结点)    def DeleteList(self, s):        if self.IsEmpty() or s < 0 or s > self.LengthList(): print("Delete failed! ") return p = self.head index = 1 while index < s:            pre = p            index += 1            p = p.next        pre.next = p.next        p = None    # 单链表的读取(获取第 s 个结点的值)    def GetList(self, s):        if self.IsEmpty() or s < 0 or s > self.LengthList(): print("Read failed! ") return p = self.head index = 1 while index < s:            index += 1            p = p.next        print("第 {} 个值为 {}".format(s, p.data))

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 单链表
    +关注

    关注

    0

    文章

    13

    浏览量

    6918
  • 链表
    +关注

    关注

    0

    文章

    80

    浏览量

    10558

原文标题:以后再也不怕别人问「单链表」的问题啦。

文章出处:【微信号:TheAlgorithm,微信公众号:算法与数据结构】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    数据结构:链表的排序

    给定一个链表的头结点head(该结点有值),长度为n的无序链表,对其按升序排序后,返回新链表。如当输入
    的头像 发表于 11-30 13:56 1586次阅读
    数据结构:<b class='flag-5'>单</b><b class='flag-5'>链表</b>的排序

    3D打印,又创新奇。妈妈再也不用担心我乱丢铅笔。...

    支。那天在某论坛找到一个笔筒的STL文件,觉得很好看,就用机器试了一下,嘻嘻。。果然很漂亮。再也不用担心找不到笔了,而且放在桌上还是一道美丽的风景线呢。跟大家分享一下哈。。很漂亮。真的不是盖的,哇咔咔。。。开心,我的桌子更加漂亮干净咯。越来越喜欢我的机器,也越来越佩服自
    发表于 12-02 15:37

    Compass打破次元壁,哆A梦新法宝

    compass差点“惨遭毒嘴”!!!!Compass再也不能保持沉默Compass纳入百宝袋条件一:只有34g,够小够轻便,不占“百宝袋”地方,无负荷携带。纳入百宝袋条件二:小身板却能超长待机,就算长期蹲在
    发表于 01-30 09:51

    2016年1月参加分享资料获得奖品

    `2016年1月参加分享资料获得奖品,好丰盛的大餐,很喜欢那个秤,手感不错,以后再也不怕吃多了看官们,上菜了1.包装很整齐干净,感谢QQ185XX3980,工作认真,态度好,点个赞2.来个全家福,两份礼品哦3.我喜欢的秤“妈妈再也不
    发表于 03-17 21:44

    兑换的哈尔斯口杯-抵抗寒冷再也不怕

    ` 本帖最后由 andyyau 于 2016-10-18 21:12 编辑 最近天气逐渐变冷,发现自己的水杯放一晚水后早上不怎么热了,用了几年了,改退役了。正好兑换一个新杯子,冬天再也不用喝凉水
    发表于 10-18 21:11

    烧录失败导致boot无法加载的解决措施,再也不怕烧成砖了

    单独烧写fastboot,然后再来擦除boot即可重新执行烧系统镜像操作,如下图:到此,板子就又恢复正常了,后面可以放心烧录系统了,再也不用担心板子成砖了
    发表于 03-03 11:45

    链表的缺陷是什么

    链表有一定的缺陷,就是单向性,只能从一个结点到下一个节点,而不能访问到上一个结点,而循环链表就可以解决这一问题,当然,用双向链表更加方便#include #include typed
    发表于 07-14 08:09

    C语言实现链表举例

    所谓链表,就是用一组任意的存储单元存储线性表元素的一种数据结构。链表又分为链表、双向链表和循环链表
    发表于 07-11 16:40 87次下载
    C语言实现<b class='flag-5'>单</b><b class='flag-5'>链表</b>举例

    利用机器学习在15分钟内破解验证码,再也不担心买不到12306的票

    再也不用输入验证码了!
    的头像 发表于 03-06 17:00 6529次阅读
    利用机器学习在15分钟内破解验证码,<b class='flag-5'>再也不</b>担心买不到12306的票<b class='flag-5'>啦</b>

    链表学习的超详细说明(二)

    昨天跟大家分享了链表的一些基本用法,今天接着继续和大家分享链表的用法,今天分享完,链表的操
    的头像 发表于 12-24 17:33 765次阅读

    有了这款 GitHub 开源神器,以后再也不怕找不到电影资源

    “ 阅读本文大概需要 3 分钟。 ”大家好。相信各位都知道,平时上网找电影是个挺费时费力的活,为了要找到清晰度高、无水印、多音轨、多语言字幕的电影,经常需要在几个网站平台来回切换,互相对...
    发表于 01-10 11:12 0次下载
    有了这款 GitHub 开源神器,<b class='flag-5'>以后</b><b class='flag-5'>再也不怕</b>找不到电影资源<b class='flag-5'>啦</b>!

    牢记以下几点 再也不怕买到劣质光纤跳线

    光纤是一种利用光在玻璃或塑料纤维中的全反射而制成的光传导媒介,由于其频率、带宽大,损耗低,已成为现代信息传输领域中的最佳工具,为了帮助大家正确选择产品,牢记以下几点 再也不怕买到劣质光纤跳线。
    的头像 发表于 12-05 09:44 1121次阅读

    医疗废物在线监管系统医院再也不怕医废丢失了

    医疗废物在线监管系统医院再也不怕医废丢失了 《医疗废物分类目录》将医疗废物分为六类:感染性废物、病理性废物、损伤性废物、药物性废物和化学性废物。医疗机构和医疗废物集中处置单位在收集和处置医疗废物
    的头像 发表于 06-01 17:09 523次阅读
    医疗废物在线监管系统医院<b class='flag-5'>再也不怕</b>医废丢失了

    链表和双链表的区别在哪里

    链表和双链表的区别 链表的每一个节点中只有指向下一个结点的指针,不能进行回溯。 双链表的每一
    的头像 发表于 07-27 11:20 1646次阅读
    <b class='flag-5'>单</b><b class='flag-5'>链表</b>和双<b class='flag-5'>链表</b>的区别在哪里

    实用的单片机接反电路,再也不怕电源接反了

    实用的单片机接反电路,再也不怕电源接反了
    的头像 发表于 10-17 17:32 1067次阅读
    实用的单片机接反电路,<b class='flag-5'>再也不怕</b>电源接反了