数据结构 | 如何一文搞定链表问题?

简介: 近期看到一个数据结构题目,翻转链表。动手写了下代码,手生了不少,发现好铁不用也会生锈,大脑也如此。于是就整体回顾了一下链表的常见操作和数据结构题,整理下分享出来,万一对读者有帮助呢?想必那是极好的!目录如下~

这是本公号的第127篇原创。
近期看到一个数据结构题目,翻转链表。动手写了下代码,手生了不少,发现好铁不用也会生锈,大脑也如此。
于是就整体回顾了一下链表的常见操作和数据结构题,整理下分享出来,万一对读者有帮助呢?想必那是极好的!目录如下~

  • 链表基础知识
  • 链表常见操作
  • 链表常见题目

链表基础知识

链表和顺序表相对应,顺序表将表中的元素按照顺序放置于连续的存储区间,便于访问和查找。而链表则将元素放置于一系列结点中,各结点空间不要去连续,虽然给访问和查找带来了一定不便,但是在元素的删除和插入等操作上有着得天独厚的优势。(数组和链表结构的在复杂度方面的优缺点你get到了吗?)

链表的结点包括数据域和指针域两部分,顾名思义,数据域保存着元素的数据信息,指针域保存着下一个结点的指针标识。当然 Python 中没有指针这个概念,刷题中也是模拟指针的概念进行操作。


71.jpg

谈到链表一定不能避开的两个定义:头结点和头指针。

头结点的设立是为了便于操作,指的是放在第一个元素结点前的结点。不是链表结构必不可少的一部分。头指针则不一样,属于必不可少的一部分,指向链表结构的第一个结点(头结点存在的时候指向头结点)。通常我们对链表的访问和各操作都是基于头指针进行的。

链表分类则可以根据不同方式:

从内存角度出发: 链表可分为 静态链表、动态链表。

从链表存储方式的角度出发: 链表可分为 单链表、双链表、以及循环链表。

72.jpg

链表常见操作

链表的常见操作包括但不局限于求链表长度、结点的删除、插入等,但小詹之前文章说过许多次,Python 中由于不存在指针,所以指针和链表指的都是模拟指针和链表。所以这里的常见操作应当加上链表的构建操作。

1.首先是用 Python 构建链表

如上所述,链表由一系列结点组成,所以构建链表的第一步是定义一个结点类。

class Node(object):
    """
    定义一个节点类
    """
    def __init__(self,data):
        self.data = data
        self.next = None


有了结点,下一步就是构建链表了。如下的 InitList() 方法用一个非空 data 构建的链表,并定义了一个 PrintList() 方法打印出链表结果。


class LinkList(object):
    """
    创建一个链表类,包括判断是否为空、打印链表
    """
    def __init__(self):
        self.head = Node(None) 
    def InitList(self, data):
        if len(data) == 0:
            print('\nIt is a empty link list')
            return False
        self.head = Node(data[0])#头结点,data的一个元素为数据域信息
        p = self.head #头指针,指向第一个结点
        for i in data[1:]:
            node = Node(i)
            p.next = node
            p = p.next
        def IsEmpty(self):
        p = self.head #指向第一个结点
    def IsEmpty(self):
        p = self.head #指向第一个结点    
        if p == None:
#           print('\nIt is a empty link list')
            return True #是空链表    
#       print('\nIt is not empty')
        return False #非空链表
    def PrintList(self):
        p = self.head
        while p: #注意不是p.next
            print(p.data, end = ' ') #end表示结束的标识,不添加说明时默认为换行符
            p = p.next

这是一个简单的构建链表方法并将其打印出来,可以测试下结果。


def main():
    """ 
    测试
    """
    data = [1, 2, 3, 4, 5]    
    lst = LinkList()
    lst.InitList(data)
    lst.PrintList()
main()

对应的结果为:


1 2 3 4 5

2.其次是链表长度的求取

链表长度的求取在使用中经常遇到,所以构建了一个链表后,我们应当把一些常见的操作封装到一起,为此,可以创建一个求表长的方法,通过便历链表所有结点即可。

def Size(self):
        """
        求取链表长度
        """
        p = self.head
        size = 0
        while p:
            size += 1
            p = p.next
        return size


3.还有常见的结点删除与插入

开篇提到顺序表对元素的查找和访问比较便捷,但是涉及到元素删除和插入就比较鸡肋了,链表中就相反,非常的便捷!

需要注意情况分类,有大概 3 种情况。且删除位置 n 应当有区间限制。

  • 当链表只有一个结点的时候
  • 当删除的是第一个结点的时候
  • 其他普适情况

对于第一种,只需要直接将头结点为 None 即可;第二种情况和普适情况类似,只是普适情况的前驱结点在第二种情况为头结点。重点还在普适情况的删除操作,可以简单的将前一个结点的指针指向待删除结点的下一个结点即可跳过该结点。代码如下所示:

def Del(self,n):
    """
    删除第n个结点,注意特殊边界情况
    """
    if self.IsEmpty() or n < 0 or n > self.Size():
        print('error occured')
        return
    p = self.head 
    if p.next is None:   # 当只有一个节点的链表时
        self.head = None
        return        
    if n == 1:   # 当删除第一个节点时
        self.head = p.next
        return
    #普通情况
    p = self.head        
    index = 1
    while index < n:#找到要删除的结点位置
        pre = p
        index += 1
        p = p.next     
      pre.next = p.next#跳过该结点即可删除
      p = None


以上是删除操作,考虑了各种删除情况。插入操作和删除有点类似倒转的感觉,只需要找到待插入位置,打断前后两个结点的指针,改变 3 者的指向关系即可。插入位置n范围应当在闭区间(0,链表长度)之间。值得注意的是这里也需要分情况讨论,许多教程忽略了边缘情况,这里着重讨论下。

  • 插入的链表为空链表
  • 插入在链表的头结点处
  • 普适情况

前俩种情况类似,只用将插入结点做头结点即可,普适情况只需要将该结点的前后位置结点指针做些调整即可,代码给出如下,可以手动画图方便理解噢。

def Insert(self,n,data):
    """
    把data插入第n个结点之后的位置
    """
    if n < 0 or n > self.Size() + 1: 
    #n最小值为0,且插入位置超过长度+1时实际只能插入到最后,即最大值为链表长【0,size】区间内有效
        print('error occured')
        return
    p = self.head
    if self.IsEmpty():#空链表时插入头指针之后即可
        node = Node(data)
        p.next = node
        return
    if n == 0:
        node = Node(data)
        lat = self.head
        self.head = node #新插入的做头结点
        node.next = lat #新插入结点后续指向之前的头结点
        return
    index = 1
    while index < n:
        index += 1
        p = p.next
        #插入操作关键之处,注意指针变换顺序
      node = Node(data)
      node.next = p.next
      p.next = node


链表常见题目

这里重要讲解 2 个题型。

1.链表翻转问题

简单的说就是将一个单链表翻转过来,这是 LeetCode 的第 206 题,在链表题目中很有分量。

输入:1->2->3->4->5
输出:5->4->3->2->1


这个题目其实不算难,思路比较清晰,只需要从前往后依次将结点指向颠倒,例如:


原 始:1->2->3->4->5
第1轮:5<-4  3->2->1
第2轮:5<-4<-3  2->1
第3轮:5<-4<-3<-2  1
第4轮:5<-4<-3<-2<-1

关键在于指针的转换顺序不能颠倒变换,建议自己按照我上边给的示例进行绘图帮助理解,代码如下:


class Solution:
    """
    leetcode题目中提交函数都只用放在此类下
    """
    def reverseList(self, head):
        """
        翻转操作leetcode题目206
        """
        cur, pre = head, None
        while cur:
            cur.next, pre, cur = pre, cur, cur.next
            #这一行等同于如下 4 行:
            #lat = cur.next
            #cur.next = pre
            #pre = cur
            #cur = lat
        return pre

2.链表有环问题(LeetCode第141题)

链表有环问题也是一个常见的问题了,之前在总结双指针类型问题(见文末推荐阅读)时候讲过,可以去回顾下。思路如下:

  • 定义两个指针 ,一快一慢 。比如慢指针一次移动 1 个位置 ,快指针移动 2 个 。
  • 初始快慢指针放在一个位置 ,并开始循环移动 。
  • 如果有环 ,那么随着移动的进行 ,终有快指针经过环遇到并超过慢指针的时候 ,那么这就可以用来判断是否存在环的依据啦 。

73.jpg


相关文章
|
1月前
|
缓存
数据结构之 - 链表数据结构详解: 从基础到实现
数据结构之 - 链表数据结构详解: 从基础到实现
77 6
|
23天前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
50 4
|
24天前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
25天前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
1月前
|
存储 Java
数据结构第三篇【链表的相关知识点一及在线OJ习题】
数据结构第三篇【链表的相关知识点一及在线OJ习题】
26 7
|
1月前
|
存储 安全 Java
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
25 3
|
1月前
|
算法 Java
数据结构与算法学习五:双链表的增、删、改、查
双链表的增、删、改、查操作及其Java实现,并通过实例演示了双向链表的优势和应用。
17 0
数据结构与算法学习五:双链表的增、删、改、查
|
23天前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
40 0
【数据结构】——双向链表详细理解和实现
【数据结构】——双向链表详细理解和实现
|
1月前
|
存储 Java
【数据结构】链表
【数据结构】链表
18 1