python数据结构与算法系列

本文是关于Python数据结构与算法系列的一部分,详细介绍了链表的概念、单向链表的节点实现、操作以及链表与顺序表的对比。内容包括链表的动态内存管理优势,单向链表的节点结构,链表操作如添加、删除、查找等的代码实现,并分析了链表与顺序表在操作复杂度上的差异。
摘要由CSDN通过智能技术生成

python数据结构与算法系列


链表

我们之前介绍了顺序表的相关内容,了解到如果要构建顺序表就需要知道数据的大小,然后再申请存储空间,而且在扩充时也需要进行数据搬迁,使用起来很不方便。

因此我们引入链表的概念,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。

链表是一种常见的基础数据结构,是一种线性表,但其存储数据的方式与顺序表不同,链表是在每个节点里存放下一个节点的位置信息,如下图所示。
在这里插入图片描述

单向链表

单向链表又称单链表,是链表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。
在这里插入图片描述
elem表示元素域,用来存放数据
next表示下一节点链接域,存放下一节点的地址
p指向链表的头节点的位置,通过p可以找到任一节点

节点实现

在这里插入图片描述

单链表的操作

is_empty() 链表是否为空
length() 链表长度
travel() 遍历整个链表
add(item) 链表头部添加元素
append(item) 链表尾部添加元
insert(pos, item) 指定位置添加元素
remove(item) 删除节点
search(item) 查找节点是否存在

代码实现

接下来,我们来看一下如何用代码实现单链表。

根据之前的介绍,我们可以先将每一个节点进行封装,然后再将其一个个连接起来。对于单个节点,之前已经用代码实现了,那么根据之前介绍的单链表的操作,我们可以依次把他们写出来。
在这里插入图片描述

如上图所示,首先我们得初始化一个表头,并让这个表头指向None,一个表头相当于一个起始点,有了表头才方便进行后续操作。然后就是之前介绍的,各个操作,接下来我们依次进行具体的代码实现。
首先,对于判断链表是否为空,因为初始就设置是空的,所以我们直接判断表头所指的是否为空,并将结果返回。
在这里插入图片描述
接下来我们来计算链表的长度,在这里为了方便我们定义一个游标,用来遍历计数,记为cur,那cur首先应当指向我们初始化的那个表头。然后我们定义一个count用来计数,我们先将其初始化为0.接着我们需要遍历整个链表,我们写一个while循环,cur每指向一个节点,count就该加1,cur再指向下一个节点,直到cur下一个指向的节点为None时,循环结束。这里注意,循环的条件是cur不等于None,而不是cur.next不为None,否则最后一个节点将不会被计入。最后将统计的count返回,具体代码如下:
在这里插入图片描述
与计算链表长度类似,遍历链表只是不需要计数,而是打印出cur每次所指向的节点的元素即可。
在这里插入图片描述
对于在尾部添加节点,我们首先应该定义一下这个要添加的节点,记为node,然后我们要考虑到一个链表为空的特殊情况,如果链表为空,直接让head指向这个节点就好了。如果不为空,那和之前介绍的类似,我们有需要遍历链表,并且在遍历到最后一个节点时,将最后一个节点next区域指向新增的节点就好了,具体代码如下:
在这里插入图片描述
接下来我们考虑从头部开始插入节点,我们不可以先让head指向要插入的节点,否则就会和先前head所指向的链表断开。我们应当让新的节点的next域先指向head所指向的第一个节点,然后再让head指向新的节点,即完成了头插,具体代码如下:
在这里插入图片描述
那如果我们想在任意位置插入节点呢,这时我们首先还是定义这个新节点,记为node,要插入的位置记为pos,然后和之前类似,我们需要一个游标方便遍历,我们知道如果你想在一个位置插入节点,那其实要在你输入的这个位置的前一个位置进行操作,为了和之前区别,我们将游标记为pre。接下来考虑,我们应该将新节点的next域指向我们要插入的那个位置先前的节点,而那个位置的前一个节点的next域指向新的节点。我们同样需要循环,与之前类似,我们需要一个count用来计数,每遍历一个节点就加1,当count小于pos-1时,跳出循环。此外,我们还应当考虑些特殊情况,比如当pos小于等于0的时候,相当于要在链表头部添加,那这个时候就可以调用我们之前写过的头插法;如果pos比整个链表的长度都大,那我们可以认为是想在最后插入,也可以直接调用我们之前写过的尾插法。整个思路基本就是这样,具体代码如下:
在这里插入图片描述
那我们有了这么多节点,如果不记得某个节点在不在了该怎么办呢?这时我们再实现一个查找功能,与之前的类似,我们还是需要使用游标来进行遍历,如果遍历到的节点的值和你想找的相等,就返回一个True,否则继续找,如果没有找到就返回一个False,具体代码如下:
在这里插入图片描述
如果我们想删除某个节点呢?我们先来考虑一般情况,为了方便理解,我们还是需要两个游标:一个pre,一个cur,cur指向当前节点,pre指向当前节点的前一个节点。在刚开始时,我们让cur指向head,让pre指向None,然后我们开始遍历,如果cur指向的那个节点是我们想删除的,我们就让pre的next域指向cur的next域,这样就跳过了当前节点,也就相当于删除。如果不是,我们就继续遍历,让pre指向cur,而让cur指向下一个节点。
那如果我们想删除的是头节点,那最开始pre并不存在next域,上述方法没法使用了,因此我们应当直接让head指向cur的next域,也就是头节点的下一个节点。即可完成所有的删除情况,具体代码如下:
在这里插入图片描述
到此为止,我们就基本实现了单链表的基本操作,我们来做一个总结。

链表与顺序表对比

链表失去了顺序表随机读取的优点,同时链表由于增加了节点的指针域,空间开销比较大,但对存储空间的使用要相对灵活。

链表与顺序表的各种操作复杂度如下所示:
在这里插入图片描述
虽然表面看起来复杂度都是 O(n),但是链表和顺序表在插入和删除时进行的是完全不同的操作。
链表的主要耗时操作是遍历查找,删除和插入操作本身的复杂度是O(1)。
顺序表查找很快,主要耗时的操作是拷贝覆盖。因为除了目标元素在尾部的特殊情况,顺序表进行插入和删除时需要对操作点之后的所有元素进行前后移位操作,只能通过拷贝和覆盖的方法进行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值