AI大模型從0到1記錄學習 數據結構和算法 day18

3.3.1 棧的概述
棧(Stack)是一個線性結構,其維護了一個有序的數據列表,列表的一端稱為棧頂(top),另一端稱為棧底(bottom)。棧對數據的操作有明確限定,插入元素只能從棧頂進行,刪除元素也只能棧頂開始逐個進行,通常將插入元素稱為入棧(push),刪除元素稱為出棧(pop)。正是由于上述規定,棧保證了后進先出的原則(LIFO,Last-In-First-Out)。

棧的底層實現既可以選擇數組也可以選擇鏈表,只要能保證后進先出的原則即可。
3.3.2 棧的功能定義
方法 說明
size() 返回棧中元素個數
is_empty() 判斷棧是否為空
push(item) 將新元素壓入棧中
pop() 獲取棧頂元素,并將棧頂元素彈出棧
peek() 獲取棧頂元素,但不彈出棧
3.3.3 棧的實現
使用動態數組實現一個棧。
class Stack:
def init(self):
“”“初始化棧”“”
self.__size = 0
self.__items = []

@property
def size(self):"""獲取棧元素個數"""return self.__sizedef is_empty(self):"""判斷棧是否為空"""return self.__size == 0def push(self, item):"""入棧"""self.__items.append(item)self.__size += 1def pop(self):"""出棧"""if self.is_empty():raise Exception("棧為空")item = self.__items[self.__size - 1]del self.__items[self.__size - 1]self.__size -= 1return itemdef peek(self):"""訪問棧頂元素"""if self.is_empty():raise Exception("棧為空")return self.__items[self.__size - 1]

3.3.4 棧的應用
1)有效括號
力扣20題https://leetcode.cn/problems/valid-parentheses/description/
(1)題目描述
給定一個只包括“(”,“)”,“[”,“]”,“{”,“}”的字符串s,判斷字符串是否有效。
有效字符串需滿足:
? 左括號必須用相同類型的右括號閉合。
? 左括號必須以正確的順序閉合。
? 每個右括號都有一個對應的相同類型的左括號。
(2)示例
示例 1:
輸入:s = “()”
輸出:true
示例 2:
輸入:s = “()[]{}”
輸出:true
示例 3:
輸入:s = “(]”
輸出:false
示例 4:
輸入:s = “([])”
輸出:true
(3)思路分析
遇到左括號則入棧,遇到右括號則出棧一個左括號與之匹配,如果能夠匹配則繼續,如果匹配失敗或者棧為空則返回False。
(4)代碼實現
class Solution:
def isValid(self, s):
stack = []
for i in s:
match i:
case “(” | “[” | “{”:
stack.append(i)
case “)”:
if (not stack) or (stack.pop() != “(”):
return False
case “]”:
if (not stack) or (stack.pop() != “[”):
return False
case “}”:
if (not stack) or (stack.pop() != “{”):
return False
return True if not stack else False

if name == “main”:
solution = Solution()
s = “()[]{}”
print(s, solution.isValid(s))
s = “(]”
print(s, solution.isValid(s))
s = “([)]”
print(s, solution.isValid(s))
s = “{[]}”
print(s, solution.isValid(s))
3.4 隊列
3.4.1 隊列的概述
隊列(Queue)也是一個線性結構,其同樣維護了一個有序的數據列表,隊列的一端稱為隊首,另一端稱為隊尾。隊列也對數據操作做出了明確限定,插入元素只能從隊尾進行,刪除元素只能從隊首進行,通常將插入操作稱為入隊(enqueue),將刪除操作稱為出隊(dequeue)。也正是由于上述限制,隊列保證了先進先出(FIFO,First-In-First-Out)的原則。

隊列的底層實現既可以選擇數組也可以選擇鏈表,只要能保證先進先出的原則即可。
常見的隊列包括兩種:
? 單向隊列:只能從一端插入數據,從另一端刪除數據,遵循先進先出。
? 雙向隊列:在隊列的兩端都可以進行插入和刪除操作。
3.4.2 隊列的功能定義
方法 說明
size() 返回隊列中元素個數
is_empty() 判斷隊列是否為空
push(item) 向隊尾添加元素
pop() 從隊首取出元素
peek() 訪問隊首元素
3.4.3 隊列的實現
使用鏈表實現一個單向隊列。
class Node:
def init(self, data):
self.data = data
self.next = None

class Queue:
def init(self):
“”“初始化隊列”“”
self.__head = None
self.__tail = None
self.__size = 0

@property
def size(self):"""獲取隊列元素個數"""return self.__sizedef is_empty(self):"""判斷隊列是否為空"""return self.__size == 0def push(self, data):"""入隊"""node = Node(data)if self.is_empty():self.__head = nodeself.__tail = nodeelse:self.__tail.next = nodeself.__tail = nodeself.__size += 1def pop(self):""" "出隊"""if self.is_empty():raise Exception("隊列為空")data = self.__head.dataself.__head = self.__head.nextself.__size -= 1return datadef peek(self):"""訪問隊首元素"""if self.is_empty():raise Exception("隊列為空")return self.__head.data

3.5 哈希表
3.5.1 哈希表的概述
哈希表(Hash Table,也叫散列表),由一系列鍵值對(key-value pairs)組成,并且可以通過鍵(key)查找對應的值(value)。哈希表通過建立key與value之間的映射,實現高效的查詢,我們向哈希表中輸入一個key,可以在O(1)的時間內獲取對應的value。
例如通過客戶id獲取客戶姓名:

哈希表常見的一個操作是根據key來查找value,考慮到數組查詢效率最高,選擇基于數組實現哈希表。利用哈希函數計算key的哈希值,然后將哈希值映射到數組索引。在實現過程中我們可能會遇到如下問題:
? 如何將一個個key映射到數組的索引?
? 如果多個key映射到數組同一個索引怎么辦?
? 數組長度是固定的,如果后續元素過多,大于數組長度怎么辦?
1)哈希函數
哈希表的核心組件是哈希函數。該函數將key轉換為一個數組索引。哈希函數的目標是盡量均勻地將所有可能的key分布到表的不同位置,以減少沖突的發生。
哈希函數的執行步驟分為兩步:
? 通過某種哈希算法計算出key的哈希值。
? 哈希值對數組長度取余,獲取key對應的數組索引。
index = hash(key) % capacity
例如我們使用一個簡單的哈希算法 hash(key)=key 將客戶id映射到一個長度為8的數組的索引,即 index = key % 8 。

常見的哈希算法:
? 通用哈希算法:除法哈希、乘法哈希、MurmurHash、CityHash。
? 加密哈希算法:MD5(已被成功攻擊)、SHA-1(已被成功攻擊)、SHA-2、SHA-3。
? 文件完整性檢查算法:Adler-32、CRC32。
2)哈希沖突
哈希函數可能會將不同的鍵值映射到同一個索引位置,這就是所謂的哈希沖突。處理沖突的方式有多種,最常見的兩種是鏈式法(Chaining)和開放尋址法(Open Addressing)。
(1)鏈式法
將發生碰撞的每個鍵值對作為一個節點(Node)組成一個鏈表(Linked List),然后將鏈表的頭節點保存在數組的目標位置中。這樣一來,向字典中寫入數據時,若發現數組的目標位置已有數據,那么就將當前的鍵值對作為一個節點插入鏈表;從字典中讀取數據時,則從數組的目標位置獲取鏈表,并進行遍歷,直到找到目標數據。

(2)開放尋址法
當發生沖突時根據某種探查策略尋找下一個空槽位。常見的探查策略包括:
? 線性探查(Linear Probing):如果當前位置已經被占用,就探查下一個位置。
? 二次探查(Quadratic Probing):以平方的步長進行探查。
? 雙重哈希(Double Hashing):使用另一個哈希函數來計算新的索引。
3)負載因子
負載因子(Load Factor)是哈希表中元素個數與表的大小的比率。當負載因子過高時,可能需要進行擴容操作,以保持操作的效率。
較小的負載因子可以減少沖突的可能性,較大的負載因子可以提高哈希表的內存利用率。通常情況下負載因子在0.7~0.8是一個比較好的選擇。
3.5.2 哈希表的功能定義
方法 說明
size() 返回哈希表中鍵值對個數
is_empty() 判斷哈希表是否為空
put(key, value) 向哈希表插入鍵值對
remove(key) 從哈希表中根據鍵刪除鍵值對
get(key) 從哈希表中根據鍵獲取值
for_each(func) 遍歷哈希表中的鍵值對
3.5.3 哈希表的實現
class Node:
def init(self, key, value):
self.key = key
self.value = value
self.next = None

class HashTable:

def __init__(self):"""初始化哈希表"""self.__capacity = 8  # 數組長度self.__size = 0  # 鍵值對個數self.__load_factor = 0.7  # 負載因子self.__table = [None] * self.__capacitydef display(self):"""顯示哈希表內容"""for i, node in enumerate(self.__table):print(f"Index {i}: ", end="")current = nodewhile current:print(f"({current.key}, {current.value}) -> ", end="")current = current.nextprint("None")print()def __hash(self, key):"""哈希函數,根據key計算索引"""return hash(key) % self.__capacitydef __grow(self):"""哈希表負載因子超過閾值時進行擴容"""self.__capacity = self.__capacity * 2self.__table, old_table = [None] * self.__capacity, self.__tableself.__size = 0# 將舊哈希表中的元素重新插入到新的哈希表中for node in old_table:current = nodewhile current:self.put(current.key, current.value)current = current.next@property
def size(self):"""獲取哈希表鍵值對個數"""return self.__sizedef is_empty(self):"""判斷哈希表是否為空"""return self.__size == 0def put(self, key, value):"""插入鍵值對,處理哈希沖突"""# 如果負載因子超過閾值則進行擴容if self.__size / self.__capacity > self.__load_factor:self.__grow()index = self.__hash(key)new_node = Node(key, value)# 如果當前位置為空,直接插入if self.__table[index] is None:self.__table[index] = new_nodeelse:# 否則,發生哈希沖突,鏈式存儲current = self.__table[index]while current and current.next:# 如果鍵已經存在,更新值if current.key == key:current.value = valuereturncurrent = current.next# 如果鍵不存在,插入到鏈表尾部current.next = new_nodeself.__size += 1def remove(self, key):"""刪除鍵值對"""index = self.__hash(key)current = self.__table[index]prev = Nonewhile current:if current.key == key:if prev:# 刪除非頭節點prev.next = current.nextelse:# 刪除頭節點self.__table[index] = current.nextself.__size -= 1return Trueprev = currentcurrent = current.nextreturn Falsedef get(self, key):"""訪問鍵值對"""index = self.__hash(key)current = self.__table[index]while current:if current.key == key:return current.valuecurrent = current.nextreturn Nonedef for_each(self, func):"""遍歷哈希表"""for node in self.__table:current = nodewhile current:func(current.key, current.value)current = current.next

3.6 樹
3.6.1 樹的概述
樹(Tree)由一系列具有層次關系的節點(Node)組成。

樹的常見術語:
? 父節點:節點的上層節點。
? 子節點:節點的下層節點。
? 根節點:位于樹的頂端,沒有父節點的節點。
? 葉節點:位于樹的底端,沒有子節點的節點。
? 邊:連接兩個節點的線段。
? 節點的度:節點的子節點數量。
? 節點的層:從根開始定義起,根為第1層,根的子節點為第2層,以此類推。
? 節點的深度:從根節點到該節點所經過的邊的數量,根的深度為0。
? 節點的高度:從距離該節點最遠的葉節點到該節點所經過的邊的數量,所有葉節點的高度為0。
? 樹的深度(高度):從根節點到最遠葉節點所經過的邊的數量。

3.6.2 二叉樹簡介
樹形結構中最具代表性的一種就是二叉樹(Binary Tree)。二叉樹規定,每個節點最多只能有兩個子節點,兩個子節點分別被稱為左子節點和右子節點。以左子節點為根節點的子樹被稱為左子樹,以右子節點為根節點的子樹被稱為右子樹。

3.6.3 二叉樹存儲結構
1)二叉樹的數組存儲

采用數組結構存儲二叉樹,訪問與遍歷速度較快。但不適合存儲數據量過大的樹,且增刪效率較低,而且樹中存在大量None的情況下空間利用率較低,因此不是主流方式。
2)二叉樹的鏈表存儲

3.6.4 常見的二叉樹
1)完全二叉樹
完全二叉樹只有最下面一層的節點未被填滿,且靠左填充。

2)滿二叉樹
滿二叉樹所有層的節點都被完全填滿,滿二叉樹也是一種完全二叉樹。

3)平衡二叉樹
平衡二叉樹中任意節點的左右子樹高度之差不超過1。

4)二叉搜索樹
二叉搜索樹中的每個節點的值,大于其左子樹中的所有節點的值,并且小于右子樹中的所有節點的值。

5)AVL樹
AVL 樹是一種自平衡的二叉搜索樹,插入和刪除時會進行旋轉操作來保證樹的平衡性。
6)紅黑樹
紅黑樹是一種特殊的二叉搜索樹,除了二叉搜索樹的要求外,它還具有以下特性:
? 每個節點或者是黑色,或者是紅色。
? 根節點是黑色。
? 每個葉節點都是黑色。這里葉節點是指為空(None)的節點。
? 紅色節點的兩個子節點必須是黑色的。即從每個葉到根的所有路徑上不能有兩個連續的紅色節點。
? 從任一個節點到其每個葉的所有路徑上包含相同數目的黑色節點。
7)堆
堆(Heap)是一種滿足特定條件的完全二叉樹,主要可分為兩種類型:
? 大頂堆:每個父節點的值都大于等于其子節點的值。根節點為樹中的最大值。
? 小頂堆:每個父節點的值都小于等于其子節點的值。根節點為樹中的最小值。
8)霍夫曼樹
霍夫曼樹又稱最優二叉樹,是一種帶權路徑長度最短的二叉樹,通常用于數據壓縮,它的構建基于字符出現頻率的概率。
9)B樹
B樹是一種自平衡的多路查找樹。雖然它不是嚴格意義上的二叉樹,但與二叉樹的結構類似。經常用于數據庫、文件系統等需要磁盤訪問的應用。
10)B+樹
B+樹是B樹的優化版本。它通過將數據集中存儲在葉子節點并通過鏈表連接來實現高效的范圍查詢,并且非葉子節點僅存儲索引,提高了磁盤利用率。
3.6.5 二叉搜索樹的功能定義
方法 說明
size() 返回樹中節點個數
is_empty() 判斷樹是否為空
search(item) 查找節點是否存在
add(item) 向二叉搜索樹中插入節點
remove(item) 從二叉搜索樹中刪除節點
for_each(func, order) 按指定方式遍歷二叉樹
3.6.6 二叉樹的創建
from collections import deque

class Node:
“”“二叉樹節點”“”

def __init__(self, data):self.data = dataself.left = Noneself.right = None

class BinarySearchTree:
“”“二叉搜索樹”“”

def __init__(self):"""初始化二叉樹"""self.__root = Noneself.__size = 0def print_tree(self):"""打印樹的結構"""# 先得到樹的層數def get_layer(node):"""遞歸計算樹的層數"""if node is None:return 0else:left_depth = get_layer(node.left)right_depth = get_layer(node.right)return max(left_depth, right_depth) + 1layer = get_layer(self.__root)# 層序遍歷并打印queue = deque([(self.__root, 1)])current_level = 1while queue:node, level = queue.popleft()if level > current_level:print()current_level += 1if node:print(f"{node.data:^{20*layer//2**(level-1)}}", end="")else:print(f"{"N":^{20*layer//2**(level-1)}}", end="")if level < layer:if node:queue.append((node.left, level + 1))queue.append((node.right, level + 1))else:queue.append((None, level + 1))queue.append((None, level + 1))print()@property
def size(self):"""返回樹中節點的個數"""return self.__sizedef is_empty(self):"""判斷樹是否為空"""return self.__size == 0

3.6.7 二叉搜索樹的查找操作
查找時先與當前節點比較大小,等于則找到了目標節點,小于則向左子節點查找,大于則向右子節點查找。如果查找到None仍未找到則說明該節點不在樹中。

后續插入與刪除操作也會用到查找,所以此處提供一個__search_pos()方法,返回查找到的節點和其父節點供后續使用。
def search(self, item):
“”“查找節點是否存在”“”
return self.__search_pos(item)[0] is not None

def __search_pos(self, item):"""查找節點,返回(節點,父節點)。如果節點不存在則為None,此時父節點為一個葉節點"""parent = Nonecurrent = self.__rootwhile current:if item == current.data:breakparent = currentcurrent = current.left if item < current.data else current.rightreturn current, parent

3.6.8 二叉搜索樹的插入操作
插入時先執行查找操作,查找時保存當前節點的父節點。如果找到了節點則說明樹中已有此元素,退出。如果找到了None,此時None的父節點為葉節點,應將該元素插入該葉節點的子節點。

def add(self, item):"""插入節點"""node = Node(item)if self.is_empty():self.__root = nodeelse:current, parent = self.__search_pos(item)# 如果節點之前已存在則返回if current:return# 如果節點之前不存在,則插入父節點的左節點或右節點if parent.data > item:parent.left = nodeelse:parent.right = nodeself.__size += 1

3.6.9 二叉搜索樹的刪除操作
需要保證刪除節點后仍然保證二叉搜索樹的性質。刪除操作需要根據目標節點的子節點數量為0、1、2分三種情況。
1)目標節點的子節點數量為0
直接刪除目標節點。

2)目標節點的子節點數量為1
將目標節點替換為其子節點。

3)目標節點的子節點數量為2
使用目標節點的右子樹最小節點、或左子樹最大節點替換目標節點。

4)代碼實現
def remove(self, item):
“”“刪除節點”“”
current, parent = self.__search_pos(item)
if not current:
return

    # 如果刪除的是葉節點(沒有子節點)if not current.left and not current.right:if parent:if parent.left == current:parent.left = Noneelse:parent.right = Noneelse:# 如果沒有父節點,說明是根節點self.__root = None# 如果刪除的節點只有一個子節點elif not current.left or not current.right:child = current.left if current.left else current.rightif parent:if parent.left == current:parent.left = childelse:parent.right = childelse:# 如果沒有父節點,說明是根節點self.__root = child# 如果刪除的節點有兩個子節點else:# 找到中序后繼(右子樹中最小的節點)successor = self.__get_min(current.right)successor_data = successor.data# 刪除中序后繼節點self.remove(successor_data)# 用中序后繼的值替代當前節點current.data = successor_dataself.__size -= 1def __get_min(self, node):"""找到當前子樹的最小節點"""current = nodewhile current.left:current = current.leftreturn current

3.6.10 二叉樹的遍歷
1)深度優先
深度優先搜索(DFS,Depth First Search)盡可能地深入每一個分支,直到不能再深入為止,然后回溯到上一個節點,繼續嘗試其他的分支。

(1)前序遍歷
先訪問當前節點,再訪問節點的左子樹,再訪問節點的右子樹。
def dfs(node):
“”“前序遍歷”“”
if node is None:
return
print(node) # 訪問當前節點
dfs(node.left) # 訪問節點的左子樹
dfs(node.right) # 訪問節點的右子樹
(2)中序遍歷
先訪問節點的左子樹,再訪問當前節點,再訪問節點的右子樹。
二叉搜索樹中序遍歷的結果是有序的。
def dfs(node):
“”“中序遍歷”“”
if node is None:
return
dfs(node.left) # 訪問節點的左子樹
print(node) # 訪問當前節點
dfs(node.right) # 訪問節點的右子樹
(3)后續遍歷
先訪問節點的左子樹,再訪問節點的右子樹,再訪問當前節點。
def dfs(node):
“”“后序遍歷”“”
if node is None:
return
dfs(node.left) # 訪問節點的左子樹
dfs(node.right) # 訪問節點的右子樹
print(node) # 訪問當前節點
2)廣度優先
(1)層序遍歷
廣度優先搜索(BFS,Breadth First Search)從起始節點開始,首先訪問該節點的所有子節點,然后再訪問子節點的子節點,依此類推,逐層訪問節點。

廣度優先搜索一般使用隊列實現,每訪問一個節點,就將該節點的子節點添加進隊列中。
3)代碼實現
def for_each(self, func, order=“inorder”):
“”“遍歷樹,默認中序遍歷”“”
match order:
case “inorder”:
self.__inorder_traversal(func)
case “preorder”:
self.__preorder_traversal(func)
case “postorder”:
self.__postorder_traversal(func)
case “levelorder”:
self.__levelorder_traversal(func)

def __inorder_traversal(self, func):"""深度優先搜索:中序遍歷"""def inorder(node):if node:inorder(node.left)func(node.data)inorder(node.right)inorder(self.__root)def __preorder_traversal(self, func):"""深度優先搜索:前序遍歷"""def preorder(node):if node:func(node.data)preorder(node.left)preorder(node.right)preorder(self.__root)def __postorder_traversal(self, func):"""深度優先搜索:后序遍歷"""def postorder(node):if node:postorder(node.left)postorder(node.right)func(node.data)postorder(self.__root)def __levelorder_traversal(self, func):"""廣度優先搜索:層序遍歷"""queue = deque()queue.append(self.__root)while queue:node = queue.popleft()func(node.data)if node.left:queue.append(node.left)if node.right:queue.append(node.right)

3.6.11 完整代碼
from collections import deque

class Node:
“”“二叉樹節點”“”

def __init__(self, data):self.data = dataself.left = Noneself.right = None

class BinarySearchTree:
“”“二叉搜索樹”“”

def __init__(self):"""初始化二叉樹"""self.__root = Noneself.__size = 0def print_tree(self):"""打印樹的結構"""# 先得到樹的層數def get_layer(node):"""遞歸計算樹的層數"""if node is None:return 0else:left_depth = get_layer(node.left)right_depth = get_layer(node.right)return max(left_depth, right_depth) + 1layer = get_layer(self.__root)# 層序遍歷并打印queue = deque([(self.__root, 1)])current_level = 1while queue:node, level = queue.popleft()if level > current_level:print()current_level += 1if node:print(f"{node.data:^{20*layer//2**(level-1)}}", end="")else:print(f"{"N":^{20*layer//2**(level-1)}}", end="")if level < layer:if node:queue.append((node.left, level + 1))queue.append((node.right, level + 1))else:queue.append((None, level + 1))queue.append((None, level + 1))print()@property
def size(self):"""返回樹中節點的個數"""return self.__sizedef is_empty(self):"""判斷樹是否為空"""return self.__size == 0def search(self, item):"""查找節點是否存在"""return self.__search_pos(item)[0] is not Nonedef __search_pos(self, item):"""查找節點,返回(節點,父節點)。如果節點不存在則為None,此時父節點為一個葉節點"""parent = Nonecurrent = self.__rootwhile current:if item == current.data:breakparent = currentcurrent = current.left if item < current.data else current.rightreturn current, parentdef add(self, item):"""插入節點"""node = Node(item)if self.is_empty():self.__root = nodeelse:current, parent = self.__search_pos(item)# 如果節點之前已存在則返回if current:return# 如果節點之前不存在,則插入父節點的左節點或右節點if parent.data > item:parent.left = nodeelse:parent.right = nodeself.__size += 1def remove(self, item):"""刪除節點"""current, parent = self.__search_pos(item)if not current:return# 如果刪除的是葉節點(沒有子節點)if not current.left and not current.right:if parent:if parent.left == current:parent.left = Noneelse:parent.right = Noneelse:# 如果沒有父節點,說明是根節點self.__root = None# 如果刪除的節點只有一個子節點elif not current.left or not current.right:child = current.left if current.left else current.rightif parent:if parent.left == current:parent.left = childelse:parent.right = childelse:# 如果沒有父節點,說明是根節點self.__root = child# 如果刪除的節點有兩個子節點else:# 找到中序后繼(右子樹中最小的節點)successor = self.__get_min(current.right)successor_data = successor.data# 刪除中序后繼節點self.remove(successor_data)# 用中序后繼的值替代當前節點current.data = successor_dataself.__size -= 1def __get_min(self, node):"""找到當前子樹的最小節點"""current = nodewhile current.left:current = current.leftreturn currentdef for_each(self, func, order="inorder"):"""遍歷樹,默認中序遍歷"""match order:case "inorder":self.__inorder_traversal(func)case "preorder":self.__preorder_traversal(func)case "postorder":self.__postorder_traversal(func)case "levelorder":self.__levelorder_traversal(func)def __inorder_traversal(self, func):"""深度優先搜索:中序遍歷"""def inorder(node):if node:inorder(node.left)func(node.data)inorder(node.right)inorder(self.__root)def __preorder_traversal(self, func):"""深度優先搜索:前序遍歷"""def preorder(node):if node:func(node.data)preorder(node.left)preorder(node.right)preorder(self.__root)def __postorder_traversal(self, func):"""深度優先搜索:后序遍歷"""def postorder(node):if node:postorder(node.left)postorder(node.right)func(node.data)postorder(self.__root)def __levelorder_traversal(self, func):"""廣度優先搜索:層序遍歷"""queue = deque()queue.append(self.__root)while queue:node = queue.popleft()func(node.data)if node.left:queue.append(node.left)if node.right:queue.append(node.right)

3.7 圖
3.7.1 圖的概述
前面我們學習了線性結構和樹,線性結構局限于只有一個直接前驅和一個直接后繼的關系,樹也只能有一個直接前驅,也就是父節點,當我們需要表示多對多的關系時,就需要用到圖了,圖是比樹更普遍的結構,可以認為樹是一種特殊的圖。圖由節點和邊組成。

圖的常見術語:
? 節點:也稱為頂點,是圖的基礎部分。
? 邊:連接兩個節點,也是圖的基礎部分。可以是單向的,也可以是雙向的。
? 權重:邊可以添加“權重”變量。
? 鄰接:兩節點之間存在邊,則稱這兩個節點鄰接。
? 度:一個節點的邊的數量。入度為指向該節點的邊的數量,出度為該節點指向其他節點的邊的數量。
? 路徑:從一節點到另一節點所經過的邊的序列。
? 環:首尾節點相同的路徑。
3.7.2 圖的分類
1)有向圖和無向圖
? 有向圖:邊是單向的。
? 無向圖:邊是雙向的。

2)連通圖和非連通圖
? 連通圖:從某個節點出發,可以到達其余任意節點。
? 非連通圖:從某個節點出發,有節點不可達。

3.7.3 圖的常用表示法
1)鄰接矩陣
鄰接矩陣用一個n×n的矩陣來表示有n個節點之間的關系,矩陣的每一行(列)代表一個節點,矩陣m行n列的值代表是否存在由m指向n的邊。鄰接矩陣適合存儲稠密圖。

2)鄰接表
鄰接表存儲n個鏈表、列表或其他容器,每個容器存儲該節點的所有鄰接節點。鄰接表適合存儲稀疏圖,空間效率高,尤其在處理邊遠少于節點的圖時表現優越,但在進行邊查找時不如鄰接矩陣高效。

3.7.4 圖的遍歷
1)廣度優先搜索
廣度優先搜索(BFS,Breadth First Search)從起始節點開始,首先訪問該節點的所有鄰接節點,然后再訪問鄰接節點的鄰接節點,依此類推,逐層訪問節點。
? 從圖的起始節點開始,首先訪問該節點,并標記為已訪問。
? 然后依次訪問所有未被訪問的鄰接節點,并將它們加入到隊列中。
? 當隊列中的節點被訪問時,繼續訪問它的鄰接節點,并將新的節點加入隊列。
? 直到隊列為空,表示所有節點都已被訪問。
2)深度優先搜索
深度優先搜索(DFS,Depth First Search)盡可能地深入到圖的每一個分支,直到不能再深入為止,然后回溯到上一個節點,繼續嘗試其他的分支。
? 從圖的一個起始節點開始,訪問這個節點并標記為已訪問。
? 對于每個未訪問的鄰接節點,遞歸地執行 DFS,直到沒有未訪問的鄰接節點。
? 當回溯到一個節點時,繼續訪問它的其他鄰接節點。
第 4 章 常用算法
4.1 查找算法
4.1.1 二分查找
1)算法原理
二分查找又稱折半查找,適用于有序列表。其利用數據的有序性,每輪縮小一半搜索范圍,直至找到目標元素或搜索區間為空為止。

2)代碼實現
def binary_search(arr, target):
left, right = 0, len(arr) - 1

while left <= right:mid = left + (right - left) // 2if arr[mid] == target:return mid  # 找到目標值,返回索引elif arr[mid] < target:left = mid + 1  # 目標值在右半部分else:right = mid - 1  # 目標值在左半部分return -1  # 未找到目標值

3)復雜度分析
(1)時間復雜度
在循環中,區間每輪縮小一半,因此時間復雜度為O(logn)。
(2)空間復雜度
使用常數大小的額外空間,空間復雜度為O(1)。
4.1.2 查找多數元素
力扣169題https://leetcode.cn/problems/majority-element/description/
返回數組中數量超過半數的元素,要求時間復雜度O(n)、空間復雜度O(1)。
示例:
? 輸入:nums = [2,2,1,1,1,2,2]
? 輸出:2
1)思路分析
為了嚴格符合復雜度要求,可以使用多數投票算法,多數投票算法也叫摩爾投票算法。摩爾投票算法的核心思想是對立性和抵消,它基于這樣一個事實:如果一個元素在數組中出現的次數超過數組長度的一半,那么在不斷消除不同元素對的過程中,這個多數元素最終會留下來。
具體來說,算法維護兩個變量:一個是候選元素 candidate,另一個是該候選元素的計數 count。在遍歷數組的過程中,遇到與候選元素相同的元素時,計數加 1;遇到不同的元素時,計數減 1。當計數減為 0 時,說明當前候選元素被抵消完,需要更換候選元素為當前遍歷到的元素,并將計數重置為 1。
算法步驟
? 初始化:
? 選擇數組的第一個元素作為初始候選元素 candidate。
? 將計數 count 初始化為 1。
? 遍歷數組:
? 從數組的第二個元素開始遍歷。
? 若當前元素與候選元素相同,count 加 1。
? 若當前元素與候選元素不同,count 減 1。
? 當 count 變為 0 時,將當前元素設為新的候選元素,并將 count 重置為 1。
? 返回結果:
? 遍歷結束后,candidate 即為多數元素。
代碼實現
def majorityElement(nums):
# 初始化候選元素為數組的第一個元素
candidate = nums[0]
# 初始化候選元素的票數為 1
count = 1
# 從數組的第二個元素開始遍歷
for num in nums[1:]:
if num == candidate:
# 如果當前元素與候選元素相同,票數加 1
count += 1
else:
# 如果當前元素與候選元素不同,票數減 1
count -= 1
if count == 0:
# 當票數為 0 時,更新候選元素為當前元素,并將票數重置為 1
candidate = num
count = 1
return candidate

測試示例

nums = [2, 2, 1, 1, 1, 2, 2]
print(majorityElement(nums))

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/76914.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/76914.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/76914.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

粘性定位(position:sticky)——微信小程序學習筆記

1. 簡介 CSS 中的粘性定位&#xff08;Sticky positioning&#xff09;是一種特殊的定位方式&#xff0c;它可以使元素在滾動時保持在視窗的特定位置&#xff0c;類似于相對定位&#xff08;relative&#xff09;&#xff0c;但當頁面滾動到元素的位置時&#xff0c;它會表現得…

通過使用 include 語句加載并執行一個CMake腳本來引入第三方庫

通過使用 include 語句加載并執行一個CMake腳本來引入第三方庫 當項目中使用到第三方庫時&#xff0c;可以通過使用 include 語句來加載并執行一個CMake腳本&#xff0c;在引入的CMake腳本中進行第三方庫的下載、構建和庫查找路徑的設置等操作&#xff0c;以這種方式簡化項目中…

DNS正反向解析復習,DNS主從服務,轉發服務及DNS和nginx聯合案例

正向解析 1、配置主機名 [rootlocalhost ~]# dnf install bash-completion -y #一個按tap鍵補全的軟件 [rootlocalhost ~]# hostnamectl hostname dns #改主機名為dns [rootlocalhost ~]# exit ssh root你的IP地址 要重啟才會生效2、安裝bind [rootdns ~]# dnf install b…

網絡安全·第一天·IP協議安全分析

本篇博客講述的是網絡安全中一些協議缺陷以及相應的理論知識&#xff0c;本博主盡可能講明白其中的一些原理以及對應的防衛措施。 學習考研408的同學也能進來看看&#xff0c;或許對考研有些許幫助&#xff08;按照考研現在的趨勢&#xff0c;年年都有新題目&#xff0c;本文當…

【詳解】Nginx配置WebSocket

目錄 Nginx配置WebSocket 簡介 準備工作 檢查 Nginx 版本 配置 Nginx 支持 WebSocket 修改 Nginx 配置文件 解釋配置項 測試配置 測試 WebSocket 連接 WebSocket 服務端 (Node.js) WebSocket 客戶端 (HTML JavaScript) 運行測試 Nginx 配置文件示例 解釋 測試配…

《軌道力學講義》——第八講:行星際軌道設計

第八講&#xff1a;行星際軌道設計 引言 行星際軌道設計是探索太陽系的核心技術&#xff0c;它涉及如何規劃和優化航天器從一個天體到另一個天體的飛行路徑。隨著人類探索太陽系的雄心不斷擴大&#xff0c;從最初的月球探測到火星探測&#xff0c;再到更遙遠的外太陽系探測&a…

操作系統學習筆記——[特殊字符]超詳細 | 如何喚醒被阻塞的 socket 線程?線程阻塞原理、線程池、fork/vfork徹底講明白!

&#x1f4a1;超詳細 | 如何喚醒被阻塞的 socket 線程&#xff1f;線程阻塞原理、線程池、fork/vfork徹底講明白&#xff01; 一、什么是阻塞&#xff1f;為什么線程會阻塞&#xff1f;二、socket線程被阻塞的典型場景&#x1f9e0; 解法思路&#xff1a; 三、線程的幾種阻塞狀…

第十六屆藍橋杯大賽軟件賽省賽 Python 大學 B 組 滿分題解

題面鏈接Htlang/2025lqb_python_b 個人覺得今年這套題整體比往年要簡單許多&#xff0c;但是G題想簡單了出大問題&#xff0c;預估50101015120860&#xff0c;道阻且長&#xff0c;再接再厲 代碼僅供學習參考&#xff0c;滿分為賽后洛谷中的測評&#xff0c;藍橋杯官方測評待…

若依代碼生成器原理velocity模板引擎(自用)

1.源碼分析 代碼生成器:導入表結構(預覽、編輯、刪除、同步)、生成前后端代碼 代碼生成器表結構說明&#xff1a; 若依提供了兩張核心表來存儲導入的業務表信息&#xff1a; gen_table&#xff1a;存儲業務表的基本信息 &#xff0c;它對應于配置代碼基本信息和生成信息的頁…

如何制定有效的風險應對計劃

制定有效的風險應對計劃的核心在于&#xff1a; 識別潛在風險、評估風險的影響與概率、選擇合適的應對策略、建立動態監控和反饋機制。 其中&#xff0c;識別潛在風險是最為關鍵的第一步。只有準確識別出可能的風險&#xff0c;才能在后續的評估、應對、監控等環節中做到有的放…

A2A協議實現詳解及示例

A2A協議概述 A2A (Agent2Agent) 是Google推出的一個開放協議&#xff0c;旨在使AI智能體能夠安全地相互通信和協作。該協議打破了孤立智能體系統之間的壁壘&#xff0c;實現了復雜的跨應用自動化。[1] A2A協議的核心目標是讓不同的AI代理能夠相互通信、安全地交換信息以及在各…

【中級軟件設計師】前趨圖 (附軟考真題)

【中級軟件設計師】前趨圖 (附軟考真題) 目錄 【中級軟件設計師】前趨圖 (附軟考真題)一、歷年真題三、真題的答案與解析答案解析 復習技巧&#xff1a; 若已掌握【前趨圖】相關知識&#xff0c;可直接刷以下真題&#xff1b; 若對知識一知半解&#xff0c;建議略讀題目&#x…

調節磁盤和CPU的矛盾——InnoDB的Buffer Pool

緩存的重要性 無論是用于存儲用戶數據的索引【聚簇索引、二級索引】還是各種系統數據&#xff0c;都是以頁的形式存放在表空間中【對一個/幾個實際文件的抽象&#xff0c;存儲在磁盤上】如果需要訪問某頁的數據&#xff0c;就會把完整的頁數據加載到內存中【即使只訪問頁中的一…

springboot和springcloud的區別

1. ?目的與功能? ?1)Spring Boot?: 主要用于快速構建獨立的、生產級的 Spring 應用程序。它通過自動配置和嵌入式服務器等特性,簡化了微服務的開發、啟動和部署,使開發者能夠專注于業務邏輯而非繁瑣的配置。?Spring Boot是一個快速開發的框架,旨在簡化Java應用程序的開…

耘想WinNAS:以聊天交互重構NAS生態,開啟AI時代的存儲革命

一、傳統NAS的交互困境與范式瓶頸 在傳統NAS&#xff08;網絡附加存儲&#xff09;領域&#xff0c;用戶需通過復雜的圖形界面或命令行工具完成文件管理、權限配置、數據檢索等操作&#xff0c;學習成本高且效率低下。例如&#xff0c;用戶若需搜索特定文件&#xff0c;需手動…

在斷網的時候,websocket 一直在CLOSING 狀態

現象 websocket 先連接成功&#xff0c;然后斷網。 由于維護了一套心跳機制&#xff0c;前端發起心跳&#xff0c;如果一段時間內沒有收到服務端返回的心跳。則表示連接斷開。 用心跳的方式處理斷網的兜底情況。 然而&#xff0c;此時網絡是斷開的&#xff0c;在代碼中直接調…

基于AWS的大模型調用場景:10大成本優化實戰方案

大模型訓練與推理是AI領域的計算密集型場景&#xff0c;如何在AWS上實現高性能與低成本的雙重目標&#xff1f;本文從實例選型、彈性伸縮、存儲優化等角度&#xff0c;分享10個經過驗證的AWS成本優化策略&#xff0c;幫助企業節省30%以上成本。 一、大模型場景的成本痛點分析 計…

【網絡原理】TCP/IP協議五層模型

目錄 一. 協議的分層 二. OSI七層網絡協議 三. TCP/IP五層網絡協議 四. 網絡設備所在分層 五. 封裝 六. 分用 七. 傳輸中的封裝和分用 八. 數據單位術語 一. 協議的分層 常見的分層為兩種OSI七層模型和TCP/IP五層模型 為什么要協議分層&#xff1f; 在網絡通信中&…

科技快訊 | 阿里云百煉MCP服務上線;英偉達官宣:CUDA 工具鏈將全面原生支持 Python

李飛飛團隊最新AI報告&#xff1a;中美模型性能差距近乎持平 4月8日&#xff0c;斯坦福大學以人為本人工智能研究所發布《2025年人工智能指數報告》。報告顯示&#xff0c;2023年AI性能顯著提升&#xff0c;AI應用加速&#xff0c;投資增長&#xff0c;中美AI模型差距縮小。報告…

貓咪如廁檢測與分類識別系統系列【三】融合yolov11目標檢測

? 前情提要 家里養了三只貓咪&#xff0c;其中一只布偶貓經常出入廁所。但因為平時忙于學業&#xff0c;沒法時刻關注牠的行為。我知道貓咪的如廁頻率和時長與健康狀況密切相關&#xff0c;頻繁如廁可能是泌尿問題&#xff0c;停留過久也可能是便秘或不適。為了更科學地了解牠…