數組與鏈表

引子

假設你去看演出,需要將東西寄存。寄存處有一個櫃子,櫃子有很多抽屜。

在這裏插入圖片描述
計算機就像是很多抽屜的集合體,每個抽屜都有地址。
在這裏插入圖片描述
fe0ffeeb是一個內存單元的地址。

需要將數據存儲到內存時,你請求計算機提供存儲空間,計算機給你一個存儲地址。需要存儲多項數據時,有兩種基本方式——數組和鏈表。但它們並非都適用於所有的情形,因此知道它們的差別很重要。接下來介紹數組和鏈表以及它們的優缺點。

數組
有時候,需要在內存中存儲一系列元素。假設你要編寫一個管理待辦事項的應用程序,爲此需要將這些待辦事項存儲在內存中。

在這裏插入圖片描述
我們先將待辦事項存儲在數組中。使用數組意味着所有待辦事項在內存中都是相連的(緊靠在一起的)。
在這裏插入圖片描述
現在假設你要添加第四個待辦事項,但後面的那個抽屜放着別人的東西!
在這裏插入圖片描述
這就像你與朋友去看電影,找到地方就坐後又來了一位朋友,但原來坐的地方沒有空位置,只得再找一個可坐下所有人的地方。在這種情況下,你需要請求計算機重新分配一塊可容納4個待辦事項的內存,再將所有待辦事項都移到那裏。如果又來了一位朋友,而當前坐的地方也沒有空位,你們就得再次轉移!真是太麻煩了。同樣,在數組中添加新元素也可能很麻煩。如果沒有了空間,就得移到內存的其他地方,因此添加新元素的速度會很慢。

再以整型數組爲例,數組的存儲形式如下圖所示。正如軍隊裏的士兵存在編號一樣,數組中的每一個元素也有着自己的下標,只不過這個下標從0開始,一直到數組長度-1。
在這裏插入圖片描述
數組中的每一個元素,都存儲在小小的內存單元中,並且元素之間緊密排列,既不能打亂元素的存儲順序,也不能跳過某個存儲單元進行存儲
在這裏插入圖片描述在上圖中,橙色的格子代表空閒的存儲單元,灰色的格子代表已佔用的存儲單元,而紅色的連續格子代表數組在內存中的位置。不同類型的數組,每個元素所佔的字節個數也不同,本圖只是一個簡單的示意圖。

數組的基本操作:

讀取元素:

array1=[1,2,3,4,5]
print(array1[0])  # 1
print(array1[-1])  # 5

更新元素:

array1=[1,2,3,4,5]
array1[0]=8 #按照索引修改指定位置的值
print(array1)  # [8,2,3,4,5]

插入元素:
尾部插入:

l1 = ['a','b','c'].append('d')
print(l1) # ['a', 'b', 'c', 'd']
l1.extend(['a','b', 'c'])
print(l1) # ['a', 'b', 'c', 'd', 'a', 'b', 'c']

中間位置插入:

l1.insert(0,"first")  # 0表示按索引位置插值
print(l1) # ['first', 'a', 'b', 'c', 'd', 'a', 'b', 'c']

刪除元素:

l = [11,22,33,44]
del l[2]  # 刪除索引爲2的元素
print(l) # [11,22,44]

# pop()默認刪除列表最後一個元素,並將刪除的值返回,括號內可以通過加索引值來指定刪除元素
l = [11,22,33,22,44]
res=l.pop()
print(res) # 44
res=l.pop(1)
print(res)# 22

# remove()括號內指名道姓表示要刪除哪個元素,沒有返回值
l = [11,22,33,22,44]
print(l.remove(22)) #從左往右查找第一個括號內需要刪除的元素  None

總結:
數組擁有非常高效的隨機訪問能力,只要給出下標,就可以用常量時間找到對應元素。有一種高效查找元素的算法叫作二分查找,就是利用了數組的這個優勢。至於數組的劣勢,體現在插入和刪除元素方面。由於數組元素連續緊密地存儲在內存中,插入、刪除元素都會導致大量元素被迫移動,影響效率。總的來說,數組所適合的是讀操作多、寫操作少的場景。

鏈表

鏈表中的元素可存儲在內存的任何地方。
在這裏插入圖片描述
鏈表的每個元素都存儲了下一個元素的地址,從而使一系列隨機的內存地址串在一起。
在這裏插入圖片描述
這猶如尋寶遊戲。你前往第一個地址,那裏有一張紙條寫着“下一個元素的地址爲123”。因此,你前往地址123,那裏又有一張紙條,寫着“下一個元素的地址爲847”,以此類推。在鏈表中添加元素很容易:只需將其放入內存,並將其地址存儲到前一個元素中。

使用鏈表時,根本就不需要移動元素。這還可避免另一個問題。假設你與五位朋友去看一部很火的電影。你們六人想坐在一起,但看電影的人較多,沒有六個在一起的座位。使用數組時有時就會遇到這樣的情況。假設你要爲數組分配10 000個位置,內存中有10 000個位置,但不都靠在一起。在這種情況下,你將無法爲該數組分配內存!鏈表相當於說“我們分開來坐”,因此,只要有足夠的內存空間,就能爲鏈表分配內存。

在這裏插入圖片描述
單向鏈表的每一個節點又包含兩部分,一部分是存放數據的變量data,另一部分是指向下一個節點的指針next。鏈表的第1個節點被稱爲頭節點,最後1個節點被稱爲尾節點,尾節點的next指針指向空。

什麼是雙向鏈表?

雙向鏈表比單向鏈表稍微複雜一些,它的每一個節點除了擁有data和next指針,還擁有指向前置節點的prev指針。
在這裏插入圖片描述

鏈表的基本操作:

查找節點:
在查找元素時,鏈表不像數組那樣可以通過下標快速進行定位,只能從頭節點開始向後一個一個節點逐一查找。

例如給出一個鏈表,需要查找從頭節點開始的第3個節點。
在這裏插入圖片描述

更新節點:
如果不考慮查找節點的過程,鏈表的更新過程會像數組那樣簡單,直接把舊數據替換成新數據即可。
在這裏插入圖片描述

插入節點:

數組類似,鏈表插入節點時,同樣分爲3種情況。

  • 尾部插入
  • 頭部插入
  • 中間插入

尾部插入,是最簡單的情況,把最後一個節點的next指針指向新插入的節點即可。
在這裏插入圖片描述
頭部插入,可以分成兩個步驟。
第1步,把新節點的next指針指向原先的頭節點。
第2步,把新節點變爲鏈表的頭節點。
在這裏插入圖片描述
中間插入,同樣分爲兩個步驟。
第1步,新節點的next指針,指向插入位置的節點。
第2步,插入位置前置節點的next指針,指向新節點。
在這裏插入圖片描述
只要內存空間允許,能夠插入鏈表的元素是無窮無盡的,不需要像數組那樣考慮擴容的問題。

刪除元素:
鏈表的刪除操作同樣分爲3種情況。

  • 尾部刪除
  • 頭部刪除
  • 中間刪除

尾部刪除,是最簡單的情況,把倒數第2個節點的next指針指向空即可。
在這裏插入圖片描述
頭部刪除,也很簡單,把鏈表的頭節點設爲原先頭節點的next指針即可。
在這裏插入圖片描述
中間刪除,同樣很簡單,把要刪除節點的前置節點的next指針,指向要刪除元素的下一個節點即可。
在這裏插入圖片描述
鏈表的基本操作:

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 is None

    def length(self):
        """鏈表長度"""
        cur = self.__head
        count = 0
        while cur is not None:
            count += 1
            cur = cur.next
        return count

    def travel(self):
        """遍歷整個鏈表"""
        cur = self.__head
        while cur is not 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 is not None:
                cur = cur.next
            cur.next = node

    def insert(self,pos,item):
        """指定位置插入元素
        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 is not 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 is not None:
            if cur.elem == item:
                return True
            else:
                cur = cur.next
        return False

    def set(self,elem,new_elem):
        """替換節點"""
        cur = self.__head
        while cur is not None and elem != cur.elem:
            cur = cur.next
        if cur is not None:
            cur.elem = new_elem
            return True
        else:
            return False
數組與鏈表對比

在這裏插入圖片描述
從表格可以看出,數組的優勢在於能夠快速定位元素,對於讀操作多、寫操作少的場景來說,用數組更合適一些。相反地,鏈表的優勢在於能夠靈活地進行插入和刪除操作,如果需要在尾部頻繁插入、刪除元素,用鏈表更合適一些。

發佈了41 篇原創文章 · 獲贊 9 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章