Smaller And Smarter 数据结构:链表(1)

简介: Smaller And Smarter 数据结构:链表(1)

简说Python,号主老表,Python终身学习者,数据分析爱好者,从18年开始分享Python知识,原创文章227篇,写过Python、SQL、Excel入门文章,也写过Web开发、数据分析文章,老表还总结整理了一份2022Python学习资料和电子书资源,关注后私信回复:2022 即可领取。

今天给大家分享的书籍《Python程序员面试算法宝典》第一章第一小节:链表逆转。

正文开始前请大家和我一起读一遍: Python里是没有指针这种数据结构的。

初学者可能就有疑惑了,那Python里,指针是怎么来的呢?链表又是如何实现的呢?

# 链表数据结构定义
class ListNode:
    def __init__(self, x):
        self.data = x  # 数据项
        self.next = None  # 指针项

如你所见,Python里的指针功能是通过引用来实现的,通过类来定义这样的一种数据结构,包含了数据项与指针项。(所谓引用,就是使用的是这个对象本身,包括地址,数据) 为了方便理解和学习,我们可以把引用就当作是指针。

接下来我们谈一下链表的优缺点:

链表是一种逻辑上相邻,物理上不一定相邻的数据结构。

优点:动态规划大小,方便添加、删除、插入。

缺点:只能顺序查找,查询效率低。

今日问题

"""
目标:写一段程序,让链表逆序
例如:
输入-> 1->2->3->4->5
输出-> 5->4->3->2->1
"""
"""
Goal: write a program to reverse the list
For example:
Input  - > 1 - > 2 - > 3 - > 4 - > 5
Output - > 5 - > 4 - > 3 - > 2 - > 1
"""

首先我们写好链表的基本操作,在a_0_base.py文件中,目前包含对链表的定义类,初始化函数,遍历函数。(带头结点)

# -*- coding: utf-8 -*-
"""
@author = 老表
@date = 2019-10-19
@个人微信公众号 : 简说Python
"""
# 链表数据结构定义
class ListNode:
    def __init__(self, x):
        self.data = x
        self.next = None
class ListOperation:
    # 根据链表数据初始化链表
    @staticmethod
    def init_list(n_list):
        # 初始化一个头指针
        head = ListNode("head")
        cur = head
        for i in n_list:
            node = ListNode(i)
            cur.next = node
            cur = node  # 相当于 cur = cur.next,后移
        return head
    # 遍历链表
    @staticmethod
    def ergodic_list(head):
        cur = head.next
        while cur:
            print(cur.data)
            cur = cur.next

解题

开始程序前需提前导入前面写好的链表基本操作包a_0_base

from Linked_list.a_0_base import ListOperation
方法一:递归逆转
"""
Method One : 递归逆转
核心思想:没次逆序链表时,都先逆序子链表,然后把第一个结点加到
逆序的链表之后,这样没错逆序链表都会产生一个子链表,直到递归到
尾结点,此时开始回溯,每次回溯,会完成一个子链的逆转,直到回溯
到头结点,设置头结点,逆转完成。
时间复杂度:O(N)
空间复杂度:O(1)
"""
def reverse_list_one(head):
    """
    :type head: ListNode
    :rtype: ListNode
    """
    if not head.next:  # 空链表或者遍历到最后一个结点
        return head  # 返回头结点或者开始回溯
    head_node = reverse_list_one(head.next)  # 递归,往后遍历
    if head.data == "head":  # 判断是不是头结点
        head.next = head_node  # 让头结点指向逆转后的第一个结点
        return head  # 返回头结点,逆转完成
    head.next.next = head  # 逆转:后面的数指向前面的数
    head.next = None  # 断开之前的链接
    return head_node  # 返回原链表尾结点
方法二:就地逆转
"""
Method Two :就地逆转
核心思想:遍历链表,每次遍历都让当前结点指向前驱结点。
时间复杂度:O(N)
空间复杂度:O(1)
"""
def reverse_list_two(head):
    """
    :type head: ListNode
    :rtype: ListNode
    """
    if not head.next:  # 空链表或者遍历到最后一个结点
        return head  # 返回头结点
    # 第一步:首结点变成逆转后尾结点
    cur_node = head.next  # 链表首结点
    next_node = cur_node.next  # 原链表第二个结点
    cur_node.next = None  # 断开原链表首结点与第二个结点链接
    # 第二步:依次逆转后续结点
    pre_node = cur_node  # 记录前驱结点
    cur_node = next_node  # 后移一位
    while cur_node.next:
        next_node = cur_node.next  # 记录后继结点
        # 让当前结点指向前驱结点
        cur_node.next = pre_node  # 逆转
        pre_node = cur_node  # 前驱结点后移
        cur_node = next_node  # 链表后移,遍历
        """
        注:为什么这个地方当前结点后移不用
        cur_node = cur_node.next
        因为在前面的代码中,cur_node.next = pre_node,即cur_node 的后继结点已经改变
        所以用next_node来标记逆转前的cur_node的后继结点
        """
    # 第三步:头结点指向原链表尾结点,完成逆转
    cur_node.next = pre_node  # 此时的cur_node应该为:原链表尾结点,最后一次逆转
    head.next = cur_node  # 头结点指向尾结点,完成逆转
    return head  # 返回头结点
方法三:插入逆转
"""
Method Three :插入逆转
核心思想:遍历链表,把遍历到的当前结点插入到头结点之后
这种方法应该是最好理解的。
时间复杂度:O(N)
空间复杂度:O(1)
"""
def reverse_list_three(head):
    """
    :type head: ListNode
    :rtype: ListNode
    """
    if not head.next:  # 空链表或者遍历到最后一个结点
        return head  # 返回头结点
    # 第一步:设置尾结点
    cur_node = head.next.next  # 取出第二个结点
    head.next.next = None  # 令第一个结点指向None,即设置成尾结点
    # 第二步:向后遍历,并把遍历到的结点插入到头结点后
    while cur_node:
        next_node = cur_node.next  # 记录后继结点
        cur_node.next = head.next
        head.next = cur_node  # 将当前结点插入到头结点后
        cur_node = next_node  # 链表后移,遍历
    return head  # 返回头结点

测试代码

# 当然,也许还有别的方法,比如建一个辅助的链表
# 欢迎你说出你的想法
# 程序入口,测试函数功能
if __name__ == "__main__":
    s0 = ListOperation()  # 初始化一个链表基本操作对象实例
    list_data = [1, 2, 3, 4]  # 链表初始化数据
    head = s0.init_list(list_data)  # 初始化链表,带头结点
    s0.ergodic_list(head)  # 未逆转前,遍历打印链表
    head = reverse_list_one(head)  # 测试方法一,逆转链表
    # head = reverse_list_two(head)  # 测试方法二,逆转链表
    # head = reverse_list_three(head)  # 测试方法三,逆转链表
    print("---------------------")
    s0.ergodic_list(head)  # 逆转后,遍历打印链表

本文代码思路来自书籍《Python程序员面试宝典》,书中部分代码有问题,文中已经修改过了,并添加上了丰厚的注释,方便大家学习,后面我会把所有内容开源放到Github上,包括代码,思路,算法图解(手绘或者电脑画),时间充裕的话,会录制视频。希望大家多多支持。

大家好,我是老表 觉得本文不错的话,转发、留言、点赞,是对我最大的支持。




相关文章
|
26天前
|
存储 算法 Perl
数据结构实验之链表
本实验旨在掌握线性表中元素的前驱、后续概念及链表的建立、插入、删除等算法,并分析时间复杂度,理解链表特点。实验内容包括循环链表应用(约瑟夫回环问题)、删除单链表中重复节点及双向循环链表的设计与实现。通过编程实践,加深对链表数据结构的理解和应用能力。
52 4
|
19天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
42 5
|
1月前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
73 4
|
1月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
26天前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
42 0
|
2月前
|
存储 Java
数据结构第三篇【链表的相关知识点一及在线OJ习题】
数据结构第三篇【链表的相关知识点一及在线OJ习题】
28 7
|
2月前
|
存储 安全 Java
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
26 3
|
2月前
|
算法 Java
数据结构与算法学习五:双链表的增、删、改、查
双链表的增、删、改、查操作及其Java实现,并通过实例演示了双向链表的优势和应用。
20 0
数据结构与算法学习五:双链表的增、删、改、查
【数据结构】——双向链表详细理解和实现
【数据结构】——双向链表详细理解和实现