數據機構按照其邏輯結構可分爲線性結構、樹結構、圖結構node
列表(其它語言是數組)是一種基本數據類型。
python
關於列表問題:算法
一、列表的元素是如何存儲的?(存內存地址(64位機器固定8bytes))數組
二、如何查找元素?(從列表起始位置的內存地址,查第幾個元素就起始位置加n*8)數據結構
三、列表不用顯示聲明長度?(python內部經過完整拷貝來調整列表長度)app
棧(Stack)是一個數據集合,能夠理解爲只能在一端進行插入或刪除操做的列表。dom
棧的特色:後進先出 LIFOide
棧的概念:棧頂,棧底函數
棧的基本操做:post
棧的實現
使用通常的列表便可實現棧
class Stack: def __init__(self): self.stack = [] def push(self, data): self.stack.append(data) def pop(self): return self.stack.pop() def get_top(self): if len(self.stack) > 0: return self.stack[-1] else: return None def is_empty(self): return len(self.stack) > 0
棧的應用 -- 括號匹配問題
括號匹配問題:給一個字符串,其中包括小括號、中括號、大括號,求該字符串中的括號是否匹配。
例如:
def brace_match(s): match = {'}': '{', ']': '[', ')': '('} stack = Stack() for ch in s: if ch in {'(', '[', '{'}: stack.push(ch) else: # ch in {'}',']',')'} if stack.is_empty(): return False elif stack.get_top() == match[ch]: stack.pop() else: return False if stack.is_empty(): return True else: return False print(brace_match('[{()}(){()}[]({}){}]')) print(brace_match('[]}'))
隊列(queue)是一個數據集合,僅容許在列表的一端進行插入,另外一端進行刪除。
隊列的實現
初步設想:列表+兩個下標指針
建立一個列表和兩個變量,front變量指向隊首,rear變量指向隊尾。初始時,front和rear都爲0
進隊操做:元素寫到li[rear]的位置,rear自增1
出隊操做:返回li[front]的元素,front自增1
出現d的狀況後,隊列沒法繼續添加元素,可是列表裏面有位置是空的,這一點很是很差
隊列的實現原理 -- 環形隊列
環形隊列:當隊尾指針front == Maxsize + 1時,再前進一個位置就自動到0
實現方式:求餘運算
隊首指針前進1:front = (front+1) % maxsize
隊尾指針前進1:rear = (rear+1) % maxsize
隊空條件:rear == front隊滿條件:(rear+1)% maxsize == front
class Queue: def __init__(self, size=100): self.queue = [0 for i in range(size)] self.size = size self.rear = 0 # 隊尾指針 self.front = 0 # 隊首指針 def push(self, element): if not self.is_filled(): self.rear = (self.rear + 1) % self.size self.queue[self.rear] = element else: raise IndexError("Queue is filled.") def pop(self): if not self.is_empty(): self.front = (self.front + 1) % self.size return self.queue[self.front] else: raise IndexError("Queue is empty.") # 判斷隊空 def is_empty(self): return self.rear == self.front # 判斷隊滿 def is_filled(self): return (self.rear + 1) % self.size == self.front q = Queue(5) for i in range(4): q.push(i) print(q.pop()) q.push(4)
隊列的內置模塊
使用方法:from collections import deque
建立隊列:queue = deque(li)
進隊:append
出隊:popleft
雙向隊列隊首進隊:appendleft
雙向隊列隊尾進隊:pop
棧和隊列的應用 -- 迷宮問題
解決方法1:棧--深度優先搜索(回溯法)
思路:從一個節點開始,任意找下一個能走的點,當找不到能走的點時,退回上一個點尋找是否有其餘方向的點。
(使用棧存儲當前路徑)
maze = [ [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 0, 0, 1, 0, 0, 0, 1, 0, 1], [1, 0, 0, 1, 0, 0, 0, 1, 0, 1], [1, 0, 0, 0, 0, 1, 1, 0, 0, 1], [1, 0, 1, 1, 1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 1, 0, 0, 0, 0, 1], [1, 0, 1, 0, 0, 0, 1, 0, 0, 1], [1, 0, 1, 1, 1, 0, 1, 1, 0, 1], [1, 1, 0, 0, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] ] dirs = [ lambda x, y: (x+1, y), lambda x, y: (x, y+1), lambda x, y: (x-1, y), lambda x, y: (x, y-1) ] def maze_path(x1, y1, x2, y2): stack = [] stack.append((x1, y1)) while len(stack) > 0: curNode = stack[-1] # 當前節點 if curNode[0] == x2 and curNode[1] == y2: # 走到終點了 for p in stack: print(p) return True # x,y 四個方向 x+1, y; x, y+1; x-1, y; x, y-1 for dir in dirs: next_Node = dir(curNode[0], curNode[1]) # 若是下一個節點能走 if maze[next_Node[0]][next_Node[1]] == 0: stack.append(next_Node) maze[next_Node[0]][next_Node[1]] = 2 break else: # 一個都找不到 maze[next_Node[0]][next_Node[1]] = 2 stack.pop() else: print('沒有路') return False maze_path(1,1,8,8)
鏈表是由一系列節點組成的元素集合。每一個元素包含兩部分,數據域item和指向下一個節點的指針next。經過節點之間的相互鏈接,最終串聯稱一個鏈表。
建立鏈表
class Node: def __init__(self, data): self.data = data self.next = None # 頭插法 def create_linklist(li): head = Node(li[0]) for num in li[1:]: node = Node(num) node.next = head head = node return head # 尾插法 def create_taillist(li): head = Node(li[0]) tail = head for num in li: node = Node(num) tail.next = node tail = node return head # 鏈表的遍歷 def print_link(lk): while lk: print(lk.data, end=',') lk = lk.next
鏈表節點的插入和刪除
插入:
p.next = curNode.next
curNode.next = p
刪除:
curNode.next=p.next
del p
雙鏈表
雙鏈表的每一個節點有兩個指針:一個指向後一個節點,另外一個指向前一個節點(單鏈表只有next)
雙鏈表的插入和刪除
插入:
p.next = curNode.next
curNode.next.prior = p
p.prior = curNode
curNode.next = p
刪除:
curNode.next = p.next
p.next.prior = curNode
del p
鏈表與順序表
哈希表是一個經過哈希函數來計算數據存儲位置的數據結構,一般支持以下操做:
知識儲備-- 直接尋址表
當關鍵字的全域U比較小時,直接尋址表是一種簡單而有效的方法
直接尋址技術缺點:
改進直接尋址表:哈希
哈希表(又稱爲散列表),是一種線性表的存儲結構。哈希表由一個直接尋址表和一個哈希函數組成。哈希函數h(k)將元素關鍵字k做爲自變量,返回元素的存儲下標。
假設有一個長度爲7的哈希表,哈希函數h(k)=k%7 元素集合{14, 22, 3, 5}的存儲方式以下圖:
哈希衝突
因爲哈希表的大小是有限的,而要存儲的值的總數量是無限的,由於對於任何哈希函數,都會出現兩個不一樣元素映射到同一個位置上的狀況,這種狀況叫作哈希衝突!
好比h(k)=k%7, h(0)=h(7)=h(14)...
解決哈希衝突的兩個辦法:
一、開放尋執法:若是哈希函數返回的位置已經有值,則能夠向後探查新的位置來存儲這個值。
二、拉鍊法:哈希表每一個位置都鏈接一個鏈表,當衝突發生時,衝突的元素將被加到該位置鏈表的最後
拉鍊法解決hash衝突代碼實現:
# 鏈表類 class LinkList: class Node: def __init__(self, item=None): self.item = item self.next = None class LinkListIterator: def __init__(self, node): self.node = node def __next__(self): if self.node: cur_node = self.node self.node = cur_node.next return cur_node.item else: raise StopIteration def __iter__(self): return self def __init__(self, iterable=None): self.head = None self.tail = None if iterable: self.extend(iterable) def append(self, obj): s = LinkList.Node(obj) if not self.head: self.head = s self.tail = s else: self.tail.next = s self.tail = s def extend(self, iterable): for obj in iterable: self.append(obj) def find(self, obj): for n in self: if n == obj: return True else: return False def __iter__(self): return self.LinkListIterator(self.head) def __repr__(self): return "<<"+", ".join(map(str, self))+">>" # 相似於集合的結構 class HashTable: def __init__(self, size=101): self.size = size self.T = [LinkList() for i in range(self.size)] def h(self, k): return k % self.size def insert(self, k): i = self.h(k) if self.find(k): print("Duplicated Insert") else: self.T[i].append(k) def find(self, k): i = self.h(k) return self.T[i].find(k) ht = HashTable() ht.insert(0) ht.insert(1) ht.insert(3) ht.insert(102) ht.insert(508) print(",".join(map(str, ht.T))) print(ht.find(203))
哈希表 -- 常見哈希函數
除法哈希法: 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':xcq, 'age':18, 'gender':'Man'}
使用哈希表存儲字典,經過哈希函數將字典的鍵映射爲下標。假設h('name')=3, h('age'=1),h('gender'=4), 則哈希表存儲爲[Node, 18, Node, 'xcq', 'Man']
若是發生哈希衝突,則經過拉鍊法或則開放尋址法解決。
樹是一種數據結構 好比:目錄結構
樹是一種能夠遞歸定義的數據結構 樹是由n個節點組成的集合
若是n=0, 那這是一顆空樹
若是n>0, 那存在1個節點做爲樹的根節點,其餘節點能夠分爲m個集合,每一個集合自己又是一棵樹
二叉樹:度不超過2的樹(節點最多有兩個叉)
二叉樹的鏈式存儲:
將二叉樹的節點定義爲一個對象,節點之間經過相似鏈表的鏈接方式來鏈接
二叉樹的4種遍歷方式:
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 # 前序遍歷 def pre_order(root): if 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=',') # 層級遍歷 from collections import deque def level_order(root): que = deque() que.append(root) while len(que): node = que.popleft() print(node.data, end=',') if node.lchild: que.append(node.lchild) if node.rchild: que.append(node.rchild)
二叉搜索樹
二叉搜索樹是一棵二叉樹且知足性質:設x是二叉樹的一個節點。若是y是x的左子樹的一個節點,那麼y.key <= x.key;若是y是x的右子樹的一個節點,那麼y.key >= x.key
(左邊的都比根節點小,右邊的都比根節點大)
二叉搜索樹的操做:查詢、插入、刪除
須要注意的是刪除操做
刪除操做分3種狀況:
一、若是要刪除的節點是葉子節點:直接刪除
二、若是要刪除的節點只有一個孩子:將此節點的父親與孩子鏈接,而後刪除該節點
三、若是要刪除的節點有兩個孩子:將其右子樹最小節點刪除,並替換當前節點。
二叉搜索樹查詢、插入、刪除代碼實現
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): 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: 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 def pre_order(self, root): if root: print(root.data, end=',') self.pre_order(root.lchild) self.pre_order(root.rchild) def in_order(self, root): if root: self.in_order(root.lchild) print(root.data, end=',') self.in_order(root.rchild) def post_order(self, root): if root: self.post_order(root.lchild) self.post_order(root.rchild) print(root.data, end=',') 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(nlogn)
最壞狀況下,二叉搜索樹可能很是偏斜(線性)
解決方案:
AVL樹是一顆自平衡的二叉搜索樹。
AVL樹具備一下性質:
AVL樹--插入
插入一個節點可能會破壞AVL樹的平衡,能夠經過旋轉操做來進行修正。
插入一個節點後,只有從插入節點到根節點的路徑上的節點的平衡可能被改變。咱們須要找出第一個破壞了平衡條件的節點,稱之爲K。k的左右子樹高度差的絕對值爲2
不平衡的出現可能會有4種狀況
一、左旋
不平衡是因爲對k的右孩子的右子樹插入致使的
二、右旋
不平衡是因爲對k的左孩子的左子樹插入致使的
三、右旋-左旋
不平衡是因爲對K的右孩子的左子樹插入致使的
四、左旋-右旋
AVL樹插入操做代碼實現:
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同樣插入(AVL要一邊插入一邊控制balance factor) 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: 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