2 鏈表
首先複習順序表
事實上右側的順序表已經很像鏈表的形式,但不同在於一開始規定了順序表的大小而鏈表是一種手拉手的形式。
直觀感受鏈表(不同的存儲空間)(找一根線串起來數據)
鏈表的定義:鏈表(Linked list)是一種常見的基礎數據結構,是一種線性表,但是不像順序表一樣連續存儲數據,而是在每一個節點(數據存儲單元)裏存放下一個節點的位置信息(即地址)(屁股保存地址)。
單向鏈表
第一個節點叫頭節點最後一個叫尾節點
單鏈表的操作
is_empty() 鏈表是否爲空
length() 鏈表長度
travel() 遍歷整個鏈表
add(item) 鏈表頭部添加元素
append(item) 鏈表尾部添加元素
insert(pos, item) 指定位置添加元素
remove(item) 刪除節點
search(item) 查找節點是否存在
此爲創建一個節點的模型,應該是每個節點的第二個元素儲存指向下一個節點元素的地址
下圖就是__head 爲頭節點,node是數據通過Node構造爲節點的形式(代碼中體現)
代碼實現:
# 節點實現
"""class SingleNode(object):
#單鏈表的節點
def __init__(self, item):
# _item存放數據元素
self.item = item
# _next是下一個節點的標識
self.next = None
"""
# 構造一個節點 類
class Node(object):
"""節點"""
def __init__(self, elem): # init 表示初始化,elem指把數據保存下來 elem作爲參數進來
self.elem = elem # 一個節點應該有兩個元素 所以將元素的值放在 self.elem中
self.next = None # self.next 存放下一個節點數據的地址,一開始指向誰不知道所以指向空(None)
class SingleLinkList(object):
"""單鏈表"""
def __init__(self, node=None): # 3: 如果用戶不輸入頭節點值 當成默認參數 None 直接創建空列表 None 其實又回到了下面一行
# self.__head = None 1:一上來設置爲空 意思是不指向任何節點 | 私有加 __ 意義:只有這個函數內部使用
self.__head = node # 2:在上一行的基礎上人性化,用戶如果先構造節點,就將頭指向這個頭節點 node
def is_empty(self):
"""鏈表是否爲空"""
return self.__head is None # class在變量和None進行比較時,應該使用 is。
# 可以把比較的值直接作爲返回值 如果相等即爲真就是1 即返回1
# 只要頭節點指的是空那麼就是空鏈表
def length(self):
"""鏈表長度"""
cur = self.__head # 讓它等於指向表頭指向的第一個節點,cur 用於移動遍歷元素
# count 記錄數量
count = 0
while cur is not None: # count 從0開始的好處就是能夠記錄空表的長度,一個代碼實現空表與一般表的長度計算
count += 1
cur = cur.next # cur指向了 下一個節點
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) # 把item這個數據封裝成鏈表所需要的節點形式
node.next = self.__head
self.__head = node # 當一開始是空的時候 頭和節點都指向None 所以跑一遍上述兩行還是滿足的
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):
"""指定位置添加元素
:param item: 元素
:param pos從0開始
"""
if pos <= 0: # 如果輸入位置是<=0的默認是在頭部插入
ll.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):
"""鏈表查找節點是否存在,並返回True或者False"""
cur = self.__head
while cur is not None:
if cur.elem == item:
return True
else:
cur = cur.next
return False
# 測試代碼
if __name__ == "__main__":
ll = SingleLinkList()
print(ll.is_empty())
print(ll.length())
ll.append(1)
print(ll.is_empty())
print(ll.length())
ll.append(2)
ll.add(8)
ll.append(3)
ll.append(4)
ll.append(5)
ll.append(6)
ll.insert(-1, 9)
ll.travel()
ll.insert(2, 100)
ll.travel()
ll.insert(10, 200)
ll.travel()
ll.remove(200)
ll.travel()
ll.insert(20, 200)
ll.travel()
ll.insert(21, 200)
ll.travel()
ll.remove(200)
ll.travel()
一些說明:
插入的原理:
代碼順序不能錯,如果先讓pre.next先指向400,整個鏈表後半部分直接丟失。
其餘的都是類似的原理保證好先後順序,考慮好特殊情況。不足是不能夠刪除重複元素
總結:
這樣的一組序列元素的組織形式,我們可以將其抽象爲線性表。
根據線性表的實際存儲方式,分爲兩種實現模型:
順序表: 將元素順序地存放在一塊連續的存儲區裏,元素間的順序關係由它們的存儲順序自然表示。
鏈表: 將元素存放在通過鏈接構造起來的一系列存儲塊中。
單鏈表和順序表的對比
鏈表失去了順序表隨機讀取的優點,同時鏈表由於增加了結點的指針域,空間開銷比較大,但對存儲空間的使用要相對靈活。
操作 | 鏈表 | 順序表 |
---|---|---|
訪問元素 | O(n) | O(1) |
在頭部插入/刪除 | O(1) | O(n) |
在尾部插入/刪除 | O(n) | O(1) |
在中間插入/刪除 | O(n) | O(n) |
注意雖然表面看起來複雜度都是 O(n),但是鏈表和順序表在插入和刪除時進行的是完全不同的操作。鏈表的主要耗時操作是遍歷查找,刪除和插入操作本身的複雜度是O(1)。順序表查找很快,主要耗時的操作是拷貝覆蓋。因爲除了目標元素在尾部的特殊情況,順序表進行插入和刪除時需要對操作點之後的所有元素進行前後移位操作,只能通過拷貝和覆蓋的方法進行。