文章目錄
- 入門級書籍:《大話數據結構》(第二章 算法 & 第三章 線性表)的學習筆記
- 以下所有代碼均已上傳至github,傳送門:github.com/Lebhoryi/Algorithms
- 複製的代碼前面只有三個空格,玄學問題,具體的規範代碼看github
一、概念
-
算法:算法就是解決問題的技巧和方式。
官方(@.@):解決特定問題求解步驟的描述,在計算機中表現爲指令的優先序列,並且每條指令表示一個或多個操作。
-
算法的特性:輸入輸出、有窮性、確定性、可行性
-
算法的要求: 正確性、可讀性、健壯性、時間效率和存儲量低
-
算法效率的度量方法:
-
事後統計方法(不靠譜)
-
事前統計方法(推薦)
-
-
算法時間複雜度
-
最好壞情況
-
平均情況
-
-
算法空間複雜度
二、線性表
1. 定義
定義:0個或者多個數據元素的序列。
-
快捷判斷:假如有兩個及以上的數據元素,則第一個元素無前驅,最後一個元素無後繼
-
特殊情況:複雜線性表中,一個元素可以有多個數據項組成。例如班級裏的人是數據元素,每個人的各科成績是數據項。
學號 | 姓名 | 語文 | 數學 |
---|---|---|---|
1 | 張三 | 78 | 98 |
2 | 李四 | 87 | 90 |
3 | 王五 | 90 | 99 |
2. 線性表的抽象數據類型
-
抽象數據類型(重溫一下概念):將數據類型和相關操作捆綁在一起
-
線性表的操作:
-
創建和初始化
-
查找數據
-
插入數據
-
刪除數據
-
擼代碼時間
# coding=utf-8
'''
@ Summary: 有線性表A和B,實現AUB
@ Update:
@ file: 2.實現交集操作.py
@ version: 1.0.0
@ Author: [email protected]
@ Date: 19-9-24 下午3:18
'''
def union(la, lb):
for i in lb:
if i not in la:
la.append(i)
return la
if __name__ == "__main__":
la = [1, 2, 3]
lb = [2, 3, 4]
result = union(la, lb)
print(result)
3. 線性表的順序存儲結構的騷操作
- 獲得元素
# coding=utf-8
'''
@ Summary: 從線性表中獲得元素
@ Update:
@ file: 3.獲得元素.py
@ version: 1.0.0
@ Author: [email protected]
@ Date: 19-9-24 下午3:33
'''
def get_elem(i, l):
if not l:
return None
return l[i-1]
if __name__ == "__main__":
l = [1, 2, 3]
i = 1
result = get_elem(i, l)
print(result)
- 插入和刪除操作
print(l.insert(i,n)) 的輸出是None,因此分開寫
# coding=utf-8
'''
@ Summary: 在線性表中插入和刪除一個元素
@ Update:
@ file: 2-3.線性表-插入操作.py
@ version: 1.0.0
@ Author: [email protected]
@ Date: 19-9-24 下午3:39
'''
def list_insert(l, i, new_elem):
if not l or i < 0 or i > len(l):
return None
if len(l) <= max_lenth:
l.insert(i, new_elem)
return l
'''
python list 刪除的常用三種方法:
1. del list[3]
2. list.pop(index) # 刪除索引指向的元素
3. list.remove(elem) # 刪除指定的元素
'''
def list_del(l, i):
if not l or i < 0 or i > len(l):
return None
if len(l) <= max_lenth:
l.pop(i-1)
return l
if __name__ == "__main__":
l = [1, 2, 3]
i, new_elem = 1, 5
global max_lenth
max_lenth = 7
result = list_insert(l, i, new_elem)
print(result)
result2 = list_del(l, i)
print(result2)
結論:線性表的順序存儲結構,在讀數據時,不管是哪個位置,時間複雜度都是O(1);而插入或刪除時,時間複雜度都是O(n)。
4. 順序表的鏈式存儲結構
-
定義:用一組任意的存儲單元存儲線性表的數據元素
-
結點:數據元素,包含數據域和指針域兩部分
-
頭結點:單鏈表第一個結點之前,設定一個頭結點,順序是第0位。該數據域爲空或者存儲鏈表的長度,指針域指向第一個結點的數據域
-
頭指針:指向鏈表的第一個結點,假若有頭結點,則指向頭結點,鏈表可以沒有頭結點,但是一定要有頭指針
-
三、單鏈表的相關操作
1. 單鏈表的元素查找
# coding=utf-8
'''
@ Summary: 整表生成和刪除
@ Update:
@ file: 2-7.線性表-鏈表生成和刪除.py
@ version: 1.0.0
@ Author: [email protected]
@ Date: 19-9-25 下午10:12
'''
class Node(object):
def __init__(self, val):
self.val = val
self.next = None
class Link(object):
@staticmethod
def c_link(vals):
if not vals:
return None
# 鏈表可以沒有頭結點但是不能沒有頭指針,下面這行看不懂的去看下頭結點
head = Node(len(vals)) # 創建頭節點
move = head
for val in vals:
tmp = Node(val)
# 頭插法,新元素永遠插在第一個節點,頭結點之後
# move2 = head.next
# head.next = tmp
# tmp.next = move2
# 尾插法,新元素永遠插在最後一個節點
move.next = tmp
move = tmp
return head, len(vals)
@staticmethod
def p_link(link):
if not link:
return None
link = link.next
while link:
print(link.val, end=" ")
link = link.next
print()
@staticmethod
def d_link(link):
if not link:
return None
link.next = None
return link
if __name__ == "__main__":
link_a = [1, 2, 1, 4, 3]
link_a, l_link = Link.c_link(link_a)
Link.p_link(link_a)
# Link.p_link(Link.d_link(link_a))
2. 單鏈表的元素插入
# coding=utf-8
'''
@ Summary: 單鏈表的元素插入
@ Update:
@ file: 2-5.線性表-鏈表元素插入.py
@ version: 1.0.0
@ Author: [email protected]
@ Date: 19-9-25 下午6:45
'''
class ListNode(object):
def __init__(self, val):
self.val = val
self.next = None
class Link(object):
@staticmethod
def c_link(vals):
# creat link
if not vals:
return None
head = ListNode(len(vals))
move = head
for val in vals:
tmp = ListNode(val)
move.next = tmp
move = tmp
return head.next
@staticmethod
def p_link(ln):
# print link
if not ln:
return None
while ln:
print(ln.val, end=" ")
ln = ln.next
print()
@staticmethod
def l_link(link):
if not link:
return None
count = 0
while link:
count += 1
link = link.next
return count
def insert_elem(ln, node, s, lenth_link):
"""單鏈表中插入元素
:param ln: node
:param a: node
:param s: int
:return: node
"""
if not ln or s < 0 or s > lenth_link:
return None
cur = ln
count = 1
while count < s:
cur = cur.next
count += 1
node = ListNode(node)
node.next = cur.next
cur.next = node
return ln
if __name__ == "__main__":
link_a = [1, 2, 3, 4]
node_b, s = 5, 2 # 在第二個節點的位置插入5
link_a = Link.c_link(link_a)
lenth_link = Link.l_link(link_a)
Link.p_link(link_a)
link_b = insert_elem(link_a, node_b, s, lenth_link)
Link.p_link(link_b)
3. 單鏈表的元素刪除
# coding=utf-8
'''
@ Summary: 單鏈表中刪除元素
@ Update:
@ file: 2-6.線性表-鏈表元素刪除.py
@ version: 1.0.0
@ Author: [email protected]
@ Date: 19-9-25 下午8:59
'''
class Node(object):
def __init__(self, val):
self.val = val
self.next = None
class Link(object):
@staticmethod
def create_link(vals):
if not vals:
return None
head = Node(len(vals))
move = head
for val in vals:
tmp = Node(val)
move.next = tmp
move = tmp
return head.next
@staticmethod
def p_link(link):
if not link:
return None
while link:
print(link.val, end=" ")
link = link.next
print()
@staticmethod
def l_link(link):
if not link:
return None
count = 0
while link:
count += 1
link = link.next
return count
def del_link(ln, i, lenth_link):
if not ln or i < 0 or i > lenth_link:
return None
cur = ln
count = 1
# 找打要刪除的節點的上一個節點,pre.next = pre.next.next
while count < i-1:
cur = cur.next
count += 1
# print(cur.val)
cur.next = cur.next.next
return ln
if __name__ == "__main__":
link_a = [1, 3, 4, 5, 2]
link_a = Link.create_link(link_a)
lenth_link = Link.l_link(link_a)
# Link.p_link(link_a)
i = 3 # 刪除第二個節點
link_a = del_link(link_a, i, lenth_link)
Link.p_link(link_a)
4. 單鏈表的整表刪除和生成
# coding=utf-8
'''
@ Summary: 整表生成和刪除
@ Update:
@ file: 2-7.線性表-鏈表生成和刪除.py
@ version: 1.0.0
@ Author: [email protected]
@ Date: 19-9-25 下午10:12
'''
class Node(object):
def __init__(self, val):
self.val = val
self.next = None
class Link(object):
@staticmethod
def c_link(vals):
if not vals:
return None
head = Node(len(vals))
move = head
for val in vals:
tmp = Node(val)
# 頭插法,新元素永遠插在第一個節點,頭結點之後
# move2 = head.next
# head.next = tmp
# tmp.next = move2
# 尾插法,新元素永遠插在最後一個節點
move.next = tmp
move = tmp
return head, len(vals)
@staticmethod
def p_link(link):
if not link:
return None
link = link.next
while link:
print(link.val, end=" ")
link = link.next
print()
@staticmethod
def d_link(link):
if not link:
return None
link.next = None
return link
if __name__ == "__main__":
link_a = [1, 2, 1, 4, 3]
link_a, l_link = Link.c_link(link_a)
Link.p_link(link_a)
# Link.p_link(Link.d_link(link_a))
5. 單鏈表和順序存儲結構的優缺點
四、靜態鏈表
1. 定義
用數組來代替指針來描述單鏈表,讓數組的元素都是由兩個數據域組成,data和cur,其中數據域data用來存放數據元素,數據域cur相當於單鏈表中的next指針,存放該元素的後繼在數組中的下標,把這種用數組描述的鏈表叫做靜態鏈表,這種描述方法還有起名叫遊標實現法。
在c中和python、java中是不一樣的,c中的數組需要指定數組長度,存儲空間是固定的,不能動態的更改,否則會溢出,而在python或者java中不需要指定,系統自檢溢出,系統可以動態的增加空間。
2. 現狀
單鏈表使用的多,對於靜態鏈表掌握程度理解即可
Q:(騰訊面試題)快速找到未知長度單鏈表的中間節點
提示:走一步和走兩步
# coding=utf-8
'''
@ Summary: 快速找到未知長度單鏈表的中間節點
@ Update:
@ file: 2-9.尋找鏈表中間節點.py
@ version: 1.0.0
@ Author: [email protected]
@ Date: 19-9-26 下午6:49
'''
class Node(object):
def __init__(self, val):
self.val = val
self.next = None
class Link(object):
@staticmethod
def c_link(vals):
if not vals:
return None
head = Node(len(vals))
move = head
for val in vals:
tmp = Node(val)
move.next = tmp
move = tmp
return head.next
@staticmethod
def p_link(link):
if not link:
return None
while link:
yield link.val
link = link.next
print()
def find_mid_node(link):
cur = link
cur2 = link.next
while cur2 and cur2.next:
cur = cur.next
cur2 = cur2.next.next
return cur.val
if __name__ == "__main__":
list_a = [1, 4, 2, 5, 3, 2, 2]
link_a = Link.c_link(list_a)
for val in Link.p_link(link_a):
print(val, end=" ")
result = find_mid_node(link_a)
print(result)
四、循環鏈表
1. 定義
將單鏈表中終端節點的指針從空指針改爲指向頭結點,形成一個環,頭尾相連,稱循環鏈表
2. 循環鏈表和單鏈表的差異
循環鏈表和單鏈表的主要差異在於循環的判斷條件上,單鏈表是判斷p->next是否爲空,循環鏈表是判斷p->next不等於頭結點,則循環未結束。
3. 循環鏈表的有點
在單鏈表中採用頭結點時,可以用O(1)的時間訪問第一個結點,用O(n)的時間訪問到最後一個結點;採用指向終端結點的尾指針來表示循環鏈表時可以用O(1)的時間由鏈表指針訪問到最後一個結點
以下的代碼都是尾指針示例
# coding=utf-8
'''
@ Summary: 循環鏈表及操作
@ Update:
@ file: 2-10.線性表-循環鏈表.py
@ version: 1.0.0
@ Author: [email protected]
@ Date: 19-9-26 下午7:45
'''
class Node(object):
def __init__(self, val):
self.val = val
self.next = None
def __repr__(self): # 這個函數將內容‘友好’地顯示出來,否則會顯示對象的內存地址
return str(self.val)
class CLink(object):
@ staticmethod
def c_link(vals):
# 創建循環鏈表
if not vals:
return None
# 創建頭結點
head = Node(len(vals))
move = head # 給定一個變量,用做節點向前移動
for i in range(len(vals)):
tmp = Node(vals[i])
if i == 0: # 首節點循環
move.next = tmp
tmp.next = tmp
move = tmp
else:
tmp.next = move.next
move.next = tmp
move = tmp
# return head
# 循環鏈表用尾節點比頭結點友好,據說
rear = head
rear.next = move
return rear
@ staticmethod
def p_link(link):
# 打印循環鏈表
if not link:
return None
cur = link.next.next # cur爲第一個節點,接着爲當前節點
while cur.next != link.next.next:
# print(cur, link.next)
yield cur.val
cur = cur.next
yield cur.val
print()
@ staticmethod
def l_link(link):
# lenth of link
if not link:
return None
cur, count = link.next.next, 1
# 判斷條件是最後一個指向是否是第一個節點
while cur.next != link.next.next:
count += 1
cur = cur.next
return count
@ staticmethod
def add_node(link, node, s, l_link):
if not link or not node or s < 0:
return None
s = s % l_link if s > l_link else s
node = Node(node)
cur = link.next.next
if s == l_link:
# cur指向第一個節點,最後一個節點指向cur
node.next = cur
link.next.next = node
else:
count = 1
while count < s:
# 找到要插入的cur位置
cur = cur.next
count += 1
# cur指向下一個節點,當前節點指向cur
node.next = cur.next
cur.next = node
return link
@ staticmethod
def del_node(link, s, l_link):
if not link or s < 0:
return None
# 存在s>l_link的情況,需要對s做一步處理
s = s % l_link if s > l_link else s
cur = link.next.next
count = 1
while count < s:
cur = cur.next
count += 1
# 當前cur指向下下一個節點,跳過下一個節點
cur.next = cur.next.next
return link
@ staticmethod
def find_node(link, s, l_link):
if not link or s < 0:
return None
s = s % l_link if s > l_link else s
cur = link.next.next
count = 1
while count < s:
cur = cur.next
count += 1
return cur.val
if __name__ == "__main__":
list_a = [1, 2, 1, 5, 3, 7]
link_a = CLink.c_link(list_a)
[print(node, end=" ") for node in CLink.p_link(link_a)]
# 插入節點,在尾部插入節點的複雜度爲O(1)
lenth_link = CLink.l_link(link_a)
node, s = 666, 10
link_a = CLink.add_node(link_a, node, s, lenth_link)
[print(node, end=" ") for node in CLink.p_link(link_a)]
# delete node
link_a = CLink.del_node(link_a, s, lenth_link)
[print(node, end=" ") for node in CLink.p_link(link_a)]
# find node
node_a = CLink.find_node(link_a, s, lenth_link)
print(node_a)
-
手撕代碼練習1-1:約瑟夫問題
-
手撕代碼練習1-2:強化版約瑟夫問題
-
手撕代碼練習2:鏈表相接
-
手撕代碼練習3:判斷鏈表是否有環
-
手撕代碼練習4:魔術師發牌問題
-
手撕代碼練習5:拉丁方陣
上述練習題的代碼已經上傳至github,傳送門☞:github.com/Lebhoryi/Algorithms
五、雙向鏈表
1. 定義
在單鏈表的基礎上,新增一個指向前驅節點的指針域
2. 循環鏈表
雙向鏈表也可以是循環表:
3. 操作
- 插入操作
- 刪除操作
雙向鏈表保存有前後指針,因此插入和刪除都很方便,時間效率提高但是空間存儲變大,用空間換取時間
4. 代碼練習time
- 練習1(代碼已經上傳,地址同上):
一句話輸出字母:
[chr(i) for i in range(65, 91)]
對於執行較慢的程序,可以通過消耗更多的內存(空間換時間)來
進行優化;而消耗過多內存的程序,可以通過消耗更多的時間(時間換空間)來降低內存的消耗。