This commit is contained in:
krahets
2025-09-20 20:08:06 +08:00
parent ae2e2535f4
commit 1fca6ca899
9 changed files with 30 additions and 10 deletions
@@ -51,3 +51,9 @@ comments: true
假设取 $n = 8$ ,你可能会发现每条曲线的值与函数对应不上。这是因为每条曲线都包含一个常数项,用于将取值范围压缩到一个视觉舒适的范围内。 假设取 $n = 8$ ,你可能会发现每条曲线的值与函数对应不上。这是因为每条曲线都包含一个常数项,用于将取值范围压缩到一个视觉舒适的范围内。
在实际中,因为我们通常不知道每个方法的“常数项”复杂度是多少,所以一般无法仅凭复杂度来选择 $n = 8$ 之下的最优解法。但对于 $n = 8^5$ 就很好选了,这时增长趋势已经占主导了。 在实际中,因为我们通常不知道每个方法的“常数项”复杂度是多少,所以一般无法仅凭复杂度来选择 $n = 8$ 之下的最优解法。但对于 $n = 8^5$ 就很好选了,这时增长趋势已经占主导了。
**Q** 是否存在根据实际使用场景,选择牺牲时间(或空间)来设计算法的情况?
在实际应用中,大部分情况会选择牺牲空间换时间。例如数据库索引,我们通常选择建立 B+ 树或哈希索引,占用大量内存空间,以换取 $O(\log n)$ 甚至 $O(1)$ 的高效查询。
在空间资源宝贵的场景,也会选择牺牲时间换空间。例如在嵌入式开发中,设备内存很宝贵,工程师可能会放弃使用哈希表,选择使用数组顺序查找,以节省内存占用,代价是查找变慢。
+3 -3
View File
@@ -571,7 +571,7 @@ comments: true
输入一个 `key` ,哈希函数的计算过程分为以下两步。 输入一个 `key` ,哈希函数的计算过程分为以下两步。
1. 通过某种哈希算法 `hash()` 计算得到哈希值。 1. 通过某种哈希算法 `hash()` 计算得到哈希值。
2. 将哈希值对桶数量(数组长度)`capacity` 取模,从而获取该 `key` 对应的数组索引 `index` 。 2. 将哈希值对桶数量(数组长度)`capacity` 取模,从而获取该 `key` 对应的桶(数组索引`index` 。
```shell ```shell
index = hash(key) % capacity index = hash(key) % capacity
@@ -610,7 +610,7 @@ index = hash(key) % capacity
index = key % 100 index = key % 100
return index return index
def get(self, key: int) -> str: def get(self, key: int) -> str | None:
"""查询操作""" """查询操作"""
index: int = self.hash_func(key) index: int = self.hash_func(key)
pair: Pair = self.buckets[index] pair: Pair = self.buckets[index]
@@ -619,7 +619,7 @@ index = hash(key) % capacity
return pair.val return pair.val
def put(self, key: int, val: str): def put(self, key: int, val: str):
"""添加操作""" """添加和更新操作"""
pair = Pair(key, val) pair = Pair(key, val)
index: int = self.hash_func(key) index: int = self.hash_func(key)
self.buckets[index] = pair self.buckets[index] = pair
+4
View File
@@ -49,3 +49,7 @@ comments: true
**Q**:为什么哈希表扩容能够缓解哈希冲突? **Q**:为什么哈希表扩容能够缓解哈希冲突?
哈希函数的最后一步往往是对数组长度 $n$ 取模(取余),让输出值落在数组索引范围内;在扩容后,数组长度 $n$ 发生变化,而 `key` 对应的索引也可能发生变化。原先落在同一个桶的多个 `key` ,在扩容后可能会被分配到多个桶中,从而实现哈希冲突的缓解。 哈希函数的最后一步往往是对数组长度 $n$ 取模(取余),让输出值落在数组索引范围内;在扩容后,数组长度 $n$ 发生变化,而 `key` 对应的索引也可能发生变化。原先落在同一个桶的多个 `key` ,在扩容后可能会被分配到多个桶中,从而实现哈希冲突的缓解。
**Q**:如果为了高效的存取,那么直接使用数组不就好了吗?
当数据的 `key` 是连续的小范围整数时,直接用数组即可,简单高效。但当 `key` 是其他类型(例如字符串)时,就需要借助哈希函数将 `key` 映射为数组索引,再通过桶数组存储元素,这样的结构就是哈希表。
+1 -1
View File
@@ -6,7 +6,7 @@ comments: true
<u>栈(stack</u>是一种遵循先入后出逻辑的线性数据结构。 <u>栈(stack</u>是一种遵循先入后出逻辑的线性数据结构。
我们可以将栈类比为桌面上的一摞盘子,如果想取出底部的盘子,则需要先将上面的盘子依次移走。我们将盘子替换为各种类型的元素(如整数、字符、对象等),就得到了栈这种数据结构。 我们可以将栈类比为桌面上的一摞盘子,规定每次只能移动一个盘子,那么想取出底部的盘子,则需要先将上面的盘子依次移走。我们将盘子替换为各种类型的元素(如整数、字符、对象等),就得到了栈这种数据结构。
如图 5-1 所示,我们把堆叠元素的顶部称为“栈顶”,底部称为“栈底”。将把元素添加到栈顶的操作叫作“入栈”,删除栈顶元素的操作叫作“出栈”。 如图 5-1 所示,我们把堆叠元素的顶部称为“栈顶”,底部称为“栈底”。将把元素添加到栈顶的操作叫作“入栈”,删除栈顶元素的操作叫作“出栈”。
@@ -12,7 +12,7 @@ Common data structures include arrays, linked lists, stacks, queues, hash tables
As shown in Figure 3-1, logical structures can be divided into two major categories: "linear" and "non-linear". Linear structures are more intuitive, indicating data is arranged linearly in logical relationships; non-linear structures, conversely, are arranged non-linearly. As shown in Figure 3-1, logical structures can be divided into two major categories: "linear" and "non-linear". Linear structures are more intuitive, indicating data is arranged linearly in logical relationships; non-linear structures, conversely, are arranged non-linearly.
- **Linear data structures**: Arrays, Linked Lists, Stacks, Queues, Hash Tables. - **Linear data structures**: Arrays, Linked Lists, Stacks, Queues, Hash Tables, where elements have a one-to-one sequential relationship.
- **Non-linear data structures**: Trees, Heaps, Graphs, Hash Tables. - **Non-linear data structures**: Trees, Heaps, Graphs, Hash Tables.
Non-linear data structures can be further divided into tree structures and network structures. Non-linear data structures can be further divided into tree structures and network structures.
@@ -38,7 +38,7 @@ Non-linear data structures can be further divided into tree structures and netwo
It's worth noting that comparing memory to an Excel spreadsheet is a simplified analogy. The actual working mechanism of memory is more complex, involving concepts like address space, memory management, cache mechanisms, virtual memory, and physical memory. It's worth noting that comparing memory to an Excel spreadsheet is a simplified analogy. The actual working mechanism of memory is more complex, involving concepts like address space, memory management, cache mechanisms, virtual memory, and physical memory.
Memory is a shared resource for all programs. When a block of memory is occupied by one program, it cannot be simultaneously used by other programs. **Therefore, considering memory resources is crucial in designing data structures and algorithms**. For instance, the algorithm's peak memory usage should not exceed the remaining free memory of the system; if there is a lack of contiguous memory blocks, then the data structure chosen must be able to be stored in non-contiguous memory blocks. Memory is a shared resource for all programs. When a block of memory is occupied by one program, it cannot be simultaneously used by other programs. **Therefore, memory resources are an important consideration in the design of data structures and algorithms**. For instance, the algorithm's peak memory usage should not exceed the remaining free memory of the system; if there is a lack of contiguous memory blocks, then the data structure chosen must be able to be stored in non-contiguous memory blocks.
As illustrated in Figure 3-3, **the physical structure reflects the way data is stored in computer memory** and it can be divided into contiguous space storage (arrays) and non-contiguous space storage (linked lists). The two types of physical structures exhibit complementary characteristics in terms of time efficiency and space efficiency. As illustrated in Figure 3-3, **the physical structure reflects the way data is stored in computer memory** and it can be divided into contiguous space storage (arrays) and non-contiguous space storage (linked lists). The two types of physical structures exhibit complementary characteristics in terms of time efficiency and space efficiency.
@@ -51,3 +51,9 @@ comments: true
假設取 $n = 8$ ,你可能會發現每條曲線的值與函式對應不上。這是因為每條曲線都包含一個常數項,用於將取值範圍壓縮到一個視覺舒適的範圍內。 假設取 $n = 8$ ,你可能會發現每條曲線的值與函式對應不上。這是因為每條曲線都包含一個常數項,用於將取值範圍壓縮到一個視覺舒適的範圍內。
在實際中,因為我們通常不知道每個方法的“常數項”複雜度是多少,所以一般無法僅憑複雜度來選擇 $n = 8$ 之下的最優解法。但對於 $n = 8^5$ 就很好選了,這時增長趨勢已經佔主導了。 在實際中,因為我們通常不知道每個方法的“常數項”複雜度是多少,所以一般無法僅憑複雜度來選擇 $n = 8$ 之下的最優解法。但對於 $n = 8^5$ 就很好選了,這時增長趨勢已經佔主導了。
**Q** 是否存在根據實際使用場景,選擇犧牲時間(或空間)來設計演算法的情況?
在實際應用中,大部分情況會選擇犧牲空間換時間。例如資料庫索引,我們通常選擇建立 B+ 樹或雜湊索引,佔用大量記憶體空間,以換取 $O(\log n)$ 甚至 $O(1)$ 的高效查詢。
在空間資源寶貴的場景,也會選擇犧牲時間換空間。例如在嵌入式開發中,裝置記憶體很寶貴,工程師可能會放棄使用雜湊表,選擇使用陣列順序查詢,以節省記憶體佔用,代價是查詢變慢。
+3 -3
View File
@@ -571,7 +571,7 @@ comments: true
輸入一個 `key` ,雜湊函式的計算過程分為以下兩步。 輸入一個 `key` ,雜湊函式的計算過程分為以下兩步。
1. 透過某種雜湊演算法 `hash()` 計算得到雜湊值。 1. 透過某種雜湊演算法 `hash()` 計算得到雜湊值。
2. 將雜湊值對桶數量(陣列長度)`capacity` 取模,從而獲取該 `key` 對應的陣列索引 `index` 。 2. 將雜湊值對桶數量(陣列長度)`capacity` 取模,從而獲取該 `key` 對應的桶(陣列索引`index` 。
```shell ```shell
index = hash(key) % capacity index = hash(key) % capacity
@@ -610,7 +610,7 @@ index = hash(key) % capacity
index = key % 100 index = key % 100
return index return index
def get(self, key: int) -> str: def get(self, key: int) -> str | None:
"""查詢操作""" """查詢操作"""
index: int = self.hash_func(key) index: int = self.hash_func(key)
pair: Pair = self.buckets[index] pair: Pair = self.buckets[index]
@@ -619,7 +619,7 @@ index = hash(key) % capacity
return pair.val return pair.val
def put(self, key: int, val: str): def put(self, key: int, val: str):
"""新增操作""" """新增和更新操作"""
pair = Pair(key, val) pair = Pair(key, val)
index: int = self.hash_func(key) index: int = self.hash_func(key)
self.buckets[index] = pair self.buckets[index] = pair
+4
View File
@@ -49,3 +49,7 @@ comments: true
**Q**:為什麼雜湊表擴容能夠緩解雜湊衝突? **Q**:為什麼雜湊表擴容能夠緩解雜湊衝突?
雜湊函式的最後一步往往是對陣列長度 $n$ 取模(取餘),讓輸出值落在陣列索引範圍內;在擴容後,陣列長度 $n$ 發生變化,而 `key` 對應的索引也可能發生變化。原先落在同一個桶的多個 `key` ,在擴容後可能會被分配到多個桶中,從而實現雜湊衝突的緩解。 雜湊函式的最後一步往往是對陣列長度 $n$ 取模(取餘),讓輸出值落在陣列索引範圍內;在擴容後,陣列長度 $n$ 發生變化,而 `key` 對應的索引也可能發生變化。原先落在同一個桶的多個 `key` ,在擴容後可能會被分配到多個桶中,從而實現雜湊衝突的緩解。
**Q**:如果為了高效的存取,那麼直接使用陣列不就好了嗎?
當資料的 `key` 是連續的小範圍整數時,直接用陣列即可,簡單高效。但當 `key` 是其他型別(例如字串)時,就需要藉助雜湊函式將 `key` 對映為陣列索引,再透過桶陣列儲存元素,這樣的結構就是雜湊表。
@@ -6,7 +6,7 @@ comments: true
<u>堆疊(stack</u>是一種遵循先入後出邏輯的線性資料結構。 <u>堆疊(stack</u>是一種遵循先入後出邏輯的線性資料結構。
我們可以將堆疊類比為桌面上的一疊盤子,如果想取出底部的盤子,則需要先將上面的盤子依次移走。我們將盤子替換為各種型別的元素(如整數、字元、物件等),就得到了堆疊這種資料結構。 我們可以將堆疊類比為桌面上的一疊盤子,規定每次只能移動一個盤子,那麼想取出底部的盤子,則需要先將上面的盤子依次移走。我們將盤子替換為各種型別的元素(如整數、字元、物件等),就得到了堆疊這種資料結構。
如圖 5-1 所示,我們把堆積疊元素的頂部稱為“堆疊頂”,底部稱為“堆疊底”。將把元素新增到堆疊頂的操作叫作“入堆疊”,刪除堆疊頂元素的操作叫作“出堆疊”。 如圖 5-1 所示,我們把堆積疊元素的頂部稱為“堆疊頂”,底部稱為“堆疊底”。將把元素新增到堆疊頂的操作叫作“入堆疊”,刪除堆疊頂元素的操作叫作“出堆疊”。