由反轉鏈表想到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
將第二個,第三個和第四個堆棧項向上提升一個位置,將頂項移動到第四個位置。
"""

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章