天生我才必有用——淺析Python列表與鏈表

前言

之前寫了一篇《沒有指針,Python如何實現鏈表、二叉樹這些數據結構》然後有大佬覺得我那裏面講的是列表,不是鏈表。當時我也很疑惑,因爲我也不太確定列表和鏈表到底有什麼區別,說實話,列表確實比較好用,因爲是Python已經封裝好了的,方法種類多樣,更加的實用。但是這難道就意味着鏈表這一數據結構沒有絲毫的用處嘛?當然不是,只是可能你還沒有到非用他不可的地步罷了。

列表詳解

列表的實現機制

Python標準類型list就是一種元素個數可變的線性表,可以加入和刪除元素,並在各種操作中維持已有元素的順序(即保序),而且還具有以下行爲特徵:
基於下標(位置)的高效元素訪問和更新,時間複雜度應該是O(1);
爲滿足該特徵,應該採用順序表技術,表中元素保存在一塊連續的存儲區中。
允許任意加入元素,而且在不斷加入元素的過程中,表對象的標識(函數id得到的值)不變。
爲滿足該特徵,就必須能更換元素存儲區,並且爲保證更換存儲區時list對象的標識id不變,只能採用分離式實現技術。
在Python的官方實現中,list就是一種採用分離式技術實現的動態順序表。這就是爲什麼用list.append(x) (或 list.insert(len(list), x),即尾部插入)比在指定位置插入元素效率高的原因。
在Python的官方實現中,list實現採用瞭如下的策略:在建立空表(或者很小的表)時,系統分配一塊能容納8個元素的存儲區;在執行插入操作(insert或append)時,如果元素存儲區滿就換一塊4倍大的存儲區。但如果此時的表已經很大(目前的閥值爲50000),則改變策略,採用加一倍的方法。引入這種改變策略的方式,是爲了避免出現過多空閒的存儲位置。

翻閱了多方的資料,看了其他一些大佬發的東西,我在這裏總結一下:
列表的實現可以是數組或者鏈表。並且通過前面的學習我們知道,列表是一種順序表,順序表一般是數組。列表是一個線性表,它允許用戶在任何位置進行插入、刪除、訪問和替換元素。
列表的實現是基於數組或者基於鏈表結構,當使用列表迭代器的時候,雙向鏈表結構比單鏈表結構更快。
Python中的列表英文名是list,因此很容易與C語言中的鏈表搞混了,因爲在C語言中大家經常給鏈表命名爲list。事實上CPython(CPython是指用C語言實現的Python,也是我們常見的用C語言開發的Python解釋器,大家應該都知道,Python語言底層是C語言實現的)中的列表根本不是列表。在CPython中列表被實現爲長度可變的數組。
從細節上看,Python中的列表是由其他對象的引用組成的連續數組,指向這個數組的指針及其長度被保存在一個列表頭結構中。這就意味着,每次添加或刪除一個元素時,由引用組成的數組需要改變大小(重新分配)。幸運的是,Python在創建這些數組時採用了指數分配,所以並不是每次操作都要改變數組的大小。但是,也因爲這個原因添加或者取出元素是平攤複雜度較低。不幸的是,在普通鏈表上“代價很小”的其他一些操作在Python中計算複雜度相對較高。
總的來說,Python中的列表是一個動態的順序表,而順序表大多是由數組實現的。

鏈表

Python鏈表的具體實現在我上面那篇文章裏面介紹過了。我這裏就再來炒個剩飯。
鏈表是由許多相同數據類型的數據項按照特定的順序排列而成的線性表。鏈表中的數據項咋計算機的內存中的位置是不連續且隨機的,然而列表是連續的。鏈表數據的插入和刪除是很方便的,但數據的查找效率較低,不能像列表一樣隨機讀取數據。
鏈表由一個一個的結點構成,每個結點由數據域和“指針域”組成,數據域存儲數字,“指針域”指向下一個結點所在的內存地址。(因爲Python中沒有指針這一概念的,這裏的指針是一種指向)

class Node(object):
    """節點"""
    def __init__(self, elem):
        self.elem = elem
        self.next = None

鏈表封裝的一系列操作:

class SingleLinkList(object):
    """單鏈表"""
    def __init__(self, node=None):
        self.__head = node

    def is_empty(self):
        """鏈表是否爲空"""
        return self.__head == None

    def length(self):
        """鏈表長度"""
        # cur遊標,用來移動遍歷節點
        cur = self.__head
        # count記錄數量
        count = 0
        while cur != None:
            count += 1
            cur = cur.next
        return count

    def travel(self):
        """遍歷整個鏈表"""
        cur = self.__head
        while cur != None:
            print(cur.elem, end=" ")
            cur = cur.next
        print("")

    def add(self, item):
        """鏈表頭部添加元素,頭插法"""
        node = Node(item)
        node.next = self.__head
        self.__head = node

    def append(self, item):
        """鏈表尾部添加元素, 尾插法"""
        node = Node(item)
        if self.is_empty():
            self.__head = node
        else:
            cur = self.__head
            while cur.next != None:
                cur = cur.next
            cur.next = node

    def insert(self, pos, item):
        """指定位置添加元素
        :param  pos 從0開始
        """
        if pos <= 0:
            self.add(item)
        elif pos > (self.length()-1):
            self.append(item)
        else:
            pre = self.__head
            count = 0
            while count < (pos-1):
                count += 1
                pre = pre.next
            # 當循環退出後,pre指向pos-1位置
            node = Node(item)
            node.next = pre.next
            pre.next = node

    def remove(self, item):
        """刪除節點"""
        cur = self.__head
        pre = None
        while cur != None:
            if cur.elem == item:
                # 先判斷此結點是否是頭節點
                # 頭節點
                if cur == self.__head:
                    self.__head = cur.next
                else:
                    pre.next = cur.next
                break
            else:
                pre = cur
                cur = cur.next

    def search(self, item):
        """查找節點是否存在"""
        cur = self.__head
        while cur != None:
            if cur.elem == item:
                return True
            else:
                cur = cur.next
        return False

鏈表與列表的差異

Python中的list(列表)並不是傳統意義上的列表,傳統的意義上的列表就是我們今天講的鏈表,鏈表真的是跟列表長的很像的啊,不同地方在於鏈表在數據存儲方面更加的自由,其帶有指示下一個結點未知的指向,也就是我可以隨意的存儲數據,只要我們有東西能找到我,我也可以找到我的下一個。而列表則只能分配在一段連續的存儲空間裏,且是作爲一個整體,這就大大限制了數據的變更操作,但其在進行一些基礎簡單的操作時,時間複雜度極低。
list(列表):動態的順序表
鏈表:存儲地址分散的,需要“指針”指向的線性表

爲什麼要學習鏈表

知乎中的一個高贊回答:

我覺得家裏的承重牆沒實際意義,正忙着拆它呢。有事等我拆完了再說。
拆完了。
鏈表插入刪除效率極高,達到O(1)。對於不需要搜索但變動頻繁且無法預知數量上限的數據,比如內存池、操作系統的進程管理、網絡通信協議棧的trunk管理等等等等,缺了它是絕對玩不轉的。
甚至就連很多腳本語言都有內置的鏈表、字典等基礎數據結構支持。哪怕只是稍微像點樣子的小項目,如果缺了鏈表……誰扔的小石子?……啊,缺了鏈表絕對玩不轉。保守估計,缺了鏈表,普通家用PC至少得慢10倍,網絡服務器慢數百到數萬倍都有可……啊,房子要塌

Python實現鏈表這一數據結構能簡化我們的閱讀,他沒有C語言裏面一大堆指針和內存分配那樣的晦澀難懂,站在了一種更高層次的地方來方便快捷的實現鏈表這一數據結構,易於大家的理解。

後記

害!我也不知道我講清楚了沒得,列表和鏈表這個玩意兒確實是傻傻分不清,怪就怪在Python封裝了一個這麼好的列表,讓鏈表這個兄弟在大多數的時候都沒地方混,列表是真的好用,但是我們爲了學習數據結構也應該要掌握一下鏈表。
大家可以參考一下其他大佬寫的文章:
python學習筆記 – list內部實現(轉)https://www.jianshu.com/p/cd75475168a
Python的列表(List)的底層實現原理 https://blog.csdn.net/Yuyh131/article/details/83592608
畢竟是新手,寫的不好或者不對的地方歡迎指正,一起加油!
新手上路,技術有限,不喜勿噴!

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