數據結構:
線性結構,元素存在一對一的關系,例如列表
樹結構,元素存在一對多的關系,例如層級結構
圖結構,元素存在多對多的關系,例如地圖
列表:
列表:
1、列表中的元素是怎么存儲的?
是順序存儲的,是一塊連續的內存
2、列表的操作:按下標查找,插入元素,刪除元素
先說C中的數組:
查找的時間復雜度是O(1),因為知道首地址+每個元素的大小*index就找到了地址
數組與列表有兩點不同:
1、數組元素類型要相同
2、數組長度固定
python中的列表如何實現?
Python中列表的類型可以不同
Python中的列表存放的不是值而是地址,32位機器一個地址占4個字節,地址的長度是固定的
列表長度不固定
Python解釋器自動維護的,發現長度不夠會新開辟一塊內存,把之前的列表進行拷貝
python下標查找和append復雜度是O(1)
python列表的插入和刪除的復雜度是O(n)
棧:
棧(stack)是一個數據集合,可以理解為只能在一端進行插入或刪除操作的列表
棧的特點:LIFO
棧的概念:棧頂,棧底
棧的基本操作:(用列表可以實現)
進棧:li.append
出棧: li.pop()
取棧頂: li[-1]
class Stack(object):
def __init__(self):
self.stack = []
def pop(self):
return self.stack.pop()
def push(self, element):
return self.stack.append(element)
def get_top(self):
if self.stack:
return self.stack[-1]
else:
return None
棧的應用:括號匹配問題 '{[()[]{}]}'遇到左括號就入棧,遇到右括號就看棧頂的左括號,把匹配的左括號出棧,匹配完后棧為空說明匹配。
隊列:
隊列的實現:

class Queue: def __init__(self, size=100): self.queue = [0 for _ in range(size)] self.size = size self.rear = 0 self.front = 0 def push(self, element): if self.is_filled(): raise IndexError("Queue is filled.") self.rear = (self.rear + 1) % self.size self.queue[self.rear] = element def pop(self): if self.is_empty(): raise IndexError("Queue is empty.") self.front = (self.front + 1) % self.size return self.queue[self.front] 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.is_filled())
Python隊列內置模塊
python中有線程queue,進程queue,這里是普通的雙向queue
使用方法:from collections import deque
創建隊列:q = deque([1, 2, 3], maxlen=5)
進隊:append()
出隊:popleft()
隊首進隊:appendleft()
隊尾出隊:pop()
棧和隊列的應用:迷宮問題
# 用棧實現迷宮問題:找到的不一定是最短路徑
# 深度優先搜索,又叫回溯法
# 棧里存放走的路徑,先選一個方向,走一步,入棧,再選方向,走一步,入棧....,當前所在位置就是棧頂的值
# 如果某一步走不通,退一步,出棧,再換方向走,循環,直到到達終點

# 用棧實現迷宮問題: # 深度優先搜索,又叫回溯法 # 棧里存放走的路徑,先選一個方向,走一步,入棧,再選方向,走一步,入棧....,當前所在位置就是棧頂的值 # 如果某一步走不通,退一步,出棧,再換方向走,循環,直到到達終點 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 - 1, y), lambda x, y: (x, y - 1), 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: # 走到終點了 print(stack) return # 四個方向,隨便選一個能走通的路走,走過的標記為2,下次不走 for dir in dirs: nextNode = dir(curNode[0], curNode[1]) # 如果下個節點能走 if maze[nextNode[0]][nextNode[1]] == 0: stack.append(nextNode) maze[nextNode[0]][nextNode[1]] = 2 break else: stack.pop() else: print('沒有路') maze_path(1, 1, 8, 8)
# 使用隊列實現迷宮問題,廣度優先搜索,找到的一定是最短路徑
# 使用隊列存儲當前正在考慮的節點
# 為了計算路徑,還需要用一個列表來保存“哪個點讓這個點進隊列的”

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 - 1, y), lambda x, y: (x, y - 1), lambda x, y: (x, y + 1), ] from collections import deque def print_path(path): ''' 打印路徑 ''' curNode = path[-1] realpath = [] while curNode[2] != -1: realpath.append(curNode[0:2]) curNode = path[curNode[2]] realpath.append(curNode[0:2]) # 起點 realpath.reverse() print(realpath) def maze_path_queue(x1, y1, x2, y2): queue = deque() queue.append((x1, y1, -1)) # 第三個數表示誰引發它進隊列的,存path中的下標 path = [] while len(queue) > 0: curNode = queue.popleft() path.append(curNode) # 出隊列的放入path,用於計算路徑 if curNode[0] == x2 and curNode[1] == y2: # 走到終點了 print_path(path) return for dir in dirs: nextNode = dir(curNode[0], curNode[1]) if maze[nextNode[0]][nextNode[1]] == 0: queue.append((nextNode[0], nextNode[1], len(path) - 1)) # path中最后一個元素就是引發它進隊列的點 maze[nextNode[0]][nextNode[1]] = 2 else: print("沒有路") maze_path_queue(1, 1, 8, 8)
棧和隊列的應用 :廣度優先和深度優先遍歷文件夾

import os def bfs_scan(dir): """廣度優先""" queue = [] # 先進先出:queue.append() queue.pop(0) queue.append(dir) while len(queue) > 0: tmp = queue.pop(0) if os.path.isdir(tmp): for f in os.listdir(tmp): abs_path = os.path.join(tmp, f) if os.path.isdir(abs_path): queue.append(abs_path) else: print("找到文件>>", abs_path) else: print("找到文件>>", tmp) if __name__ == '__main__': dir = "C:\\Users\\Administrator\\Desktop\\test" bfs_scan(dir)

import os def dfs_scan(dir): """深度優先""" stack = [] # 后進先出:queue.append() queue.pop() stack.append(dir) while len(stack) > 0: tmp = stack.pop() if os.path.isdir(tmp): for f in os.listdir(tmp): abs_path = os.path.join(tmp, f) if os.path.isdir(abs_path): stack.append(abs_path) else: print("找到文件>>", abs_path) else: print("找到文件>>", tmp) if __name__ == '__main__': dir = "C:\\Users\\Administrator\\Desktop\\test" dfs_scan(dir)
鏈表:
順序表與鏈表時間復雜度對比:
按元素值查找:O(n)
按下標查找:O(1) O(n)
在某元素后插入:O(n) O(1)
刪除某元素:O(n) O(1)
總結:
1.鏈表在插入和刪除的操作上明顯快於順序表
2.鏈表的內存可以更靈活的分配
3.可以實現堆和棧
哈希表
哈希表:
哈希表通過哈希函數來計算數據存儲位置的數據結構,通常支持如下操作
insert:插入鍵值對
get:如果存在鍵為key的返回其value,否則返回空
delete:刪除為key的鍵值對
直接尋址表:
直接尋址技術缺點:
域U很大時,需要消耗大量內存,不實際
域U很大而實際出現的key很少,則大量空間被浪費
無法處理關鍵字不是數字的情況
直接尋址表:key=k的元素放在列表的k位置
直接尋址表改為哈希表:
1、構建大小為m的尋址表T
2、key=k的元素放到h(k)位置上
3、h(k)是一個函數,作用是將域U映射到表T[0,1,2,...,m-1]
哈希表
哈希表又稱散列表,是一種線性表的存儲結構。哈希表由一個
直接尋址表和一個哈希函數組成。哈希函數h(k)將元素關鍵字作為自變量,
返回元素的存儲下標。
假設有一個長度為7的哈希表,哈希函數h(k)=k%7,元素集合
{14,22,3,5}的存儲方式如下圖:
哈希沖突:
由於哈希表的大小是有限的,而要存儲的值的總數量是無限的,
因此對於任何哈希函數,都會出現兩個不同元素映射到同一個位置上的情況,
這種情況叫做哈希沖突.
比如h(k)=k%7, h(0)=h(7)=h(14)=...
解決hash沖突的兩種方式:
一、開放尋址法:如果哈希函數返回的位置已經有值,則可以向后探查新的位置來存儲這個值。
線性探查:如果位置i被占用,則探查i+1,i+2,...查找的時候,也要進行線性探查
二次探查:如果位置i被占用,則探查i+1²,i-1²,i+2²,i-2²,...
二度哈希:有n個哈希函數,當使用第1個哈希函數h1發生沖突時,則嘗試使用h2,h3...
這個方式大家不太喜歡,有可能hash表滿了,就肯定沒空間了
二、拉鏈法
哈希表每個位置都連接一個鏈表,當沖突發生時,沖突的元素將被加到該位置鏈表的最后。
查找的時候,先找到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 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 _ in range(self.size)] def h(self, k): ''' hash函數 ''' 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) def __repr__(self): return ",".join(map(str, self.T)) ht = HashTable() ht.insert(0) ht.insert(1) ht.insert(0) ht.insert(202) print(ht) print(ht.find(202))
23223