简说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上,包括代码,思路,算法图解(手绘或者电脑画),时间充裕的话,会录制视频。希望大家多多支持。
大家好,我是老表 觉得本文不错的话,转发、留言、点赞,是对我最大的支持。