一、算法
1.算法的時間復雜度
大 O 記法,是描述算法復雜度的符號
O(1)
常數復雜度,最快速的算法。
取數組第 1000000 個元素
字典和集合的存取都是 O(1)
數組的存取是 O(1)
O(logN)
對數復雜度
假設有一個有序數組,以二分法查找
O(n)
線性復雜度
假設有一個數組,以遍歷的方式在其中查找元素 最壞情況是全部過一遍
O(nlogn)
求兩個數組交集,其中一個是有序數組
A 數組每一個元素都要在 B 數組中進行查找操作
每次查找如果使用二分法則復雜度是 logN
O(N^2)
平方復雜度
求兩個無序數組的交集
2.常見問題
""" 常見問題: O(1)和O(n)區別是什么? O(1) 是確定的 O(n) 是隨着數據規模增長而增長的 最壞情況是全部過一遍 如果第一次就找到了 還是O(n)嗎? 我們衡量的是性質 不是特例 以最壞情況來計算 復雜度就意味着查詢時間的多少嗎? 常數復雜度是直接取第一百萬個元素還是按順序來? 程序中循環的次數,而非真正意義上的時間,因為不同的機器可能執行相同的程序所耗時不同 常數復雜度是說 執行的時間 是確定的 數組的長度不一樣,存取元素的時間復雜度是不一樣的嗎? 數組是一個隨機存取的數據結構 無論多長, 存取元素的時間都是確定的, O(1) """ # 都是O(1) # def fun1(): # for i in range(1000000): # print(i) # for i in range(1000000): # print(i*2) # # def fun2(): # for i in range(1000000): # print(i) # print(i*2)
二、數據結構基礎
1.數組
連續的一塊內存,存取元素時間是 O(1),插入、刪除是 O(n)
2.鏈表
像火車,車廂掛車廂
以下標方式讀取元素的時間是 O(n)
插入、刪除是 O(1)
棧和隊列是鏈表的改良型
棧:先進后出(壓彈夾)
隊列:先進先出(銀行取號排隊)

class Node(): def __init__(self, element=None): self.e = element self.next = None head = Node() n1 = Node(111) n2 = Node(222) n3 = Node(333) n1.next = n2 n2.next = n3 head.next = n1 def append(node, element): """ 我們往 node 的末尾插入一個元素 :param node: 是一個 Node 實例 :param element: 任意類型的元素 """ n = node # 這里不應該使用 while n.next: 這樣的隱含類型轉換來判定 while n.next is not None: n = n.next # n 現在是最后一個元素 new_node = Node(element) n.next = new_node def prepend(head, element): n = Node(element) n.next = head.next head.next = n def pop(head): """ pop 是 stack 的兩個操作之一 push 入棧 pop 出棧 現在我們有這么 3 個函數 append 添加一個元素到末尾 prepend 添加一個元素到開頭 pop 刪除並返回末尾的元素 prepend + pop 就實現了 隊列 的 入隊(enqueue)和出隊(dequeue)操作 append + pop 就實現了 棧 的 入棧(push) 和 出棧(pop)操作 """ tail = head while tail.next is not None: tail = tail.next # 現在 tail 是最后一個元素了 n = head while n.next is not tail: n = n.next # 現在 n 是 tail 之前的元素了 n.next = None return tail.e def log_list(node): n = node s = '' while n is not None: s += (str(n.e) + ' > ') n = n.next print(s) # log_list(n1) # append(n1, 'gua') # log_list(n1) # prepend(head, 'hello') # log_list(head) # print('pop head ', pop(head)) # log_list(head)

""" 隊列的特點是「先進先出」,一般有這幾個操作 # enqueue 將一個元素存入隊列中 # dequeue 將一個元素從隊列中取出,並在隊列中刪除它 # empty 查看棧是否是空的 可以把隊列看做排隊,銀行叫號機就是隊列,先取號的先入隊,叫號的時候也就先出隊 """ # Node類是一個節點,有兩個屬性,一個存儲元素,一個存儲指向另一個節點的引用 class Node(): def __init__(self, element=None, next=None): self.element = element self.next = next # 這個函數是會在print的時候被自動調用,就是把這個Node顯示出來 def __repr__(self): return str(self.element) class Myqueue(): # 初始化函數,自動被調用 # 初始化Queue()類的時候,它有2個屬性,分別指向頭尾 def __init__(self): self.head = Node() self.tail = self.head # 如果head的next屬性為空,則說明隊列是空的 def empty(self): return self.head.next is None # 創建一個node # 讓tail.next指向它 # 讓tail指向它,tail現在就是新的隊尾了 def enqueue(self, element): n = Node(element) self.tail.next = n self.tail = n # 取出head.next指向的元素,如果隊列不是空的,就讓head.next指向node.next,這樣node就不在隊列中了 def dequeue(self): node = self.head.next if not self.empty(): self.head.next = node.next return node # 測試函數 def test(): q = Myqueue() q.enqueue(1) q.enqueue(2) q.enqueue(3) q.enqueue(4) print(q.dequeue()) print(q.dequeue()) print(q.dequeue()) print(q.dequeue()) if __name__ == '__main__': # 運行測試函數 test()

""" 數據結構的核心是,一個結構只有特定的操作,可以實現特定的功能 棧的特點是「先進后出」,一般有這幾個操作 # push 將一個元素存入棧中 # pop 將一個元素從棧中取出,並在棧中刪除它 # top 將一個元素從棧中取出 # is_empty 查看棧是否是空的 """ # Node類是一個節點,有兩個屬性,一個存儲元素,一個存儲指向另一個節點的引用 class Node(): def __init__(self, element=None, next=None): self.element = element self.next = next # 這個函數是會在print的時候被自動調用,就是把這個Node顯示出來 def __repr__(self): return str(self.element) class Mystack(): # 初始化函數,自動被調用 # 初始化Stack()類的時候,它有一個head屬性,值是一個空的Node def __init__(self): self.head = Node() # 如果head的next屬性為空,則說明棧是空的 def is_empty(self): return self.head.next is None # 創建一個node,並讓它指向當前head.next指向的元素,再把head.next指向它 def push(self, element): self.head.next = Node(element, self.head.next) # 取出head.next指向的元素,如果棧不是空的,就讓head.next指向node.next,這樣node就不在棧中了 def pop(self): node = self.head.next if not self.is_empty(): self.head.next = node.next return node # head.next就是棧里面第一個元素 def top(self): return self.head.next # 測試函數 def test(): s = Mystack() s.push(1) s.push(2) s.push(3) s.push(4) print(s.pop()) print(s.pop()) print(s.pop()) print(s.pop()) if __name__ == '__main__': # 運行測試函數 test()
3.字典(哈希表 對象 Map)
把字符串轉為數字作為下標存儲到數組中
字符串轉化為數字的算法是 O(1)
所以字典的存取操作都是 O(1)
除非對數據有順序要求,否則字典永遠是最佳選擇
字符串轉化為數字的算法
1,確定數據規模,這樣可以確定容器數組的大小 Size
2,把字符當作 N 進制數字得到結果
'qwe' 被視為 q* 1 + w * 10 + e* 100 得到結果 n
n % Size 作為字符串在數組中的下標
通常 Size 會選一個 素數
3, 當下標沖突(沖突屬於叫做碰撞)的時候, 我們有標准解決碰撞的辦法
鏈接法

class HashTable(object): def __init__(self): # table 是用來存儲數據的數組 # 先讓它有 10007 個格子好了 # 上課的時候說過, 這個尺寸最好選素數 # 這樣可以得到更為合理的下標分布 self.table_size = 10007 self.table = [0] * self.table_size # 這個魔法方法是用來實現 in not in 語法的 def __contains__(self, item): return self.has_key(item) def has_key(self, key): """ 檢查一個 key 是否存在, 時間很短, 是 O(1) 如果用 list 來存儲, 需要遍歷, 時間是 O(n) """ index = self._index(key) # 取元素 v = self.table[index] if isinstance(v, list): # 檢查是否包含我們要找的 key for kv in v: if kv[0] == key: return True # 如果得到的是 int 0 說明沒找到, 返回 False # 如果得到的是 list 但是遍歷結果沒有我們要找的 key 也是沒找到 return False def _insert_at_index(self, index, key, value): # 檢查下標處是否是第一次插入數據 v = self.table[index] data = [key, value] # 也可以用這個判斷 if v == 0: if isinstance(v, int): # 如果是第一次, 得到的是 int 0 # 那么就插入一個 list 來存, 以后相同 key 的元素都放這里面 # 注意我們把 key value 作為一個數組保存進去了, 這是因為 # 會出現相同 hash 值的 key # 這時候就需要比較原始信息來找到相應的數據 self.table[index] = [data] else: # 如果不是, 得到的會是 list, 直接 append self.table[index].append(data) def add(self, key, value): """ add 函數往 hashtable 中加入一對元素 我們先只支持字符串當 key; value是uuid生成的隨機字符串 """ # 先計算出下標 index = self._index(key) # 在下標處插入元素 self._insert_at_index(index, key, value) def get(self, key, default_value=None): """ 這個和 dict 的 get 函數一樣 """ index = self._index(key) # 取元素 v = self.table[index] if isinstance(v, list): # 檢查是否包含我們要找的 key for kv in v: if kv[0] == key: return kv[1] # 如果得到的是 int 0 說明沒找到, 返回 default_value # 如果得到的是 list 但是遍歷結果沒有我們要找的 key 也是沒找到 return default_value def _index(self, key): # 先計算出下標 return self._hash(key) % self.table_size def _hash(self, s): """ # 輸入一個字符串,生成一串對應的數字 下划線開始的函數被我們視為私有函數 但實際上還是可以在外部調用, 這只是一個給自己看的標記 """ n = 1 f = 1 for i in s: n += ord(i) * f f *= 10 return n def test(): import uuid names = [ 'gua', 'xiao', 'name', 'web', 'python', ] ht = HashTable() for key in names: value = uuid.uuid4() ht.add(key, value) print('add 元素', key, value) for key in names: v = ht.get(key) print('get 元素', key, v) print('魔法方法', 'gua' in ht) if __name__ == '__main__': test()
4.搜索樹

class Tree(object): def __init__(self, element=None): self.element = element self.left = None self.right = None def traversal(self): """ 樹的遍歷, 是一個遞歸操作 """ print(self.element) if self.left is not None: self.left.traversal() if self.right is not None: self.right.traversal() def reverse(self): self.left, self.right = self.right, self.left if self.left is not None: self.left.reverse() if self.right is not None: self.right.reverse() def test(): # 手動構建二叉樹 # 為什么手動這么麻煩呢, 因為一般都是自動生成的 # 這里只需要掌握性質就好 t = Tree(0) left = Tree(1) right = Tree(2) t.left = left t.right = right # 遍歷 t.traversal() if __name__ == '__main__': test()