線性表:一組元素看成一個序列,用元素在序列裏的位置和順序,表示實際應用中的某種有意義的信息,或者表示數據之間的某種關係。
根據線性表的實際存儲方式,分爲兩種實現模型:
•順序表,將元素順序地存放在一塊連續的存儲區裏,元素間的順序關係由它們的存儲順序自然表示。
•鏈表,將元素存放在通過鏈接構造起來的一系列存儲塊中。‘
下文將分開介紹兩種不同的結構
順序表
上圖表示的是順序表的基本形式,數據元素本身連續存儲,每個元素所佔的存儲單元大小固定相同,元素的下標是其邏輯地址,而元素存儲的物理地址(實際內存地址)可以通過存儲區的起始地址Loc (e0)加上邏輯地址(第i個元素)與存儲單元大小(c)的乘積計算而得,即:Loc(ei) = Loc(e0) + c*i 。故,訪問指定元素時無需從頭遍歷,通過計算便可獲得對應地址,其時間複雜度爲O(1)。
一個順序表的完整信息包括兩部分,一部分是表中的元素集合,另一部分是爲實現正確操作而需記錄的信息,即有關表的整體情況的信息,這部分信息主要包括元素存儲區的容量和當前表中已有的元素個數兩項。
注意,這裏有存在2種結構,一體式結構和分離式結構,當存儲表信息的單元與元素存儲區以連續的方式安排在一塊存儲區裏,兩部分數據的整體形成一個完整的順序表對象。一體式結構整體性強,易於管理。但是由於數據元素存儲區域是表對象的一部分,順序表創建後,元素存儲區就固定了。圖b爲分離式結構,表對象裏只保存與整個表有關的信息(即容量和元素個數),實際數據元素存放在另一個獨立的元素存儲區裏,通過鏈接與基本表對象關聯。
存儲區替換操作
一體式結構由於順序表信息區與數據區連續存儲在一起,所以若想更換數據區,則只能整體搬遷,即整個順序表對象(指存儲順序表的結構信息的區域)改變了。
分離式結構若想更換數據區,只需將表信息區中的數據區鏈接地址更新即可,而該順序表對象不變。
元素存儲區擴充
採用分離式結構的順序表,若將數據區更換爲存儲空間更大的區域,則可以在不改變表對象的前提下對其數據存儲區進行了擴充,所有使用這個表的地方都不必修改。只要程序的運行環境(計算機系統)還有空閒存儲,這種表結構就不會因爲滿了而導致操作無法進行。人們把採用這種技術實現的順序表稱爲動態順序表,因爲其容量可以在使用中動態變化。
擴充的兩種策略
•每次擴充增加固定數目的存儲位置,如每次擴充增加10個元素位置,這種策略可稱爲線性增長。
特點:節省空間,但是擴充操作頻繁,操作次數多。
•每次擴充容量加倍,如每次擴充增加一倍存儲空間。
特點:減少了擴充操作的執行次數,但可能會浪費空間資源。以空間換時間,推薦的方式。
增加元素
-
尾端加入元素,時間複雜度爲O(1)
-
非保序的加入元素(不常見),時間複雜度爲O(1)
-
保序的元素加入,時間複雜度爲O(n)
刪除操作
基本和添加操作相似
-
刪除表尾元素,時間複雜度爲O(1)
-
非保序的元素刪除(不常見),時間複雜度爲O(1)
-
保序的元素刪除,時間複雜度爲O(n)
python實現
Python中的list和tuple兩種類型採用了順序表的實現技術,具有前面討論的順序表的所有性質。
tuple是不可變類型,即不變的順序表,因此不支持改變其內部狀態的任何操作,而其他方面,則與list的性質類似。
#list的基本實現技術
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需要我們自己定義類型,這裏我直接上代碼,方便大家理解
#!usr/bin/env python
# -*- coding:utf-8 _*-
"""
@author: LSY
@file: maina.py
@time: 2020/02/13
@desc: 單鏈表相關操作
"""
# •is_empty() 鏈表是否爲空
# •length() 鏈表長度
# •travel() 遍歷整個鏈表
# •add(item) 鏈表頭部添加元素
# •append(item) 鏈表尾部添加元素
# •insert(pos, item) 指定位置添加元素
# •remove(item) 刪除節點
# •search(item) 查找節點是否存在
class SingleNode(object): #類的基類,不帶即默認
def __init__(self,item): #創建節點
self.item = item
self.next = None
class SingleLinkList():
def __init__(self):
self.head = None
def is_empty(self):
return self.head == None
def length(self):
cur = self.head
count = 0
while(cur != None):
count += 1
cur = cur.next
return count
def travel(self):
cur = self.head
while cur != None:
print(cur.item)
cur = cur.next
def add(self, item):
"""頭部添加元素"""
node = SingleNode(item)
node.next = self.head
self.head = node
def append(self,item):
"""尾部添加元素"""
node = SingleNode(item)
cur = self.head
while cur.next != None:
cur = cur.next
cur.next = node
def insert(self,pos,item):
"""指定位置添加元素"""
node = SingleNode(item)
length = self.length()
if pos <=0 :
self.add(item)
elif pos >= self.length()-1:
self.append(item)
else :
cur = self.head
count = 0
while(count != pos-1):
cur = cur.next
count += 1
node.next = cur.next
cur.next = node
def remove(self,item):
"""按照取值刪除,其他操作和添加類似"""
cur = self.head
pre = None
length = 1
if self.length() == 0: #判斷是否爲空表
print("空表無法刪除")
return None
while cur != None:
if cur.item == item :
break
else :
length +=1
pre = cur
cur = cur.next
if length == 1: #刪除表頭
self.head = cur.next
elif length == self.length()+1: #未找到元素
print("未找到刪除元素")
return None
else:
pre.next = cur .next
def search(self,item):
"""判斷節點受否存在"""
cur = self.head
while cur == None:
if cur.item == item:
return True
else:
cur = cur.next
return False
if __name__ == "__main__":
ll = SingleLinkList()
ll.add(1)
ll.add(2)
ll.append(3)
ll.insert(2, 4)
print("length:",ll.length())
ll.travel()
print(ll.search(3))
print(ll.search(5))
ll.remove(1)
print("length:",ll.length())
ll.travel()
鏈表與順序表的對比
鏈表失去了順序表隨機讀取的優點,同時鏈表由於增加了結點的指針域,空間開銷比較大,但對存儲空間的使用要相對靈活。
雖然表面看起來複雜度都是 O(n),但是鏈表和順序表在插入和刪除時進行的是完全不同的操作。鏈表的主要耗時操作是遍歷查找,刪除和插入操作本身的複雜度是O(1)。順序表查找很快,主要耗時的操作是拷貝覆蓋。因爲除了目標元素在尾部的特殊情況,順序表進行插入和刪除時需要對操作點之後的所有元素進行前後移位操作,只能通過拷貝和覆蓋的方法進行