資料結構(連結串列 雜湊表 二叉樹 AVL樹)
連結串列
連結串列是由一系列節點組成的元素集合。每個節點包含兩部分,資料域item和指向下一個節點的指標next。
通過節點之間的相互連線next,最終串聯成一個連結串列。
# 手動建立連結串列
class Node:
def __init__(self, item):
self.item = item
self.next = None
a = Node(1)
b = Node(2)
c = Node(3)
a.next = b
b.next = c
print(a.next.next.item) # 3
建立列表
頭插法(倒敘插)
# 手動建立連結串列
classNode: def __init__(self, item): self.item = item self.next = None
# 建立連結串列 頭插法
def create_linklist_head(li):
head = Node(li[0])
for element in li[1:]:
node = Node(element)# 建立節點
node.next = head # 把頭連起來
head = node
return head
尾插法(正序插)
# 手動建立連結串列
class Node: def __init__(self, item): self.item = item self.next = None
# 尾插法
def create_linklist_tail(li):
head = Node(li[0]) # 頭指向Node(li[0])
tail = head # 尾指向Node(li[0])
for element in li[1:]:
node = Node(element) # 建立節點
tail.next = node # 把尾巴連起來
tail = node # 新的node成為tail
return head
連結串列節點的插入
插入p節點 先連結4和2(如果先連結1和4的話原來的連結串列就斷了) 斷開1和2 再連結1和4
p.next = curNode.next # 先連結4和2
curNode.next = p # 再連結1和4
連結串列節點的刪除*(把p節點刪掉 先連結1和2 要不連結串列就斷了 找不到下一節點了)
p = curNode.next
curNode.next = curNode.next.next # 或者 curNode.next = p.next 連結1和2
del p # 再刪掉p
雙鏈表
雙鏈表的每個節點有兩個指標:一個指向後一個節點,另一個指向前一個節點。
如何建立雙鏈表?
# 雙鏈表
class Node(object): def __init__(self, item=None): self.item = item self.next = None self.prior = None
雙鏈表節點的插入:
p.next = curNode.next # 連結2和3
curNode.next.prior = p # 再連結3和2
p.prior = curNode # 連結2和1
curNode.next = p # 連結1和2
雙鏈表節點的刪除:
p = curNode.next
curNode.next = p.next # 連結1和3 斷開1和2
p.next.prior = curNode # 連結3和1 斷開2和1
del p
雜湊表
雜湊表一個通過雜湊函式來計算資料存 儲位置的資料結構,通常支援如下操作:
insert(key, value):插入鍵值對(key,value)
get(key):如果存在鍵為key的鍵值對則返回其value,否則返回空值
delete(key):刪除鍵為key的鍵值對
直接定址表
當關鍵字的全域U比較小時,直接定址是一種簡單而有效的方法。
直接定址技術缺點:
當域U很大時,需要消耗大量記憶體,很不實際
如果域U很大而實際出現的key很少,則大量空間被浪費
無法處理關鍵字不是數字的情況
雜湊
直接定址表:key為k的元素放到T列表的k位置上
改進直接定址表:雜湊(Hashing)
構建大小為m的定址表T
key為k的元素放到h(k)位置上(h(k)是一個函式,其將域U對映到表T[0,1,...,m-1])
雜湊表(Hash Table,又稱為散列表)
是一種線性表的儲存結構。雜湊表由一個直接定址表和一個雜湊函式組成。雜湊函式h(k)將元素關鍵字k作為自變數,返回元素的儲存下標。
假設有一個長度為7的雜湊表,雜湊函式h(k)=k%7。元素集合{14,22,3,5}的儲存方式如下圖。
14對7取餘數0 存到0位置上 , 22對7取餘1存到1的位置上,3對7取餘3存到3的位置上。。。(對7取餘隻能是六個位置)
雜湊衝突
由於雜湊表的大小是有限的,而要儲存的值的總數量是無限的,因此對於任何雜湊函式,都會出現兩個不同元素對映到同一個位置上的情況,這種情況叫做雜湊衝突。
比如h(k)=k%7, h(0)=h(7)=h(14)=...
解決雜湊衝突——開放定址法
開放定址法:如果雜湊函式返回的位置已經有值,則可以向後探查新的位置來儲存這個值。
線性探查:如果位置i被佔用,則探查i+1, i+2,……
(查的時候怎麼查呢 也是同理 探查直到遇到空的位置停止 要是沒有那就是表裡沒有此值)
二次探查:如果位置i被佔用,則探查i+12,i-12,i+22,i-22,……
二度雜湊:有n個雜湊函式,當使用第1個雜湊函式h1發生衝突時,則嘗試使用h2,h3,……
解決雜湊衝突——拉鍊法
拉鍊法:
雜湊表每個位置都連線一個連結串列,當衝突發生時,衝突的元素將被加到該位置連結串列的最後。
雜湊表——常見雜湊函式*
除法雜湊法: h(k) = k % m
乘法雜湊法: h(k) = floor(m*(A*key%1))
全域雜湊法: ha,b(k) = ((a*key + b) mod p) mod m a,b=1,2,...,p-1
雜湊表的應用——集合與字典
字典與集合都是通過雜湊表來實現的。 a = {'name': 'Alex', 'age': 18, 'gender': 'Man'}
使用雜湊表儲存字典,通過雜湊函式將字典的鍵對映為下標:
假設h('name') = 3, h('age') = 1, h('gender') = 4,
則雜湊表儲存為[None, 18, None, 'Alex', 'Man']
如果發生雜湊衝突,則通過拉鍊法或開發定址法解決
1、遠端伺服器一個檔案 本地一個檔案 有可能下到80%斷點了 重啟的時候先判斷我的檔案是不是下完了 比較自己的MD5值和伺服器的md5值是不是一樣
2、百度雲可能秒傳,因為伺服器上儲存好多檔案,你上傳的檔案可能是從別的地方下載的也有可能之前就被別人上傳過去了,我上傳這個電影的時候 軟體先算一下這個md5值 如果一樣表示已經有人傳過了,直接就把別人傳的移到你的雲盤目錄裡就行了
二叉樹
二叉樹的 鏈式 儲存:將二叉樹的節點定義為一個物件,節點之間通過 類似連結串列 的連結方式來連線。
節點定義:
from collections import deque
# 節點定義: class BiTreeNode: def __init__(self, data): self.data = data self.lchild = None # 左孩子 self.rchild = None # 右孩子 a = BiTreeNode("A") b = BiTreeNode("B") c = BiTreeNode("C") d = BiTreeNode("D") e = BiTreeNode("E") f = BiTreeNode("F") g = BiTreeNode("G") e.lchild = a e.rchild = g a.rchild = c c.lchild = b c.rchild = d g.rchild = f root = e
print(root.lchild.rchild.data) # c
二叉樹的遍歷方式:
前序遍歷:EACBDGF
中序遍歷:ABCDEGF (先將E放中間左右是AG ,再把分別把AG放中間A的左邊沒有右邊是C G的左邊沒有右邊是F,再把CF放中間 C左邊B右邊D F左右沒有)
後序遍歷:BDCAFGE (先左後右 左後自己 每次是根在最後 AGE , CAFGE , BDCAFGE)
層次遍歷:EAGCFBD
#! /usr/bin/env python # -*- coding: utf-8 -*- # Date: 2018/3/24 from collections import deque class BiTreeNode: def __init__(self, data): self.data = data self.lchild = None # 左孩子 self.rchild = None # 右孩子 a = BiTreeNode("A") b = BiTreeNode("B") c = BiTreeNode("C") d = BiTreeNode("D") e = BiTreeNode("E") f = BiTreeNode("F") g = BiTreeNode("G") e.lchild = a e.rchild = g a.rchild = c c.lchild = b c.rchild = d g.rchild = f root = e print(root,lchild.rchild.data) # 前序遍歷 def pre_order(root): if root: # 如果root不是空 print(root.data, end=',') # 列印自己 pre_order(root.lchild) pre_order(root.rchild) # 中序遍歷 def in_order(root): if root: in_order(root.lchild) print(root.data, end=',') in_order(root.rchild) # 後序遍歷 def post_order(root): if root: post_order(root.lchild) post_order(root.rchild) print(root.data, end=',') # 層次遍歷 def level_order(root): queue = deque() # 需要佇列 queue.append(root) while len(queue) > 0: # 只要隊不空 一直訪問 node = queue.popleft() # 出隊 print(node.data, end=',') if node.lchild: # 不是空就進隊 queue.append(node.lchild) if node.rchild: queue.append(node.rchild) level_order(root)
二叉搜尋樹
二叉搜尋樹是一顆二叉樹且滿足性質:
設x是二叉樹的一個節點。如果y是x左子樹的一個節點,那麼y.key ≤ x.key;
如果y是x右子樹的一個節點,那麼y.key ≥ x.key。
二叉搜尋樹的操作:查詢(查詢只是查樹的深度就能找到結果)、插入(同查詢)、刪除
import random class BiTreeNode: def __init__(self, data): self.data = data self.lchild = None # 左孩子 self.rchild = None # 右孩子 self.parent = None class BST: def __init__(self, li=None): self.root = None # 根節點 if li: for val in li: self.insert_no_rec(val) # 插入(遞迴) def insert(self, node, val): # node插入的節點 val要插入的值 if not node: # 如果還沒有節點 空樹 node = BiTreeNode(val) elif val < node.data: # 傳入的值小於 根節點 node.lchild = self.insert(node.lchild, val) # 放在左邊 node.lchild.parent = node # 左孩子的父親 elif val > node.data: node.rchild = self.insert(node.rchild, val) node.rchild.parent = node return node # 插入(非遞迴) def insert_no_rec(self, val): p = self.root if not p: # 空樹 self.root = BiTreeNode(val) return while True: if val < p.data: if p.lchild: p = p.lchild else: # 左孩子不存在 p.lchild = BiTreeNode(val) p.lchild.parent = p return elif val > p.data: if p.rchild: p = p.rchild else: p.rchild = BiTreeNode(val) p.rchild.parent = p return else: return # 查詢(遞迴) def query(self, node, val): if not node: # node是空 遞迴的終止條件 return None if node.data < val: return self.query(node.rchild, val) elif node.data > val: return self.query(node.lchild, val) else: return node # 查詢(非遞迴) def query_no_rec(self, val): p = self.root while p: if p.data < val: p = p.rchild elif p.data > val: p = p.lchild else: return p return None
二叉搜尋樹——刪除操作
1.如果要刪除的節點是葉子節點:直接刪除
2.如果要刪除的節點只有一個孩子:將此節點的父親與孩子連線,然後刪除該節點。
3. 如果要刪除的節點有兩個孩子:將其左子樹最大節點或右子樹的最小節點替換當前節點(該節點最多有一個右孩子)並刪除替換前的替換當前節點
#! /usr/bin/env python # -*- coding: utf-8 -*- # Date: 2018/3/24 import random class BiTreeNode: def __init__(self, data): self.data = data self.lchild = None # 左孩子 self.rchild = None # 右孩子 self.parent = None class BST: def __init__(self, li=None): self.root = None # 根節點 if li: for val in li: self.insert_no_rec(val)# 1直接刪除 def __remove_node_1(self, node): # 情況1:node是葉子節點(沒有孩子) if not node.parent: # 根節點是空 self.root = None # 變成空樹了 if node == node.parent.lchild: # 如果node是它父親的左孩子 node.parent.lchild = None # else: #右孩子 node.parent.rchild = None def __remove_node_21(self, node): # 情況2.1:node只有一個左孩子 if not node.parent: # 如果是根節點 self.root = node.lchild # 孩子變成了根 node.lchild.parent = None # 將父親置 空刪除 elif node == node.parent.lchild: # 如果此節點是他父親的左孩子 node.parent.lchild = node.lchild # 將此節點的父親與孩子連線,然後刪除該節點。 node.lchild.parent = node.parent # 在將此節點的孩子與父親連線 (二叉樹是鏈式儲存雙向連線) else: #是右孩子 node.parent.rchild = node.lchild node.lchild.parent = node.parent def __remove_node_22(self, node): # 情況2.2:node只有一個右孩子 if not node.parent: self.root = node.rchild elif node == node.parent.lchild: node.parent.lchild = node.rchild node.rchild.parent = node.parent else: node.parent.rchild = node.rchild node.rchild.parent = node.parent def delete(self, val): if self.root: # 不是空樹 node = self.query_no_rec(val) # 找到節點 if not node: # 如果樹裡面 不存在此節點 return False if not node.lchild and not node.rchild: #1. 葉子節點(沒有孩子 直接刪) self.__remove_node_1(node) elif not node.rchild: # 2.1 只有一個左孩子 self.__remove_node_21(node) elif not node.lchild: # 2.2 只有一個右孩子 self.__remove_node_22(node) else: # 3. 兩個孩子都有 (找右子樹的最小節點) min_node = node.rchild while min_node.lchild: # 有左孩子 min_node = min_node.lchild node.data = min_node.data # 替換 # 刪除min_node(此節點可定沒有左孩子) if min_node.rchild: self.__remove_node_22(min_node) else: self.__remove_node_1(min_node) # tree = BST([1,4,2,5,3,8,6,9,7]) # tree.in_order(tree.root) # print("") # # tree.delete(4) # tree.delete(1) # tree.delete(8) # tree.in_order(tree.root)
二叉搜尋樹的效率
平均情況下,二叉搜尋樹進行搜尋的時間複雜度為O(nlgn)。
最壞情況下,二叉搜尋樹可能非常偏斜。
解決方案:
隨機化插入
AVL樹
AVL樹:
AVL樹是一棵自平衡的二叉搜尋樹。
左子樹的高度減去右子樹的高度
AVL樹具有以下性質:(不會說是一邊是0個一邊是1萬個 保持大概減半)
根的左右子樹的高度之差的絕對值不能超過1 根的左右子樹都是平衡二叉樹
AVL樹——插入
插入一個節點可能會破壞AVL樹的平衡,可以通過旋轉操作來進行修正。
插入一個節點後,只有從插入節點到根節點的路徑上的節點的平衡可能被改變。我們需要找出第一個破壞了平衡條件的節點,稱之為K。K的兩顆子樹的高度差2。
不平衡的出現可能有4種情況
1.不平衡是由於對K的左孩子的左子樹插入導致的:右旋
2.不平衡是由於對K的右孩子的右子樹插入導致的:左旋
3.不平衡是由於對K的右孩子的左子樹插入導致的:右旋-左旋
4.不平衡是由於對K的左孩子的右子樹插入導致的:左旋-右旋
#! /usr/bin/env python # -*- coding: utf-8 -*- # Date: 2018/3/24 from bst import BiTreeNode, BST class AVLNode(BiTreeNode): # 繼承二叉樹 def __init__(self, data): BiTreeNode.__init__(self, data) self.bf = 0 # 插入節點 class AVLTree(BST): def __init__(self, li=None): BST.__init__(self, li) def rotate_left(self, p, c): # 左旋 s2 = c.lchild p.rchild = s2 if s2: s2.parent = p c.lchild = p p.parent = c p.bf = 0 c.bf = 0 return c def rotate_right(self, p, c): # 右旋 s2 = c.rchild p.lchild = s2 if s2: s2.parent = p c.rchild = p p.parent = c p.bf = 0 c.bf = 0 return c def rotate_right_left(self, p, c): # 右旋左旋 g = c.lchild s3 = g.rchild c.lchild = s3 if s3: s3.parent = c g.rchild = c c.parent = g s2 = g.lchild p.rchild = s2 if s2: s2.parent = p g.lchild = p p.parent = g # 更新bf if g.bf > 0: p.bf = -1 c.bf = 0 elif g.bf < 0: p.bf = 0 c.bf = 1 else: # 插入的是g p.bf = 0 c.bf = 0 return g def rotate_left_right(self, p, c): # 左旋右旋 g = c.rchild s2 = g.lchild c.rchild = s2 if s2: s2.parent = c g.lchild = c c.parent = g s3 = g.rchild p.lchild = s3 if s3: s3.parent = p g.rchild = p p.parent = g # 更新bf if g.bf < 0: p.bf = 1 c.bf = 0 elif g.bf > 0: p.bf = 0 c.bf = -1 else: p.bf = 0 c.bf = 0 return g def insert_no_rec(self, val): # 1. 和BST一樣,插入 p = self.root if not p: # 空樹 self.root = AVLNode(val) return while True: if val < p.data: if p.lchild: p = p.lchild else: # 左孩子不存在 p.lchild = AVLNode(val) p.lchild.parent = p node = p.lchild # node 儲存的就是插入的節點 break elif val > p.data: if p.rchild: p = p.rchild else: p.rchild = AVLNode(val) p.rchild.parent = p node = p.rchild break else: # val == p.data return # 2. 更新balance factor while node.parent: # node.parent不空 if node.parent.lchild == node: # 傳遞是從左子樹來的,左子樹更沉了 #更新node.parent的bf -= 1 if node.parent.bf < 0: # 原來node.parent.bf == -1, 更新後變成-2 # 做旋轉 # 看node哪邊沉 g = node.parent.parent # 為了連線旋轉之後的子樹 x = node.parent # 旋轉前的子樹的根 if node.bf > 0: n = self.rotate_left_right(node.parent, node) else: n = self.rotate_right(node.parent, node) # 記得:把n和g連起來 elif node.parent.bf > 0: # 原來node.parent.bf = 1,更新之後變成0 node.parent.bf = 0 break else: # 原來node.parent.bf = 0,更新之後變成-1 node.parent.bf = -1 node = node.parent continue else: # 傳遞是從右子樹來的,右子樹更沉了 #更新node.parent.bf += 1 if node.parent.bf > 0: # 原來node.parent.bf == 1, 更新後變成2 # 做旋轉 # 看node哪邊沉 g = node.parent.parent # 為了連線旋轉之後的子樹 x = node.parent # 旋轉前的子樹的根 if node.bf < 0: # node.bf = 1 n = self.rotate_right_left(node.parent, node) else: # node.bf = -1 n = self.rotate_left(node.parent, node) # 記得連起來 elif node.parent.bf < 0: # 原來node.parent.bf = -1,更新之後變成0 node.parent.bf = 0 break else: # 原來node.parent.bf = 0,更新之後變成1 node.parent.bf = 1 node = node.parent continue # 連結旋轉後的子樹 n.parent = g if g: # g不是空 if x == g.lchild: g.lchild = n else: g.rchild = n break else: self.root = n break tree = AVLTree([9,8,7,6,5,4,3,2,1]) tree.pre_order(tree.root) print("") tree.in_order(tree.root)