由反转链表想到python链式交换变量
这两天在刷题,看到链表的反转,在翻解体思路时看到有位同学写出循环中一句搞定三个变量的交换时觉得挺6的,一般用的时候都是两个变量交换(a,b=b,a),这种三个变量的交换还真不敢随便用,而且这三个变量都是可变类型.
心存疑惑然后就多方查找,于是有了下面的测试代码和解体思路.这里需要了解dis查看字节码了解变量的大致交换过程,顺带也延申了反转链表时哪几种是可用的,以及为什么?好了,废话不多说,代码中注释也比较充分,应该能帮助理解.
__doc__ = """
Python的变量并不直接存储值,而只是引用一个内存地址,交换变量时,只是交换了引用的地址。
在 2、3 个值分配的时候是直接运用栈,在 3 个以上值分配的时候才是用了拆包的原理。
https://www.v2ex.com/t/483347 # 一些讨论
https://stackoverflow.com/questions/21047524/how-does-swapping-of-members-in-tuples-a-b-b-a-work-internally # 一行交换变量的两种原理
https://www.cnblogs.com/aydenwang/p/9398826.html # 四种交换变量的方法
"""
import dis
def swap2(a, b):
a, b = b, a # ROT_TWO
print(a, b)
def swap3(a, b, c):
a, b, c = c, b, a # ROT_THREE ROT_TWO
print(a, b, c)
def swap4(a, b, c, d):
a, b, c, d = d, c, b, a # BUILD_TUPLE UNPACK_SEQUENCE
print(a, b, c, d)
def swap5(a, b, c, d, e):
a, b, c, d, e = e, d, c, b, a # BUILD_TUPLE UNPACK_SEQUENCE
print(a, b, c, d, e)
def swap55(a, b, c, d, e):
a, b, c, d, e = d, e, a, c, b # BUILD_TUPLE UNPACK_SEQUENCE
print(a, b, c, d, e)
def swap():
"""交换变量,不涉及引用"""
a, b, c, d, e = 10, 20, 30, 40, 50
swap2(a, b)
dis.dis(swap2)
swap3(a, b, c)
dis.dis(swap3)
swap4(a, b, c, d)
dis.dis(swap4)
swap5(a, b, c, d, e)
dis.dis(swap5)
swap55(a, b, c, d, e)
dis.dis(swap55)
"""
python3.8
ROT_TWO ROT_THREE ROT_FOUR 这样的指令可以直接交换两个变量,三个变量,四个变量,但是上面的例子中并没用到ROT_FOUR.
Python 将右侧表达式与左侧赋值分开。
首先计算右侧,结果存储在堆栈上,然后使用再次从堆栈中引用值的操作代码从左到右分配左侧名称。
对于包含 2 个或 3 个项目的元组分配,Python 仅直接使用堆栈.
"""
class Node(object):
"""单链表的结点"""
def __init__(self, item):
# item存放数据元素
self.item = item
# next是下一个节点的标识
self.next = None
# def __repr__(self):
# return "{} -> ".format(self.item)
# def __getattr__(self, item):
# print('get')
# return super().__getattr__(item)
# # return self[item]
# def __setattr__(self, key, value):
# print('set', key, value)
# super().__setattr__(key, value)
class SingleLinkList(object):
"""单链表"""
def __init__(self):
self._head = None
self._end = None
# 创建单链表
def create(self, node_list):
for k, v in enumerate(node_list):
if k == 0:
self._head = Node(v)
self._end = self._head
else:
p = self._end
p.next = Node(v)
self._end = p.next
def print(self):
"""遍历打印链表"""
# 获取head指针
cur = self._head
# 循环遍历
while cur is not None:
# 返回生成器
print(cur.item, end=',')
# 指针下移
cur = cur.next
print("\n--------------")
# 不考虑操作顺序反转必须要操作的步骤有:cur.next=last,last=cur,cur=cur.next,这样一共有6中操作
def reverse1(self):
"""交换变量,3种正常:左侧cur都是在cur.next之后改的,遍历顺序不会断裂"""
last, cur = None, self._head
while cur:
cur.next, cur, last = last, cur.next, cur # 无中间变量交换,等式右边先计算,然后链式赋值从左到右.
# cur.next, last, cur = last, cur, cur.next # 同上
# last, cur.next, cur = cur, last, cur.next # 同上
self._head = last
"""
124 16 LOAD_FAST 1 (last) # 从左到右,以此取值
18 LOAD_FAST 2 (cur)
20 LOAD_ATTR 1 (next)
22 LOAD_FAST 2 (cur)
24 ROT_THREE
26 ROT_TWO # 从左到右以此赋值
28 LOAD_FAST 2 (cur) # 获取cur引用
30 STORE_ATTR 1 (next) # 修改当前节点
32 STORE_FAST 2 (cur) # 变更cur应用
34 STORE_FAST 1 (last)
"""
def reverse2(self):
"""交换变量,3种异常:左侧curl在curl.next前被赋值,导致遍历顺序断裂"""
last, cur = None, self._head
while cur:
cur, cur.next, last = cur.next, last, cur
# cur, last, cur.next = cur.next, cur, last
# last, cur, cur.next = cur, cur.next, last
self._head = last
"""
146 16 LOAD_FAST 2 (cur) # 从左到右,以此取值
18 LOAD_ATTR 1 (next)
20 LOAD_FAST 1 (last)
22 LOAD_FAST 2 (cur)
24 ROT_THREE
26 ROT_TWO # 从左到右以此赋值
28 STORE_FAST 2 (cur) # 变更cur
30 LOAD_FAST 2 (cur) # 获取最新cur引用
32 STORE_ATTR 1 (next) # 变更最新cur的next
34 STORE_FAST 1 (last)
36 JUMP_ABSOLUTE 12
"""
def reverse_swap(self):
"""异常:AttributeError: 'NoneType' object has no attribute 'next'"""
last, cur = None, self._head
while cur:
print('前:', cur, cur.next, last)
# 异常,cur值先被修改,导致cur.next取的值已经是被修改后的cur,导致链表断裂
# cur, cur.next, last = cur.next, last, cur
# cur, last, cur.next = cur.next, cur, last
# last, cur, cur.next = cur, cur.next, last
# 正常,cur.next先被修改,再把cur引用被覆盖并不会影响之前已经被修改的cur节点以及cur.next
cur.next, cur, last = last, cur.next, cur # 无中间变量交换,等式右边先计算,然后链式赋值从左到右.
# cur.next, last, cur = last, cur, cur.next # 同上
# last, cur.next, cur = cur, last, cur.next # 同上
print('后:', cur, cur.next, last)
# break
self._head = last
def reverse_swap2(self): # 通过翻转单个note节点达到链表翻转
"""翻转链表"""
# 非递归实现
if not self._head or not self._head.next:
return self._head
last = None # 指向上一个节点,以备后用
cur = self._head # 当前节点,也可以不定义变量直接参与循环,此处为了方便理解,单独定义变量
while cur: # 不会像上面那种有多种顺序,用这种零时变量的方式,顺序只有这一种.主要是因为链式交换等式的右侧已经入栈固定了在随后的网左侧赋值时不会改变.
# 先用next_tmp保存head的下一个节点的信息,保证单链表不会因为失去head节点的next而就此断裂(内部循环使用)
next_tmp = cur.next
# 下一跳已经保存好,可以开始修改当前节点的下一跳了,也就是上一个节点last,初始头的上一个是没有的即None
cur.next = last
# 记录下修改后的当前节点,并保存跟新'上一个节点'给下次用.
last = cur
# 当前节点处理完毕,更新为备份好的原先的下一个节点
cur = next_tmp
# 最后一个节点变成了头节点
self._head = last
line = SingleLinkList()
line.create(range(10))
# print('-------------------1')
# dis.dis(line.reverse1)
# print('-------------------2')
# dis.dis(line.reverse2)
# line.reverse_swap()
# line.print()
line.reverse_swap2()
line.print()
"""dis每一列的意思:
https://docs.python.org/zh-cn/3/library/dis.html?highlight=dis#module-dis
第一列:对应的源代码行数。
第二列:对应的内存字节码的索引位置。
第三列:内部机器代码的操作。
第四列:指令参数。
第五列:实际参数。
LOAD_FAST(var_num)
将指向局部对象 co_varnames[var_num] 的引用推入栈顶。
STORE_FAST(var_num)
将 TOS 存放到局部对象 co_varnames[var_num]。
BUILD_TUPLE(count)
创建一个使用了来自栈的 count 个项的元组,并将结果元组推入栈顶。
UNPACK_SEQUENCE(count)
将 TOS 解包为 count 个单独的值,它们将按从右至左的顺序被放入堆栈。
POP_TOP
删除堆栈顶部(TOS)项。
ROT_TWO
交换两个最顶层的堆栈项。
ROT_THREE
将第二个和第三个堆栈项向上提升一个位置,顶项移动到位置三。
ROT_FOUR
将第二个,第三个和第四个堆栈项向上提升一个位置,将顶项移动到第四个位置。
"""
由反转链表想到python链式交换变量的更多相关文章
- Python链式赋值执行顺序及执行方式的证明
专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 在<关于Python链式赋值的赋值顺序问题& ...
- [Python]解决python链式extend的技巧
众所周知python中的list是可以extend的,功能 旨在将两个list合并成一个.譬如[1,2,3].extend([4,5,6])=[1,2,3,4,5,6] 假如有一个list的list, ...
- 基于链式链表的栈链式存储的C风格实现
链式链表的头文件与CPP文件见前文 头文件: #ifndef _LINKSTACK_H_ #define _LINKSTACK_H_ typedef void LinkStack; //创建一个栈 L ...
- python链式对比
参考 https://www.cnblogs.com/shanghongyun/p/10519579.html 为什么Python中“2==2>1”结果为True在Python中,你可能会发现这 ...
- Python 笔试集(1):关于 Python 链式赋值的坑
前言 Python 的链式赋值是一种简易型批量赋值语句,一行代码即可为多个变量同时进行赋值. 例如: x = y = z = 1 链式赋值是一种非常优雅的赋值方式,简单.高效且实用.但同时它也是一个危 ...
- 关于Python链式赋值的赋值顺序问题
在<第4.7节 Python特色的序列解包.链式赋值.链式比较>一文中,老猿这样介绍的: 链式赋值是用一行语句将多个变量赋值为同一个值,语法如下: 变量1=变量2=变量n=赋值表达式 该语 ...
- 记python 链式比较的坑
前两天,python交流群里有人问: “x”<"y"==True 这个表达式输出的是什么,脑子都没动,就觉得应该是True 居然被否定了!立马在命令行里敲了一下,准备用事实打 ...
- python链式调用REST API把参数放到URL中
需求格式:GET /users/:user/repos 程序: class Chain(object): def __init__(self,path=''): self._path=path def ...
- C语言实现链表(链式存储结构)
链表(链式存储结构)及创建 链表,别名链式存储结构或单链表,用于存储逻辑关系为 "一对一" 的数据.与顺序表不同,链表不限制数据的物理存储状态,换句话说,使用链表存储的数据元素,其 ...
随机推荐
- Java基础语法(10)-面向对象之三大特征
title: Java基础语法(9)-面向对象之类的成员 blog: CSDN data: Java学习路线及视频 1.面向对象特征--封装 为什么需要封装?封装的作用和含义? 我要用洗衣机,只需要按 ...
- 为什么阿里巴巴禁止使用BigDecimal的equals方法做等值比较?
GitHub 17k Star 的Java工程师成神之路,不来了解一下吗! GitHub 17k Star 的Java工程师成神之路,真的不来了解一下吗! GitHub 17k Star 的Java工 ...
- PowerJob 从 0 到 1.9k star 的经历
本文适合有 Java 基础知识的人群 作者:HelloGitHub-Salieri 本文就是<讲解 PowerJob>系列的最后一篇文章了,纯粹是写点经历写点心路历程啥的,和大家道个别. ...
- 原子类CAS的底层实现
原子类使用 public class CASDemo { public static void main(String[] args) { AtomicInteger atomicInteger = ...
- PHP正则表达式核心技术完全详解 第2节
作者:极客小俊 一个专注于web技术的80后 我不用拼过聪明人,我只需要拼过那些懒人 我就一定会超越大部分人! CSDN@极客小俊,原创文章, B站技术分享 B站视频 : Bilibili.com 个 ...
- mac电脑,charles,安卓手机如何配置代理,以及配置代理之后无法上网。已解决
设备: 电脑:mac book pro 手机:小米10 charles:4.5.6 方法一: 步骤: 首先确保电脑,手机在同一局域网, 1. charles设置代理:proxy -> proxy ...
- Java基础——消息队列
1.消息队列的适用场景:商品秒杀.系统解耦.日志记录等 2.使用Queue实现消息对列 双端队列(Deque)是 Queue 的子类也是 Queue 的补充类,头部和尾部都支持元素插入和获取阻塞队列指 ...
- 【SCOI2016】背单词
P3294[SCOI2016]背单词 [提示] 这道题大概是告诉我们,让我们用一堆n个单词安排顺序,如果当前位置为x,当前单词的后缀没在这堆单词出现过,代价就为x,这里的后缀是原意,但不算自己(不算本 ...
- VS2013中带命令行参数的调试方法---C++
今天先记录一下(也是传说中大神喜欢装逼的comment line)c++中向主函数int main(int argc,char** argv )传递4中方法,欢迎添加新方法, 然后可以参考别人写的很好 ...
- matlab做gaussian高斯滤波
原文链接:https://blog.csdn.net/humanking7/article/details/46826105 核心提示 在Matlab中高斯滤波非常方便,主要涉及到下面两个函数: 函数 ...