This commit is contained in:
krahets
2024-04-16 04:01:59 +08:00
parent 1bc9502c19
commit cdd8923e98
23 changed files with 1113 additions and 107 deletions

View File

@ -24,15 +24,15 @@ comments: true
**第一步:思考每轮的决策,定义状态,从而得到 $dp$ 表** **第一步:思考每轮的决策,定义状态,从而得到 $dp$ 表**
对于每个物品来说,不放入背包,背包容量不变;放入背包,背包容量减小。由此可得状态定义:当前物品编号 $i$ 和剩余背包容量 $c$ ,记为 $[i, c]$ 。 对于每个物品来说,不放入背包,背包容量不变;放入背包,背包容量减小。由此可得状态定义:当前物品编号 $i$ 和背包容量 $c$ ,记为 $[i, c]$ 。
状态 $[i, c]$ 对应的子问题为:**前 $i$ 个物品在剩余容量为 $c$ 的背包中的最大价值**,记为 $dp[i, c]$ 。 状态 $[i, c]$ 对应的子问题为:**前 $i$ 个物品在容量为 $c$ 的背包中的最大价值**,记为 $dp[i, c]$ 。
待求解的是 $dp[n, cap]$ ,因此需要一个尺寸为 $(n+1) \times (cap+1)$ 的二维 $dp$ 表。 待求解的是 $dp[n, cap]$ ,因此需要一个尺寸为 $(n+1) \times (cap+1)$ 的二维 $dp$ 表。
**第二步:找出最优子结构,进而推导出状态转移方程** **第二步:找出最优子结构,进而推导出状态转移方程**
当我们做出物品 $i$ 的决策后,剩余的是前 $i-1$ 个物品决策,可分为以下两种情况。 当我们做出物品 $i$ 的决策后,剩余的是前 $i-1$ 个物品决策的子问题,可分为以下两种情况。
- **不放入物品 $i$** :背包容量不变,状态变化为 $[i-1, c]$ 。 - **不放入物品 $i$** :背包容量不变,状态变化为 $[i-1, c]$ 。
- **放入物品 $i$** :背包容量减少 $wgt[i-1]$ ,价值增加 $val[i-1]$ ,状态变化为 $[i-1, c-wgt[i-1]]$ 。 - **放入物品 $i$** :背包容量减少 $wgt[i-1]$ ,价值增加 $val[i-1]$ ,状态变化为 $[i-1, c-wgt[i-1]]$ 。
@ -47,7 +47,7 @@ $$
**第三步:确定边界条件和状态转移顺序** **第三步:确定边界条件和状态转移顺序**
当无物品或无剩余背包容量时最大价值为 $0$ ,即首列 $dp[i, 0]$ 和首行 $dp[0, c]$ 都等于 $0$ 。 当无物品或背包容量为 $0$ 时最大价值为 $0$ ,即首列 $dp[i, 0]$ 和首行 $dp[0, c]$ 都等于 $0$ 。
当前状态 $[i, c]$ 从上方的状态 $[i-1, c]$ 和左上方的状态 $[i-1, c-wgt[i-1]]$ 转移而来,因此通过两层循环正序遍历整个 $dp$ 表即可。 当前状态 $[i, c]$ 从上方的状态 $[i-1, c]$ 和左上方的状态 $[i-1, c-wgt[i-1]]$ 转移而来,因此通过两层循环正序遍历整个 $dp$ 表即可。

View File

@ -15,7 +15,7 @@ comments: true
**背包问题** **背包问题**
- 背包问题是最典型的动态规划问题之一,具有 0-1 背包、完全背包、多重背包等变种。 - 背包问题是最典型的动态规划问题之一,具有 0-1 背包、完全背包、多重背包等变种。
- 0-1 背包的状态定义为前 $i$ 个物品在剩余容量为 $c$ 的背包中的最大价值。根据不放入背包和放入背包两种决策,可得到最优子结构,并构建出状态转移方程。在空间优化中,由于每个状态依赖正上方和左上方的状态,因此需要倒序遍历列表,避免左上方状态被覆盖。 - 0-1 背包的状态定义为前 $i$ 个物品在容量为 $c$ 的背包中的最大价值。根据不放入背包和放入背包两种决策,可得到最优子结构,并构建出状态转移方程。在空间优化中,由于每个状态依赖正上方和左上方的状态,因此需要倒序遍历列表,避免左上方状态被覆盖。
- 完全背包问题的每种物品的选取数量无限制,因此选择放入物品的状态转移与 0-1 背包问题不同。由于状态依赖正上方和正左方的状态,因此在空间优化中应当正序遍历。 - 完全背包问题的每种物品的选取数量无限制,因此选择放入物品的状态转移与 0-1 背包问题不同。由于状态依赖正上方和正左方的状态,因此在空间优化中应当正序遍历。
- 零钱兑换问题是完全背包问题的一个变种。它从求“最大”价值变为求“最小”硬币数量,因此状态转移方程中的 $\max()$ 应改为 $\min()$ 。从追求“不超过”背包容量到追求“恰好”凑出目标金额,因此使用 $amt + 1$ 来表示“无法凑出目标金额”的无效解。 - 零钱兑换问题是完全背包问题的一个变种。它从求“最大”价值变为求“最小”硬币数量,因此状态转移方程中的 $\max()$ 应改为 $\min()$ 。从追求“不超过”背包容量到追求“恰好”凑出目标金额,因此使用 $amt + 1$ 来表示“无法凑出目标金额”的无效解。
- 零钱兑换问题 II 从求“最少硬币数量”改为求“硬币组合数量”,状态转移方程相应地从 $\min()$ 改为求和运算符。 - 零钱兑换问题 II 从求“最少硬币数量”改为求“硬币组合数量”,状态转移方程相应地从 $\min()$ 改为求和运算符。

View File

@ -601,13 +601,45 @@ index = hash(key) % capacity
=== "Ruby" === "Ruby"
```ruby title="simple_hash.rb" ```ruby title="simple_hash.rb"
[class]{}-[func]{add_hash} ### 加法哈希 ###
def add_hash(key)
hash = 0
modulus = 1_000_000_007
[class]{}-[func]{mul_hash} key.each_char { |c| hash += c.ord }
[class]{}-[func]{xor_hash} hash % modulus
end
[class]{}-[func]{rot_hash} ### 乘法哈希 ###
def mul_hash(key)
hash = 0
modulus = 1_000_000_007
key.each_char { |c| hash = 31 * hash + c.ord }
hash % modulus
end
### 异或哈希 ###
def xor_hash(key)
hash = 0
modulus = 1_000_000_007
key.each_char { |c| hash ^= c.ord }
hash % modulus
end
### 旋转哈希 ###
def rot_hash(key)
hash = 0
modulus = 1_000_000_007
key.each_char { |c| hash = (hash << 4) ^ (hash >> 28) ^ c.ord }
hash % modulus
end
``` ```
=== "Zig" === "Zig"
@ -955,7 +987,29 @@ $$
=== "Ruby" === "Ruby"
```ruby title="built_in_hash.rb" ```ruby title="built_in_hash.rb"
num = 3
hash_num = num.hash
# 整数 3 的哈希值为 -4385856518450339636
bol = true
hash_bol = bol.hash
# 布尔量 true 的哈希值为 -1617938112149317027
dec = 3.14159
hash_dec = dec.hash
# 小数 3.14159 的哈希值为 -1479186995943067893
str = "Hello 算法"
hash_str = str.hash
# 字符串“Hello 算法”的哈希值为 -4075943250025831763
tup = [12836, '小哈']
hash_tup = tup.hash
# 元组 (12836, '小哈') 的哈希值为 1999544809202288822
obj = ListNode.new(0)
hash_obj = obj.hash
# 节点对象 #<ListNode:0x000078133140ab70> 的哈希值为 4302940560806366381
``` ```
=== "Zig" === "Zig"

View File

@ -1426,7 +1426,99 @@ comments: true
=== "Ruby" === "Ruby"
```ruby title="hash_map_chaining.rb" ```ruby title="hash_map_chaining.rb"
[class]{HashMapChaining}-[func]{} ### 键式地址哈希表 ###
class HashMapChaining
### 构造方法 ###
def initialize
@size = 0 # 键值对数量
@capacity = 4 # 哈希表容量
@load_thres = 2.0 / 3.0 # 触发扩容的负载因子阈值
@extend_ratio = 2 # 扩容倍数
@buckets = Array.new(@capacity) { [] } # 桶数组
end
### 哈希函数 ###
def hash_func(key)
key % @capacity
end
### 负载因子 ###
def load_factor
@size / @capacity
end
### 查询操作 ###
def get(key)
index = hash_func(key)
bucket = @buckets[index]
# 遍历桶,若找到 key ,则返回对应 val
for pair in bucket
return pair.val if pair.key == key
end
# 若未找到 key , 则返回 nil
nil
end
### 添加操作 ###
def put(key, val)
# 当负载因子超过阈值时,执行扩容
extend if load_factor > @load_thres
index = hash_func(key)
bucket = @buckets[index]
# 遍历桶,若遇到指定 key ,则更新对应 val 并返回
for pair in bucket
if pair.key == key
pair.val = val
return
end
end
# 若无该 key ,则将键值对添加至尾部
pair = Pair.new(key, val)
bucket << pair
@size += 1
end
### 删除操作 ###
def remove(key)
index = hash_func(key)
bucket = @buckets[index]
# 遍历桶,从中删除键值对
for pair in bucket
if pair.key == key
bucket.delete(pair)
@size -= 1
break
end
end
end
### 扩容哈希表 ###
def extend
# 暫存原哈希表
buckets = @buckets
# 初始化扩容后的新哈希表
@capacity *= @extend_ratio
@buckets = Array.new(@capacity) { [] }
@size = 0
# 将键值对从原哈希表搬运至新哈希表
for bucket in buckets
for pair in bucket
put(pair.key, pair.val)
end
end
end
### 打印哈希表 ###
def print
for bucket in @buckets
res = []
for pair in bucket
res << "#{pair.key} -> #{pair.val}"
end
pp res
end
end
end
``` ```
=== "Zig" === "Zig"
@ -3086,7 +3178,118 @@ comments: true
=== "Ruby" === "Ruby"
```ruby title="hash_map_open_addressing.rb" ```ruby title="hash_map_open_addressing.rb"
[class]{HashMapOpenAddressing}-[func]{} ### 开放寻址哈希表 ###
class HashMapOpenAddressing
TOMBSTONE = Pair.new(-1, '-1') # 删除标记
### 构造方法 ###
def initialize
@size = 0 # 键值对数量
@capacity = 4 # 哈希表容量
@load_thres = 2.0 / 3.0 # 触发扩容的负载因子阈值
@extend_ratio = 2 # 扩容倍数
@buckets = Array.new(@capacity) # 桶数组
end
### 哈希函数 ###
def hash_func(key)
key % @capacity
end
### 负载因子 ###
def load_factor
@size / @capacity
end
### 搜索 key 对应的桶索引 ###
def find_bucket(key)
index = hash_func(key)
first_tombstone = -1
# 线性探测,当遇到空桶时跳出
while !@buckets[index].nil?
# 若遇到 key ,返回对应的桶索引
if @buckets[index].key == key
# 若之前遇到了删除标记,则将键值对移动至该索引处
if first_tombstone != -1
@buckets[first_tombstone] = @buckets[index]
@buckets[index] = TOMBSTONE
return first_tombstone # 返回移动后的桶索引
end
return index # 返回桶索引
end
# 记录遇到的首个删除标记
first_tombstone = index if first_tombstone == -1 && @buckets[index] == TOMBSTONE
# 计算桶索引,越过尾部则返回头部
index = (index + 1) % @capacity
end
# 若 key 不存在,则返回添加点的索引
first_tombstone == -1 ? index : first_tombstone
end
### 查询操作 ###
def get(key)
# 搜索 key 对应的桶索引
index = find_bucket(key)
# 若找到键值对,则返回对应 val
return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index])
# 若键值对不存在,则返回 nil
nil
end
### 添加操作 ###
def put(key, val)
# 当负载因子超过阈值时,执行扩容
extend if load_factor > @load_thres
# 搜索 key 对应的桶索引
index = find_bucket(key)
# 若找到键值对,则覆盖 val 开返回
unless [nil, TOMBSTONE].include?(@buckets[index])
@buckets[index].val = val
return
end
# 若键值对不存在,则添加该键值对
@buckets[index] = Pair.new(key, val)
@size += 1
end
### 删除操作 ###
def remove(key)
# 搜索 key 对应的桶索引
index = find_bucket(key)
# 若找到键值对,则用删除标记覆盖它
unless [nil, TOMBSTONE].include?(@buckets[index])
@buckets[index] = TOMBSTONE
@size -= 1
end
end
### 扩容哈希表 ###
def extend
# 暂存原哈希表
buckets_tmp = @buckets
# 初始化扩容后的新哈希表
@capacity *= @extend_ratio
@buckets = Array.new(@capacity)
@size = 0
# 将键值对从原哈希表搬运至新哈希表
for pair in buckets_tmp
put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair)
end
end
### 打印哈希表 ###
def print
for pair in @buckets
if pair.nil?
puts "Nil"
elsif pair == TOMBSTONE
puts "TOMBSTONE"
else
puts "#{pair.key} -> #{pair.val}"
end
end
end
end
``` ```
=== "Zig" === "Zig"

View File

@ -303,7 +303,24 @@ comments: true
=== "Ruby" === "Ruby"
```ruby title="hash_map.rb" ```ruby title="hash_map.rb"
# 初始化哈希表
hmap = {}
# 添加操作
# 在哈希表中添加键值对 (key, value)
hmap[12836] = "小哈"
hmap[15937] = "小啰"
hmap[16750] = "小算"
hmap[13276] = "小法"
hmap[10583] = "小鸭"
# 查询操作
# 向哈希表中输入键 key ,得到值 value
name = hmap[15937]
# 删除操作
# 在哈希表中删除键值对 (key, value)
hmap.delete(10583)
``` ```
=== "Zig" === "Zig"
@ -523,7 +540,15 @@ comments: true
=== "Ruby" === "Ruby"
```ruby title="hash_map.rb" ```ruby title="hash_map.rb"
# 遍历哈希表
# 遍历键值对 key->value
hmap.entries.each { |key, value| puts "#{key} -> #{value}" }
# 单独遍历键 key
hmap.keys.each { |key| puts key }
# 单独遍历值 value
hmap.values.each { |val| puts val }
``` ```
=== "Zig" === "Zig"
@ -1666,9 +1691,78 @@ index = hash(key) % capacity
=== "Ruby" === "Ruby"
```ruby title="array_hash_map.rb" ```ruby title="array_hash_map.rb"
[class]{Pair}-[func]{} ### 键值对 ###
class Pair
attr_accessor :key, :val
[class]{ArrayHashMap}-[func]{} def initialize(key, val)
@key = key
@val = val
end
end
### 基于数组实现的哈希表 ###
class ArrayHashMap
### 构造方法 ###
def initialize
# 初始化数组,包含 100 个桶
@buckets = Array.new(100)
end
### 哈希函数 ###
def hash_func(key)
index = key % 100
end
### 查询操作 ###
def get(key)
index = hash_func(key)
pair = @buckets[index]
return if pair.nil?
pair.val
end
### 添加操作 ###
def put(key, val)
pair = Pair.new(key, val)
index = hash_func(key)
@buckets[index] = pair
end
### 删除操作 ###
def remove(key)
index = hash_func(key)
# 置为 nil ,代表删除
@buckets[index] = nil
end
### 获取所有键值对 ###
def entry_set
result = []
@buckets.each { |pair| result << pair unless pair.nil? }
result
end
### 获取所有键 ###
def key_set
result = []
@buckets.each { |pair| result << pair.key unless pair.nil? }
result
end
### 获取所有值 ###
def value_set
result = []
@buckets.each { |pair| result << pair.val unless pair.nil? }
result
end
### 打印哈希表 ###
def print
@buckets.each { |pair| puts "#{pair.key} -> #{pair.val}" unless pair.nil? }
end
end
``` ```
=== "Zig" === "Zig"

View File

@ -462,7 +462,7 @@ comments: true
def is_empty(self) -> bool: def is_empty(self) -> bool:
"""判断双向队列是否为空""" """判断双向队列是否为空"""
return self.size() == 0 return self._size == 0
def push(self, num: int, is_front: bool): def push(self, num: int, is_front: bool):
"""入队操作""" """入队操作"""

View File

@ -416,7 +416,7 @@ comments: true
def is_empty(self) -> bool: def is_empty(self) -> bool:
"""判断队列是否为空""" """判断队列是否为空"""
return not self._front return self._size == 0
def push(self, num: int): def push(self, num: int):
"""入队""" """入队"""

View File

@ -412,7 +412,7 @@ comments: true
def is_empty(self) -> bool: def is_empty(self) -> bool:
"""判断栈是否为空""" """判断栈是否为空"""
return not self._peek return self._size == 0
def push(self, val: int): def push(self, val: int):
"""入栈""" """入栈"""
@ -1284,7 +1284,7 @@ comments: true
def is_empty(self) -> bool: def is_empty(self) -> bool:
"""判断栈是否为空""" """判断栈是否为空"""
return self._stack == [] return self._size == 0
def push(self, item: int): def push(self, item: int):
"""入栈""" """入栈"""

View File

@ -11,7 +11,7 @@ icon: material/graphql
In the journey of life, we are like individual nodes, connected by countless invisible edges. In the journey of life, we are like individual nodes, connected by countless invisible edges.
Every encountering and parting leaves a unique mark on this vast network graph. Each encounter and parting leaves a distinctive imprint on this vast network graph.
## Chapter contents ## Chapter contents

View File

@ -601,13 +601,45 @@ The design of hash algorithms is a complex issue that requires consideration of
=== "Ruby" === "Ruby"
```ruby title="simple_hash.rb" ```ruby title="simple_hash.rb"
[class]{}-[func]{add_hash} ### 加法哈希 ###
def add_hash(key)
hash = 0
modulus = 1_000_000_007
[class]{}-[func]{mul_hash} key.each_char { |c| hash += c.ord }
[class]{}-[func]{xor_hash} hash % modulus
end
[class]{}-[func]{rot_hash} ### 乘法哈希 ###
def mul_hash(key)
hash = 0
modulus = 1_000_000_007
key.each_char { |c| hash = 31 * hash + c.ord }
hash % modulus
end
### 异或哈希 ###
def xor_hash(key)
hash = 0
modulus = 1_000_000_007
key.each_char { |c| hash ^= c.ord }
hash % modulus
end
### 旋转哈希 ###
def rot_hash(key)
hash = 0
modulus = 1_000_000_007
key.each_char { |c| hash = (hash << 4) ^ (hash >> 28) ^ c.ord }
hash % modulus
end
``` ```
=== "Zig" === "Zig"

View File

@ -1426,7 +1426,99 @@ The code below provides a simple implementation of a separate chaining hash tabl
=== "Ruby" === "Ruby"
```ruby title="hash_map_chaining.rb" ```ruby title="hash_map_chaining.rb"
[class]{HashMapChaining}-[func]{} ### 键式地址哈希表 ###
class HashMapChaining
### 构造方法 ###
def initialize
@size = 0 # 键值对数量
@capacity = 4 # 哈希表容量
@load_thres = 2.0 / 3.0 # 触发扩容的负载因子阈值
@extend_ratio = 2 # 扩容倍数
@buckets = Array.new(@capacity) { [] } # 桶数组
end
### 哈希函数 ###
def hash_func(key)
key % @capacity
end
### 负载因子 ###
def load_factor
@size / @capacity
end
### 查询操作 ###
def get(key)
index = hash_func(key)
bucket = @buckets[index]
# 遍历桶,若找到 key ,则返回对应 val
for pair in bucket
return pair.val if pair.key == key
end
# 若未找到 key , 则返回 nil
nil
end
### 添加操作 ###
def put(key, val)
# 当负载因子超过阈值时,执行扩容
extend if load_factor > @load_thres
index = hash_func(key)
bucket = @buckets[index]
# 遍历桶,若遇到指定 key ,则更新对应 val 并返回
for pair in bucket
if pair.key == key
pair.val = val
return
end
end
# 若无该 key ,则将键值对添加至尾部
pair = Pair.new(key, val)
bucket << pair
@size += 1
end
### 删除操作 ###
def remove(key)
index = hash_func(key)
bucket = @buckets[index]
# 遍历桶,从中删除键值对
for pair in bucket
if pair.key == key
bucket.delete(pair)
@size -= 1
break
end
end
end
### 扩容哈希表 ###
def extend
# 暫存原哈希表
buckets = @buckets
# 初始化扩容后的新哈希表
@capacity *= @extend_ratio
@buckets = Array.new(@capacity) { [] }
@size = 0
# 将键值对从原哈希表搬运至新哈希表
for bucket in buckets
for pair in bucket
put(pair.key, pair.val)
end
end
end
### 打印哈希表 ###
def print
for bucket in @buckets
res = []
for pair in bucket
res << "#{pair.key} -> #{pair.val}"
end
pp res
end
end
end
``` ```
=== "Zig" === "Zig"
@ -3086,7 +3178,118 @@ The code below implements an open addressing (linear probing) hash table with la
=== "Ruby" === "Ruby"
```ruby title="hash_map_open_addressing.rb" ```ruby title="hash_map_open_addressing.rb"
[class]{HashMapOpenAddressing}-[func]{} ### 开放寻址哈希表 ###
class HashMapOpenAddressing
TOMBSTONE = Pair.new(-1, '-1') # 删除标记
### 构造方法 ###
def initialize
@size = 0 # 键值对数量
@capacity = 4 # 哈希表容量
@load_thres = 2.0 / 3.0 # 触发扩容的负载因子阈值
@extend_ratio = 2 # 扩容倍数
@buckets = Array.new(@capacity) # 桶数组
end
### 哈希函数 ###
def hash_func(key)
key % @capacity
end
### 负载因子 ###
def load_factor
@size / @capacity
end
### 搜索 key 对应的桶索引 ###
def find_bucket(key)
index = hash_func(key)
first_tombstone = -1
# 线性探测,当遇到空桶时跳出
while !@buckets[index].nil?
# 若遇到 key ,返回对应的桶索引
if @buckets[index].key == key
# 若之前遇到了删除标记,则将键值对移动至该索引处
if first_tombstone != -1
@buckets[first_tombstone] = @buckets[index]
@buckets[index] = TOMBSTONE
return first_tombstone # 返回移动后的桶索引
end
return index # 返回桶索引
end
# 记录遇到的首个删除标记
first_tombstone = index if first_tombstone == -1 && @buckets[index] == TOMBSTONE
# 计算桶索引,越过尾部则返回头部
index = (index + 1) % @capacity
end
# 若 key 不存在,则返回添加点的索引
first_tombstone == -1 ? index : first_tombstone
end
### 查询操作 ###
def get(key)
# 搜索 key 对应的桶索引
index = find_bucket(key)
# 若找到键值对,则返回对应 val
return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index])
# 若键值对不存在,则返回 nil
nil
end
### 添加操作 ###
def put(key, val)
# 当负载因子超过阈值时,执行扩容
extend if load_factor > @load_thres
# 搜索 key 对应的桶索引
index = find_bucket(key)
# 若找到键值对,则覆盖 val 开返回
unless [nil, TOMBSTONE].include?(@buckets[index])
@buckets[index].val = val
return
end
# 若键值对不存在,则添加该键值对
@buckets[index] = Pair.new(key, val)
@size += 1
end
### 删除操作 ###
def remove(key)
# 搜索 key 对应的桶索引
index = find_bucket(key)
# 若找到键值对,则用删除标记覆盖它
unless [nil, TOMBSTONE].include?(@buckets[index])
@buckets[index] = TOMBSTONE
@size -= 1
end
end
### 扩容哈希表 ###
def extend
# 暂存原哈希表
buckets_tmp = @buckets
# 初始化扩容后的新哈希表
@capacity *= @extend_ratio
@buckets = Array.new(@capacity)
@size = 0
# 将键值对从原哈希表搬运至新哈希表
for pair in buckets_tmp
put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair)
end
end
### 打印哈希表 ###
def print
for pair in @buckets
if pair.nil?
puts "Nil"
elsif pair == TOMBSTONE
puts "TOMBSTONE"
else
puts "#{pair.key} -> #{pair.val}"
end
end
end
end
``` ```
=== "Zig" === "Zig"

View File

@ -1625,9 +1625,78 @@ The following code implements a simple hash table. Here, we encapsulate `key` an
=== "Ruby" === "Ruby"
```ruby title="array_hash_map.rb" ```ruby title="array_hash_map.rb"
[class]{Pair}-[func]{} ### 键值对 ###
class Pair
attr_accessor :key, :val
[class]{ArrayHashMap}-[func]{} def initialize(key, val)
@key = key
@val = val
end
end
### 基于数组实现的哈希表 ###
class ArrayHashMap
### 构造方法 ###
def initialize
# 初始化数组,包含 100 个桶
@buckets = Array.new(100)
end
### 哈希函数 ###
def hash_func(key)
index = key % 100
end
### 查询操作 ###
def get(key)
index = hash_func(key)
pair = @buckets[index]
return if pair.nil?
pair.val
end
### 添加操作 ###
def put(key, val)
pair = Pair.new(key, val)
index = hash_func(key)
@buckets[index] = pair
end
### 删除操作 ###
def remove(key)
index = hash_func(key)
# 置为 nil ,代表删除
@buckets[index] = nil
end
### 获取所有键值对 ###
def entry_set
result = []
@buckets.each { |pair| result << pair unless pair.nil? }
result
end
### 获取所有键 ###
def key_set
result = []
@buckets.each { |pair| result << pair.key unless pair.nil? }
result
end
### 获取所有值 ###
def value_set
result = []
@buckets.each { |pair| result << pair.val unless pair.nil? }
result
end
### 打印哈希表 ###
def print
@buckets.each { |pair| puts "#{pair.key} -> #{pair.val}" unless pair.nil? }
end
end
``` ```
=== "Zig" === "Zig"

View File

@ -408,7 +408,7 @@ The implementation code is as follows:
def is_empty(self) -> bool: def is_empty(self) -> bool:
"""判断双向队列是否为空""" """判断双向队列是否为空"""
return self.size() == 0 return self._size == 0
def push(self, num: int, is_front: bool): def push(self, num: int, is_front: bool):
"""入队操作""" """入队操作"""

View File

@ -368,7 +368,7 @@ Below is the code for implementing a queue using a linked list:
def is_empty(self) -> bool: def is_empty(self) -> bool:
"""判断队列是否为空""" """判断队列是否为空"""
return not self._front return self._size == 0
def push(self, num: int): def push(self, num: int):
"""入队""" """入队"""

View File

@ -365,7 +365,7 @@ Below is an example code for implementing a stack based on a linked list:
def is_empty(self) -> bool: def is_empty(self) -> bool:
"""判断栈是否为空""" """判断栈是否为空"""
return not self._peek return self._size == 0
def push(self, val: int): def push(self, val: int):
"""入栈""" """入栈"""
@ -1237,7 +1237,7 @@ Since the elements to be pushed onto the stack may continuously increase, we can
def is_empty(self) -> bool: def is_empty(self) -> bool:
"""判断栈是否为空""" """判断栈是否为空"""
return self._stack == [] return self._size == 0
def push(self, item: int): def push(self, item: int):
"""入栈""" """入栈"""

View File

@ -24,15 +24,15 @@ comments: true
**第一步:思考每輪的決策,定義狀態,從而得到 $dp$ 表** **第一步:思考每輪的決策,定義狀態,從而得到 $dp$ 表**
對於每個物品來說,不放入背包,背包容量不變;放入背包,背包容量減小。由此可得狀態定義:當前物品編號 $i$ 和剩餘背包容量 $c$ ,記為 $[i, c]$ 。 對於每個物品來說,不放入背包,背包容量不變;放入背包,背包容量減小。由此可得狀態定義:當前物品編號 $i$ 和背包容量 $c$ ,記為 $[i, c]$ 。
狀態 $[i, c]$ 對應的子問題為:**前 $i$ 個物品在剩餘容量為 $c$ 的背包中的最大價值**,記為 $dp[i, c]$ 。 狀態 $[i, c]$ 對應的子問題為:**前 $i$ 個物品在容量為 $c$ 的背包中的最大價值**,記為 $dp[i, c]$ 。
待求解的是 $dp[n, cap]$ ,因此需要一個尺寸為 $(n+1) \times (cap+1)$ 的二維 $dp$ 表。 待求解的是 $dp[n, cap]$ ,因此需要一個尺寸為 $(n+1) \times (cap+1)$ 的二維 $dp$ 表。
**第二步:找出最優子結構,進而推導出狀態轉移方程** **第二步:找出最優子結構,進而推導出狀態轉移方程**
當我們做出物品 $i$ 的決策後,剩餘的是前 $i-1$ 個物品決策,可分為以下兩種情況。 當我們做出物品 $i$ 的決策後,剩餘的是前 $i-1$ 個物品決策的子問題,可分為以下兩種情況。
- **不放入物品 $i$** :背包容量不變,狀態變化為 $[i-1, c]$ 。 - **不放入物品 $i$** :背包容量不變,狀態變化為 $[i-1, c]$ 。
- **放入物品 $i$** :背包容量減少 $wgt[i-1]$ ,價值增加 $val[i-1]$ ,狀態變化為 $[i-1, c-wgt[i-1]]$ 。 - **放入物品 $i$** :背包容量減少 $wgt[i-1]$ ,價值增加 $val[i-1]$ ,狀態變化為 $[i-1, c-wgt[i-1]]$ 。
@ -47,7 +47,7 @@ $$
**第三步:確定邊界條件和狀態轉移順序** **第三步:確定邊界條件和狀態轉移順序**
當無物品或無剩餘背包容量時最大價值為 $0$ ,即首列 $dp[i, 0]$ 和首行 $dp[0, c]$ 都等於 $0$ 。 當無物品或背包容量為 $0$ 時最大價值為 $0$ ,即首列 $dp[i, 0]$ 和首行 $dp[0, c]$ 都等於 $0$ 。
當前狀態 $[i, c]$ 從上方的狀態 $[i-1, c]$ 和左上方的狀態 $[i-1, c-wgt[i-1]]$ 轉移而來,因此透過兩層迴圈正序走訪整個 $dp$ 表即可。 當前狀態 $[i, c]$ 從上方的狀態 $[i-1, c]$ 和左上方的狀態 $[i-1, c-wgt[i-1]]$ 轉移而來,因此透過兩層迴圈正序走訪整個 $dp$ 表即可。

View File

@ -15,7 +15,7 @@ comments: true
**背包問題** **背包問題**
- 背包問題是最典型的動態規劃問題之一,具有 0-1 背包、完全背包、多重背包等變種。 - 背包問題是最典型的動態規劃問題之一,具有 0-1 背包、完全背包、多重背包等變種。
- 0-1 背包的狀態定義為前 $i$ 個物品在剩餘容量為 $c$ 的背包中的最大價值。根據不放入背包和放入背包兩種決策,可得到最優子結構,並構建出狀態轉移方程。在空間最佳化中,由於每個狀態依賴正上方和左上方的狀態,因此需要倒序走訪串列,避免左上方狀態被覆蓋。 - 0-1 背包的狀態定義為前 $i$ 個物品在容量為 $c$ 的背包中的最大價值。根據不放入背包和放入背包兩種決策,可得到最優子結構,並構建出狀態轉移方程。在空間最佳化中,由於每個狀態依賴正上方和左上方的狀態,因此需要倒序走訪串列,避免左上方狀態被覆蓋。
- 完全背包問題的每種物品的選取數量無限制,因此選擇放入物品的狀態轉移與 0-1 背包問題不同。由於狀態依賴正上方和正左方的狀態,因此在空間最佳化中應當正序走訪。 - 完全背包問題的每種物品的選取數量無限制,因此選擇放入物品的狀態轉移與 0-1 背包問題不同。由於狀態依賴正上方和正左方的狀態,因此在空間最佳化中應當正序走訪。
- 零錢兌換問題是完全背包問題的一個變種。它從求“最大”價值變為求“最小”硬幣數量,因此狀態轉移方程中的 $\max()$ 應改為 $\min()$ 。從追求“不超過”背包容量到追求“恰好”湊出目標金額,因此使用 $amt + 1$ 來表示“無法湊出目標金額”的無效解。 - 零錢兌換問題是完全背包問題的一個變種。它從求“最大”價值變為求“最小”硬幣數量,因此狀態轉移方程中的 $\max()$ 應改為 $\min()$ 。從追求“不超過”背包容量到追求“恰好”湊出目標金額,因此使用 $amt + 1$ 來表示“無法湊出目標金額”的無效解。
- 零錢兌換問題 II 從求“最少硬幣數量”改為求“硬幣組合數量”,狀態轉移方程相應地從 $\min()$ 改為求和運算子。 - 零錢兌換問題 II 從求“最少硬幣數量”改為求“硬幣組合數量”,狀態轉移方程相應地從 $\min()$ 改為求和運算子。

View File

@ -601,13 +601,45 @@ index = hash(key) % capacity
=== "Ruby" === "Ruby"
```ruby title="simple_hash.rb" ```ruby title="simple_hash.rb"
[class]{}-[func]{add_hash} ### 加法雜湊 ###
def add_hash(key)
hash = 0
modulus = 1_000_000_007
[class]{}-[func]{mul_hash} key.each_char { |c| hash += c.ord }
[class]{}-[func]{xor_hash} hash % modulus
end
[class]{}-[func]{rot_hash} ### 乘法雜湊 ###
def mul_hash(key)
hash = 0
modulus = 1_000_000_007
key.each_char { |c| hash = 31 * hash + c.ord }
hash % modulus
end
### 互斥或雜湊 ###
def xor_hash(key)
hash = 0
modulus = 1_000_000_007
key.each_char { |c| hash ^= c.ord }
hash % modulus
end
### 旋轉雜湊 ###
def rot_hash(key)
hash = 0
modulus = 1_000_000_007
key.each_char { |c| hash = (hash << 4) ^ (hash >> 28) ^ c.ord }
hash % modulus
end
``` ```
=== "Zig" === "Zig"
@ -955,7 +987,29 @@ $$
=== "Ruby" === "Ruby"
```ruby title="built_in_hash.rb" ```ruby title="built_in_hash.rb"
num = 3
hash_num = num.hash
# 整數 3 的雜湊值為 -4385856518450339636
bol = true
hash_bol = bol.hash
# 布林量 true 的雜湊值為 -1617938112149317027
dec = 3.14159
hash_dec = dec.hash
# 小數 3.14159 的雜湊值為 -1479186995943067893
str = "Hello 演算法"
hash_str = str.hash
# 字串“Hello 演算法”的雜湊值為 -4075943250025831763
tup = [12836, '小哈']
hash_tup = tup.hash
# 元組 (12836, '小哈') 的雜湊值為 1999544809202288822
obj = ListNode.new(0)
hash_obj = obj.hash
# 節點物件 #<ListNode:0x000078133140ab70> 的雜湊值為 4302940560806366381
``` ```
=== "Zig" === "Zig"

View File

@ -1426,7 +1426,99 @@ comments: true
=== "Ruby" === "Ruby"
```ruby title="hash_map_chaining.rb" ```ruby title="hash_map_chaining.rb"
[class]{HashMapChaining}-[func]{} ### 鍵式位址雜湊表 ###
class HashMapChaining
### 建構子 ###
def initialize
@size = 0 # 鍵值對數量
@capacity = 4 # 雜湊表容量
@load_thres = 2.0 / 3.0 # 觸發擴容的負載因子閾值
@extend_ratio = 2 # 擴容倍數
@buckets = Array.new(@capacity) { [] } # 桶陣列
end
### 雜湊函式 ###
def hash_func(key)
key % @capacity
end
### 負載因子 ###
def load_factor
@size / @capacity
end
### 查詢操作 ###
def get(key)
index = hash_func(key)
bucket = @buckets[index]
# 走訪桶,若找到 key ,則返回對應 val
for pair in bucket
return pair.val if pair.key == key
end
# 若未找到 key , 則返回 nil
nil
end
### 新增操作 ###
def put(key, val)
# 當負載因子超過閾值時,執行擴容
extend if load_factor > @load_thres
index = hash_func(key)
bucket = @buckets[index]
# 走訪桶,若遇到指定 key ,則更新對應 val 並返回
for pair in bucket
if pair.key == key
pair.val = val
return
end
end
# 若無該 key ,則將鍵值對新增至尾部
pair = Pair.new(key, val)
bucket << pair
@size += 1
end
### 刪除操作 ###
def remove(key)
index = hash_func(key)
bucket = @buckets[index]
# 走訪桶,從中刪除鍵值對
for pair in bucket
if pair.key == key
bucket.delete(pair)
@size -= 1
break
end
end
end
### 擴容雜湊表 ###
def extend
# 暫存原雜湊表
buckets = @buckets
# 初始化擴容後的新雜湊表
@capacity *= @extend_ratio
@buckets = Array.new(@capacity) { [] }
@size = 0
# 將鍵值對從原雜湊表搬運至新雜湊表
for bucket in buckets
for pair in bucket
put(pair.key, pair.val)
end
end
end
### 列印雜湊表 ###
def print
for bucket in @buckets
res = []
for pair in bucket
res << "#{pair.key} -> #{pair.val}"
end
pp res
end
end
end
``` ```
=== "Zig" === "Zig"
@ -3086,7 +3178,118 @@ comments: true
=== "Ruby" === "Ruby"
```ruby title="hash_map_open_addressing.rb" ```ruby title="hash_map_open_addressing.rb"
[class]{HashMapOpenAddressing}-[func]{} ### 開放定址雜湊表 ###
class HashMapOpenAddressing
TOMBSTONE = Pair.new(-1, '-1') # 刪除標記
### 建構子 ###
def initialize
@size = 0 # 鍵值對數量
@capacity = 4 # 雜湊表容量
@load_thres = 2.0 / 3.0 # 觸發擴容的負載因子閾值
@extend_ratio = 2 # 擴容倍數
@buckets = Array.new(@capacity) # 桶陣列
end
### 雜湊函式 ###
def hash_func(key)
key % @capacity
end
### 負載因子 ###
def load_factor
@size / @capacity
end
### 搜尋 key 對應的桶索引 ###
def find_bucket(key)
index = hash_func(key)
first_tombstone = -1
# 線性探查,當遇到空桶時跳出
while !@buckets[index].nil?
# 若遇到 key ,返回對應的桶索引
if @buckets[index].key == key
# 若之前遇到了刪除標記,則將鍵值對移動至該索引處
if first_tombstone != -1
@buckets[first_tombstone] = @buckets[index]
@buckets[index] = TOMBSTONE
return first_tombstone # 返回移動後的桶索引
end
return index # 返回桶索引
end
# 記錄遇到的首個刪除標記
first_tombstone = index if first_tombstone == -1 && @buckets[index] == TOMBSTONE
# 計算桶索引,越過尾部則返回頭部
index = (index + 1) % @capacity
end
# 若 key 不存在,則返回新增點的索引
first_tombstone == -1 ? index : first_tombstone
end
### 查詢操作 ###
def get(key)
# 搜尋 key 對應的桶索引
index = find_bucket(key)
# 若找到鍵值對,則返回對應 val
return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index])
# 若鍵值對不存在,則返回 nil
nil
end
### 新增操作 ###
def put(key, val)
# 當負載因子超過閾值時,執行擴容
extend if load_factor > @load_thres
# 搜尋 key 對應的桶索引
index = find_bucket(key)
# 若找到鍵值對,則覆蓋 val 開返回
unless [nil, TOMBSTONE].include?(@buckets[index])
@buckets[index].val = val
return
end
# 若鍵值對不存在,則新增該鍵值對
@buckets[index] = Pair.new(key, val)
@size += 1
end
### 刪除操作 ###
def remove(key)
# 搜尋 key 對應的桶索引
index = find_bucket(key)
# 若找到鍵值對,則用刪除標記覆蓋它
unless [nil, TOMBSTONE].include?(@buckets[index])
@buckets[index] = TOMBSTONE
@size -= 1
end
end
### 擴容雜湊表 ###
def extend
# 暫存原雜湊表
buckets_tmp = @buckets
# 初始化擴容後的新雜湊表
@capacity *= @extend_ratio
@buckets = Array.new(@capacity)
@size = 0
# 將鍵值對從原雜湊表搬運至新雜湊表
for pair in buckets_tmp
put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair)
end
end
### 列印雜湊表 ###
def print
for pair in @buckets
if pair.nil?
puts "Nil"
elsif pair == TOMBSTONE
puts "TOMBSTONE"
else
puts "#{pair.key} -> #{pair.val}"
end
end
end
end
``` ```
=== "Zig" === "Zig"

View File

@ -303,7 +303,24 @@ comments: true
=== "Ruby" === "Ruby"
```ruby title="hash_map.rb" ```ruby title="hash_map.rb"
# 初始化雜湊表
hmap = {}
# 新增操作
# 在雜湊表中新增鍵值對 (key, value)
hmap[12836] = "小哈"
hmap[15937] = "小囉"
hmap[16750] = "小算"
hmap[13276] = "小法"
hmap[10583] = "小鴨"
# 查詢操作
# 向雜湊表中輸入鍵 key ,得到值 value
name = hmap[15937]
# 刪除操作
# 在雜湊表中刪除鍵值對 (key, value)
hmap.delete(10583)
``` ```
=== "Zig" === "Zig"
@ -523,7 +540,15 @@ comments: true
=== "Ruby" === "Ruby"
```ruby title="hash_map.rb" ```ruby title="hash_map.rb"
# 走訪雜湊表
# 走訪鍵值對 key->value
hmap.entries.each { |key, value| puts "#{key} -> #{value}" }
# 單獨走訪鍵 key
hmap.keys.each { |key| puts key }
# 單獨走訪值 value
hmap.values.each { |val| puts val }
``` ```
=== "Zig" === "Zig"
@ -1666,9 +1691,78 @@ index = hash(key) % capacity
=== "Ruby" === "Ruby"
```ruby title="array_hash_map.rb" ```ruby title="array_hash_map.rb"
[class]{Pair}-[func]{} ### 鍵值對 ###
class Pair
attr_accessor :key, :val
[class]{ArrayHashMap}-[func]{} def initialize(key, val)
@key = key
@val = val
end
end
### 基於陣列實現的雜湊表 ###
class ArrayHashMap
### 建構子 ###
def initialize
# 初始化陣列,包含 100 個桶
@buckets = Array.new(100)
end
### 雜湊函式 ###
def hash_func(key)
index = key % 100
end
### 查詢操作 ###
def get(key)
index = hash_func(key)
pair = @buckets[index]
return if pair.nil?
pair.val
end
### 新增操作 ###
def put(key, val)
pair = Pair.new(key, val)
index = hash_func(key)
@buckets[index] = pair
end
### 刪除操作 ###
def remove(key)
index = hash_func(key)
# 置為 nil ,代表刪除
@buckets[index] = nil
end
### 獲取所有鍵值對 ###
def entry_set
result = []
@buckets.each { |pair| result << pair unless pair.nil? }
result
end
### 獲取所有鍵 ###
def key_set
result = []
@buckets.each { |pair| result << pair.key unless pair.nil? }
result
end
### 獲取所有值 ###
def value_set
result = []
@buckets.each { |pair| result << pair.val unless pair.nil? }
result
end
### 列印雜湊表 ###
def print
@buckets.each { |pair| puts "#{pair.key} -> #{pair.val}" unless pair.nil? }
end
end
``` ```
=== "Zig" === "Zig"

View File

@ -462,7 +462,7 @@ comments: true
def is_empty(self) -> bool: def is_empty(self) -> bool:
"""判斷雙向佇列是否為空""" """判斷雙向佇列是否為空"""
return self.size() == 0 return self._size == 0
def push(self, num: int, is_front: bool): def push(self, num: int, is_front: bool):
"""入列操作""" """入列操作"""

View File

@ -416,7 +416,7 @@ comments: true
def is_empty(self) -> bool: def is_empty(self) -> bool:
"""判斷佇列是否為空""" """判斷佇列是否為空"""
return not self._front return self._size == 0
def push(self, num: int): def push(self, num: int):
"""入列""" """入列"""

View File

@ -412,7 +412,7 @@ comments: true
def is_empty(self) -> bool: def is_empty(self) -> bool:
"""判斷堆疊是否為空""" """判斷堆疊是否為空"""
return not self._peek return self._size == 0
def push(self, val: int): def push(self, val: int):
"""入堆疊""" """入堆疊"""
@ -1284,7 +1284,7 @@ comments: true
def is_empty(self) -> bool: def is_empty(self) -> bool:
"""判斷堆疊是否為空""" """判斷堆疊是否為空"""
return self._stack == [] return self._size == 0
def push(self, item: int): def push(self, item: int):
"""入堆疊""" """入堆疊"""