链表1-单链表(Python实现)

本文详细介绍了单链表的定义、实现及各种操作,包括初始化、判断空表、插入元素(表头、定位、表尾)、删除元素(表头、定位、表尾)、扫描定位遍历、求链表长度、链表反转和排序。所有操作均在Python中实现,重点讨论了时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、链表定义

1、线性表需求

线性表的基本需求有两点:

  • 能够找到线性表的首元素(head)。
  • 从线性表的任何一个元素开始,能够找到它之后的下一个元素(next)。

2、什么是链表(链接表)

基于链接技术实现的线性表称为链接表(简称 链表)。链接技术实现原理:

  • 把表中的元素 分别 存储在 独立的存储块(称为链表的 结点 )中。
  • 在前一节点中 显示 的记录下一节点。

二、单链表(single linked list)

1、定义

单向链表 是链接方向为单向的链表。简称 单链表 或者 链表,一般情况下,我们说的链表也是指单向链表。一个单链表的 结点 包含两个部分,第一个部分(元素域)存储关于节点的信息(如表示元素的数据项),第二部分(链接域)存储下一个节点的地址。在这里插入图片描述

(图来自维基百科)

(1)表头变量(表头指针)

可以用一个变量表示单链表的首结点的引用(如上图的head),这样的变量称为表头变量或者表头指针。

(2)空链接

为了表示一个单链表的结束,最后一个节点的第二个部分需设置一个表示结束的值在Python里,用None表示,这个值称为 空链接

2、节点的算法实现

class LNode :
    def __init__(self, elm, nxt):
        self.elem = elm
        self.next = nxt

3、单链表的算法实现

这是一个最简单的单链表。

class LList:
    def __init__(self):
        """
        实例属性head表示首结点的引用
        """
        self.head = None

三、单链表的操作

操作都在类LList里面定义为方法。

1、初始化单链表

把表头变量head的值设置为None。

2、判断表是否为空

将表头变量head的值与空值进行比较。

 def is_empty(self):
        """判断单链表是否为空"""
        return self.head is None

3、插入元素

插入元素可以分为表头插入,定位插入,表尾插入。和顺序表的不同的是,在单向链表中插入元素时,并不需要移动已有的元素,而是修改链接,接入新的节点。

(1)表头插入

表头插入元素只需要两步即可完成。第一步:将原单链表首节点的链接存入新节点的链接域next。第二步:修改表头变量,使之指向新节点。

代码实现:

    def prepend(self, elem):
        """
        表头插入元素。
        :param elem: element
        """
        self.head = LNode(elem, self.head)

时间复杂度分析:

因为可以通过head直接找到首结点,所以时间复杂度为常量时间复杂度O(1)。

(2)定位插入

定位插入是指在链表的某个位置插入一个新结点。一般需要三步可以完成。第一步:从首结点开始找,找到该位置的前一个节点(pre)。第二步:将前一个节点原来所指向的链接域next存入新结点的链接域next。第三步:修改前一个结点的链接域next,使之指向新结点。

代码实现:

q = LNode(13) # 创建一个新节点
q.next = pre.next # 将前一个节点原来所指向的链接域next存入新结点的链接域next
pre.next = q # 修改表头变量,使之指向新节点。

时间复杂度:

定位插入元素的时间主要消耗在查找元素。假设单链表有n个结点,在各位置插入时,查找元素次数如下:

结点索引查找次数
0(表头插入)0
11
22
n(表尾插入)n

由上表可得,平均时间复杂度可以用高斯算法进行计算:
( 0 + n ) ∗ n 2 ( n + 1 ) \frac{(0+n)*n}{2(n+1)} 2(n+1)(0+n)n
即O(n)。

最坏时间复杂度(即表尾插入元素)为:O(n)

(3)表尾插入

空表:新结点next为空链接,head指向新结点。

非空链表:新结点next为空链接,原单链表的尾结点的next指向新结点,head不变。

代码实现:

 def append(self, elem):
        """
        表尾插入元素。
        :param elem: element
        """
        if self.head is None:
            self.head = LNode(elem, self.head)
            return
        p = self.head
        while p.next is not None:
            p = p.nex
        p.next = LNode(elem, None)

时间复杂度:

表尾插入即定位插入的最坏情况。总是从首结点开始找,直到找到最后一个结点,所以复杂度为O(n)。

4、删除元素

删除元素可以分为表头,定位删除,表尾删除和其它删除。

(1)表头删除

空表:抛出异常。

非空表:head指向原表的第二个结点,返回被删除的元素值。

代码实现:

 def pop(self):
        """
        删除表头元素
        :return: element that deleted
        """
        if self.head is None:
            raise ValueError
        e = self.head.elem
        self.head = self.head.next
        return e 

时间复杂度:

无论单链表有多少个元素,删除表头元素都是只要一步即可完成,所以时间复杂度为O(1)。

(2)定位删除

删除指定位置的元素需要两步实现:第一步:找到指定位置的结点的前一个结点(pre)。第二步骤:修改前一个结点的链接域next,使之指向被删除结点的下一个结点。在Python中,结点被删除后由解释器自动回收。

代码实现:

prex.next = pre.next.next # 等号右边的pre.next表示被删除的节点

时间复杂度分析:

同定位插入元素,删除指定位置的元素的时间也是主要消耗在查找元素。假设单链表有n个结点,删除指定位置元素时,查找元素次数如下:

结点索引查找次数
0(表头插入)0
11
22
n(表尾插入)n

由上表可得,平均时间复杂度可以用高斯算法进行计算:
( 0 + n ) ∗ n 2 ( n + 1 ) \frac{(0+n)*n}{2(n+1)} 2(n+1)(0+n)n
约等于O(n)。

最坏时间复杂度(即表尾插入元素)为:O(n)

(3)表尾删除

空表:抛出异常。

只有一个结点的表:设置head的值为空,返回被删除的元素。

包含两个以上结点的表:从表头开始,找到倒数第二个结点,设置倒数第二个结点的next为空连接。head不变。返回被删除的元素。

代码实现:

    def pop_last(self):
        """
        删除表尾元素
        :return:element that deleted
        """
        if self.head is None:
            raise ValueError

        p = self.head
        if p.next is None:
            e = p.elem
            self.head = None
            return e
        # p.next.next表示第二个结点的链接域next
        while p.next.next is not None:
            p = p.next
        e = p.next.elem
        p.next = None
        return e

时间复杂度:

表尾删除即定位删除的最坏情况。所以时间复杂度为O(n)。

6、扫描、定位和遍历

(1)扫描

从单链表的表头开始,找到符合条件的节点,这种操作过程称为单链表的 扫描

代码实现:


如上代码所示,循环种使用的辅助变量p称为扫描指针

(2)定位

定位分为两种,按下标定位和按元素定位。

确定第i个元素所在的节点称为按下标定位。按下表定位代码实现:

p = head
while p is not None and i > 0:
    i -= 1
    p = p.next

确定某个元素所在的节点称为按元素定位。按元素定位代码实现:

p = head
while p is not None and p.data == 5:
    p = p.next

(3)遍历

扫描所有的元素称为单链表的 遍历。代码实现:

    def printall(self):
        p = self.head
        while p is not None:
            print(p.elem, end='')
            if p.next is not None:
                print(', ', end='')
            p = p.next
        print('')

7、求单链表的长度

从首结点开始,直到结点链接域next为None.

    def length(self):
        """
        get the length of singly linked list
        :return: length
        """
        p = self.head
        count = 0
        while p is not None:
            count += 1
            p = p.next
        return count

时间复杂度为O(n)。

8、单链表反转

(1)法1:结点之间互相交换

略。

(2)法2:修改链接

将需要反转的链表的首结点不断取下,然后在另一链表的表头插入,最后将head指向新拼接的链表。

代码实现:

    def rev(self):
        """reverse the singly linked list"""

        p = None
        while self.head is not None:
            # 取下首结点
            q = self.head
            self.head = q.next
            # 设置结点的next为之前的取下的结点
            q.next = p
            # 重新给p赋值
            p = q
        self.head = p

算法分析:

首结点的取下和加入的操作时间为O(1),所以链表反转的时间复杂度为O(n)。

9、链表排序

采用插入排序进行链表排序。

(1)法一:元素(elem)交换

该方式的排序本质上是交换了原结点elem值,结点的链接顺序保持不变。

    def sort1(self):
        """通过交换元素排序"""
        if self.head is None:
            return
        # 第一个结点默认已经排序,从第二个结点开始
        crt = self.head.next
        while crt is not None:
            p = self.head
            x = crt.elem
            while p is not crt:
                if p.elem <= x:
                    # 跳到下一个结点
                    p = p.next
                else:
                    # 交换了一个元素,则其它元素也整体向右移动一个位置
                    y = p.elem
                    p.elem = x
                    x = y
                    p = p.next
            # p==crt时,回填crt.elem
            crt.elem = x
            crt = crt.next   

(2)发二:结点插入

该方式的排序本质上是改变结点的链接顺序,结点的elem值保持不变。

    def sort2(self):
        """通过结点移动排序"""
        # 空表或者只有一个结点的表
        if self.head is None or self.head.next is None:
            return
        # last: 已排序结点的最后一个结点,初始化时是首结点
        last = self.head
        crt = last.next
        # 遍历未排序的结点
        while crt is not None:
            p = self.head
            q = None
            while p is not crt and p.elem <= crt.elem:
                q = p
                p = p.next
            if p is crt:
                last = crt
            else:
                last.next = crt.next
                crt.next = p
                if q is None:
                    self.head = crt
                else:
                    q.next = crt
            crt = last.next

参考资料

[1]裘宗燕,《数据结构与算法(Python语言描述)》

[2] 裘宗燕, http://www.math.pku.edu.cn/teachers/qiuzy/ds_python/courseware/index.htm

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值