小白备战大厂算法笔试(四)——哈希表

文章目录

  • 哈希表
    • 常用操作
    • 简单实现
    • 冲突与扩容
    • 链式地址
    • 开放寻址
      • 线性探测
      • 多次哈希

哈希表

哈希表,又称散列表,其通过建立键 key 与值 value 之间的映射,实现高效的元素查询。具体而言,我们向哈希表输入一个键 key ,则可以在 O(1) 时间内获取对应的值 value

image-20230908142902540

除哈希表外,数组和链表也可以实现查询功能,它们的效率对比如下所示。

  • 添加元素:仅需将元素添加至数组(链表)的尾部即可,使用 O(1) 时间。
  • 查询元素:由于数组(链表)是乱序的,因此需要遍历其中的所有元素,使用 O(n) 时间。
  • 删除元素:需要先查询到元素,再从数组(链表)中删除,使用 O(n) 时间。
数组链表哈希表
查找元素O(n)O(n)O(1)
添加元素O(1)O(1)O(1)
删除元素O(n)O(n)O(1)

在哈希表中进行增删查改的时间复杂度都是 O(1) ,非常高效。

常用操作

Python:

# 初始化哈希表
hmap: dict = {}# 添加操作
# 在哈希表中添加键值对 (key, value)
hmap[12836] = "小哈"
hmap[15937] = "小啰"
hmap[16750] = "小算"
hmap[13276] = "小法"
hmap[10583] = "小鸭"# 查询操作
# 向哈希表输入键 key ,得到值 value
name: str = hmap[15937]# 删除操作
# 在哈希表中删除键值对 (key, value)
hmap.pop(10583)# 遍历哈希表
# 遍历键值对 key->value
for key, value in hmap.items():print(key, "->", value)
# 单独遍历键 key
for key in hmap.keys():print(key)
# 单独遍历值 value
for value in hmap.values():print(value)

Go:

/* 初始化哈希表 */
hmap := make(map[int]string)/* 添加操作 */
// 在哈希表中添加键值对 (key, value)
hmap[12836] = "小哈"
hmap[15937] = "小啰"
hmap[16750] = "小算"
hmap[13276] = "小法"
hmap[10583] = "小鸭"/* 查询操作 */
// 向哈希表输入键 key ,得到值 value
name := hmap[15937]/* 删除操作 */
// 在哈希表中删除键值对 (key, value)
delete(hmap, 10583)/* 遍历哈希表 */
// 遍历键值对 key->value
for key, value := range hmap {fmt.Println(key, "->", value)
}
// 单独遍历键 key
for key := range hmap {fmt.Println(key)
}
// 单独遍历值 value
for _, value := range hmap {fmt.Println(value)
}

简单实现

先考虑最简单的情况,仅用一个数组来实现哈希表。在哈希表中,我们将数组中的每个空位称为桶,每个桶可存储一个键值对。因此,查询操作就是找到 key 对应的桶,并在桶中获取 value

那么,如何基于 key 来定位对应的桶呢?这是通过「哈希函数 hash function」实现的。哈希函数的作用是将一个较大的输入空间映射到一个较小的输出空间。在哈希表中,输入空间是所有 key ,输出空间是所有桶(数组索引)。换句话说,输入一个 key我们可以通过哈希函数得到该 key 对应的键值对在数组中的存储位置

输入一个 key ,哈希函数的计算过程分为以下两步。

  1. 通过某种哈希算法 hash() 计算得到哈希值。
  2. 将哈希值对桶数量(数组长度)capacity 取模,从而获取该 key 对应的数组索引 index
index = hash(key) % capacity

随后,我们就可以利用 index 在哈希表中访问对应的桶,从而获取 value

设数组长度 capacity = 100、哈希算法 hash(key) = key ,易得哈希函数为 key % 100 。以 key 学号和 value 姓名为例,展示哈希函数的工作原理:

image-20230908145727410

以下代码实现了一个简单哈希表。其中,我们将 keyvalue 封装成一个类 Pair ,以表示键值对。

Python:

class Pair:"""键值对"""def __init__(self, key: int, val: str):self.key = keyself.val = valclass ArrayHashMap:"""基于数组简易实现的哈希表"""def __init__(self):"""构造方法"""# 初始化数组,包含 100 个桶self.buckets: list[Pair | None] = [None] * 100def hash_func(self, key: int) -> int:"""哈希函数"""index = key % 100return indexdef get(self, key: int) -> str:"""查询操作"""index: int = self.hash_func(key)pair: Pair = self.buckets[index]if pair is None:return Nonereturn pair.valdef put(self, key: int, val: str):"""添加操作"""pair = Pair(key, val)index: int = self.hash_func(key)self.buckets[index] = pairdef remove(self, key: int):"""删除操作"""index: int = self.hash_func(key)# 置为 None ,代表删除self.buckets[index] = Nonedef entry_set(self) -> list[Pair]:"""获取所有键值对"""result: list[Pair] = []for pair in self.buckets:if pair is not None:result.append(pair)return resultdef key_set(self) -> list[int]:"""获取所有键"""result = []for pair in self.buckets:if pair is not None:result.append(pair.key)return resultdef value_set(self) -> list[str]:"""获取所有值"""result = []for pair in self.buckets:if pair is not None:result.append(pair.val)return resultdef print(self):"""打印哈希表"""for pair in self.buckets:if pair is not None:print(pair.key, "->", pair.val)

Go:

/* 键值对 */
type pair struct {key intval string
}/* 基于数组简易实现的哈希表 */
type arrayHashMap struct {buckets []*pair
}/* 初始化哈希表 */
func newArrayHashMap() *arrayHashMap {// 初始化数组,包含 100 个桶buckets := make([]*pair, 100)return &arrayHashMap{buckets: buckets}
}/* 哈希函数 */
func (a *arrayHashMap) hashFunc(key int) int {index := key % 100return index
}/* 查询操作 */
func (a *arrayHashMap) get(key int) string {index := a.hashFunc(key)pair := a.buckets[index]if pair == nil {return "Not Found"}return pair.val
}/* 添加操作 */
func (a *arrayHashMap) put(key int, val string) {pair := &pair{key: key, val: val}index := a.hashFunc(key)a.buckets[index] = pair
}/* 删除操作 */
func (a *arrayHashMap) remove(key int) {index := a.hashFunc(key)// 置为 nil ,代表删除a.buckets[index] = nil
}/* 获取所有键对 */
func (a *arrayHashMap) pairSet() []*pair {var pairs []*pairfor _, pair := range a.buckets {if pair != nil {pairs = append(pairs, pair)}}return pairs
}/* 获取所有键 */
func (a *arrayHashMap) keySet() []int {var keys []intfor _, pair := range a.buckets {if pair != nil {keys = append(keys, pair.key)}}return keys
}/* 获取所有值 */
func (a *arrayHashMap) valueSet() []string {var values []stringfor _, pair := range a.buckets {if pair != nil {values = append(values, pair.val)}}return values
}/* 打印哈希表 */
func (a *arrayHashMap) print() {for _, pair := range a.buckets {if pair != nil {fmt.Println(pair.key, "->", pair.val)}}
}

冲突与扩容

本质上看,哈希函数的作用是将所有 key 构成的输入空间映射到数组所有索引构成的输出空间,而输入空间往往远大于输出空间。因此,理论上一定存在“多个输入对应相同输出”的情况

对于上述示例中的哈希函数,当输入的 key 后两位相同时,哈希函数的输出结果也相同。例如,查询学号为 12836 和 20336 的两个学生时,我们得到:

12836 % 100 = 36
20336 % 100 = 36

两个学号指向了同一个姓名,这显然是不对的。我们将这种多个输入对应同一输出的情况称为哈希冲突 。

image-20230908150857947

容易想到,哈希表容量n越大,多个 key 被分配到同一个桶中的概率就越低,冲突就越少。因此,我们可以通过扩容哈希表来减少哈希冲突

image-20230908150958709

类似于数组扩容,哈希表扩容需将所有键值对从原哈希表迁移至新哈希表,非常耗时。并且由于哈希表容量 capacity 改变,我们需要通过哈希函数来重新计算所有键值对的存储位置,这进一步提高了扩容过程的计算开销。为了提升效率,我们可以采用以下策略。

  1. 改良哈希表数据结构,使得哈希表可以在存在哈希冲突时正常工作
  2. 仅在必要时,即当哈希冲突比较严重时,才执行扩容操作。

哈希表的结构改良方法主要包括“链式地址”和“开放寻址”。

负载因子是哈希表的一个重要概念,其定义为哈希表的元素数量除以桶数量,用于衡量哈希冲突的严重程度,也常被作为哈希表扩容的触发条件。例如在 Java 中,当负载因子超过 0.75 时,系统会将哈希表容量扩展为原先的 2 倍。

链式地址

在原始哈希表中,每个桶仅能存储一个键值对。链式地址将单个元素转换为链表,将键值对作为链表节点,将所有发生冲突的键值对都存储在同一链表中。

image-20230908151610522

哈希表在链式地址下的操作方法发生了一些变化。

  • 查询元素:输入 key ,经过哈希函数得到数组索引,即可访问链表头节点,然后遍历链表并对比 key 以查找目标键值对。
  • 添加元素:先通过哈希函数访问链表头节点,然后将节点(即键值对)添加到链表中。
  • 删除元素:根据哈希函数的结果访问链表头部,接着遍历链表以查找目标节点,并将其删除。

链式地址存在以下局限性。

  • 占用空间增大,链表包含节点指针,它相比数组更加耗费内存空间。
  • 查询效率降低,因为需要线性遍历链表来查找对应元素。

以下代码给出了链式地址哈希表的简单实现,需要注意两点。

  • 使用列表(动态数组)代替链表,从而简化代码。在这种设定下,哈希表(数组)包含多个桶,每个桶都是一个列表。
  • 以下实现包含哈希表扩容方法。当负载因子超过 0.75 时,我们将哈希表扩容至 2 倍。

Python:

class HashMapChaining:"""链式地址哈希表"""def __init__(self):"""构造方法"""self.size = 0  # 键值对数量self.capacity = 4  # 哈希表容量self.load_thres = 2 / 3  # 触发扩容的负载因子阈值self.extend_ratio = 2  # 扩容倍数self.buckets = [[] for _ in range(self.capacity)]  # 桶数组def hash_func(self, key: int) -> int:"""哈希函数"""return key % self.capacitydef load_factor(self) -> float:"""负载因子"""return self.size / self.capacitydef get(self, key: int) -> str:"""查询操作"""index = self.hash_func(key)bucket = self.buckets[index]# 遍历桶,若找到 key 则返回对应 valfor pair in bucket:if pair.key == key:return pair.val# 若未找到 key 则返回 Nonereturn Nonedef put(self, key: int, val: str):"""添加操作"""# 当负载因子超过阈值时,执行扩容if self.load_factor() > self.load_thres:self.extend()index = self.hash_func(key)bucket = self.buckets[index]# 遍历桶,若遇到指定 key ,则更新对应 val 并返回for pair in bucket:if pair.key == key:pair.val = valreturn# 若无该 key ,则将键值对添加至尾部pair = Pair(key, val)bucket.append(pair)self.size += 1def remove(self, key: int):"""删除操作"""index = self.hash_func(key)bucket = self.buckets[index]# 遍历桶,从中删除键值对for pair in bucket:if pair.key == key:bucket.remove(pair)self.size -= 1breakdef extend(self):"""扩容哈希表"""# 暂存原哈希表buckets = self.buckets# 初始化扩容后的新哈希表self.capacity *= self.extend_ratioself.buckets = [[] for _ in range(self.capacity)]self.size = 0# 将键值对从原哈希表搬运至新哈希表for bucket in buckets:for pair in bucket:self.put(pair.key, pair.val)def print(self):"""打印哈希表"""for bucket in self.buckets:res = []for pair in bucket:res.append(str(pair.key) + " -> " + pair.val)print(res)

Go:

/* 链式地址哈希表 */
type hashMapChaining struct {size        int      // 键值对数量capacity    int      // 哈希表容量loadThres   float64  // 触发扩容的负载因子阈值extendRatio int      // 扩容倍数buckets     [][]pair // 桶数组
}/* 构造方法 */
func newHashMapChaining() *hashMapChaining {buckets := make([][]pair, 4)for i := 0; i < 4; i++ {buckets[i] = make([]pair, 0)}return &hashMapChaining{size:        0,capacity:    4,loadThres:   2 / 3.0,extendRatio: 2,buckets:     buckets,}
}/* 哈希函数 */
func (m *hashMapChaining) hashFunc(key int) int {return key % m.capacity
}/* 负载因子 */
func (m *hashMapChaining) loadFactor() float64 {return float64(m.size / m.capacity)
}/* 查询操作 */
func (m *hashMapChaining) get(key int) string {idx := m.hashFunc(key)bucket := m.buckets[idx]// 遍历桶,若找到 key 则返回对应 valfor _, p := range bucket {if p.key == key {return p.val}}// 若未找到 key 则返回空字符串return ""
}/* 添加操作 */
func (m *hashMapChaining) put(key int, val string) {// 当负载因子超过阈值时,执行扩容if m.loadFactor() > m.loadThres {m.extend()}idx := m.hashFunc(key)// 遍历桶,若遇到指定 key ,则更新对应 val 并返回for _, p := range m.buckets[idx] {if p.key == key {p.val = valreturn}}// 若无该 key ,则将键值对添加至尾部p := pair{key: key,val: val,}m.buckets[idx] = append(m.buckets[idx], p)m.size += 1
}/* 删除操作 */
func (m *hashMapChaining) remove(key int) {idx := m.hashFunc(key)// 遍历桶,从中删除键值对for i, p := range m.buckets[idx] {if p.key == key {// 切片删除m.buckets[idx] = append(m.buckets[idx][:i], m.buckets[idx][i+1:]...)m.size -= 1break}}
}/* 扩容哈希表 */
func (m *hashMapChaining) extend() {// 暂存原哈希表tmpBuckets := make([][]pair, len(m.buckets))for i := 0; i < len(m.buckets); i++ {tmpBuckets[i] = make([]pair, len(m.buckets[i]))copy(tmpBuckets[i], m.buckets[i])}// 初始化扩容后的新哈希表m.capacity *= m.extendRatiom.buckets = make([][]pair, m.capacity)for i := 0; i < m.capacity; i++ {m.buckets[i] = make([]pair, 0)}m.size = 0// 将键值对从原哈希表搬运至新哈希表for _, bucket := range tmpBuckets {for _, p := range bucket {m.put(p.key, p.val)}}
}/* 打印哈希表 */
func (m *hashMapChaining) print() {var builder strings.Builderfor _, bucket := range m.buckets {builder.WriteString("[")for _, p := range bucket {builder.WriteString(strconv.Itoa(p.key) + " -> " + p.val + " ")}builder.WriteString("]")fmt.Println(builder.String())builder.Reset()}
}

开放寻址

「开放寻址 open addressing」不引入额外的数据结构,而是通过“多次探测”来处理哈希冲突,探测方式主要包括线性探测、平方探测、多次哈希等。

线性探测

线性探测采用固定步长的线性搜索来进行探测,其操作方法与普通哈希表有所不同。

  • 插入元素:通过哈希函数计算数组索引,若发现桶内已有元素,则从冲突位置向后线性遍历(步长通常为 1 ),直至找到空位,将元素插入其中。
  • 查找元素:若发现哈希冲突,则使用相同步长向后线性遍历,直到找到对应元素,返回 value 即可;如果遇到空位,说明目标键值对不在哈希表中,返回 None 。

下图展示了一个在开放寻址(线性探测)下工作的哈希表。

image-20230908152721558

然而,线性探测存在以下缺陷。

  • 不能直接删除元素。删除元素会在数组内产生一个空位,当查找该空位之后的元素时,该空位可能导致程序误判元素不存在。为此,通常需要借助一个标志位来标记已删除元素。
  • 容易产生聚集。数组内连续被占用位置越长,这些连续位置发生哈希冲突的可能性越大,进一步促使这一位置的聚堆生长,形成恶性循环,最终导致增删查改操作效率劣化。

以下代码实现了一个简单的开放寻址(线性探测)哈希表。

  • 我们使用一个固定的键值对实例 removed 来标记已删除元素。也就是说,当一个桶内的元素为 None 或 removed 时,说明这个桶是空的,可用于放置键值对。
  • 在线性探测时,我们从当前索引 index 向后遍历;而当越过数组尾部时,需要回到头部继续遍历。

Python:

class HashMapOpenAddressing:"""开放寻址哈希表"""def __init__(self):"""构造方法"""self.size = 0  # 键值对数量self.capacity = 4  # 哈希表容量self.load_thres = 2 / 3  # 触发扩容的负载因子阈值self.extend_ratio = 2  # 扩容倍数self.buckets: list[Pair | None] = [None] * self.capacity  # 桶数组self.removed = Pair(-1, "-1")  # 删除标记def hash_func(self, key: int) -> int:"""哈希函数"""return key % self.capacitydef load_factor(self) -> float:"""负载因子"""return self.size / self.capacitydef get(self, key: int) -> str:"""查询操作"""index = self.hash_func(key)# 线性探测,从 index 开始向后遍历for i in range(self.capacity):# 计算桶索引,越过尾部返回头部j = (index + i) % self.capacity# 若遇到空桶,说明无此 key ,则返回 Noneif self.buckets[j] is None:return None# 若遇到指定 key ,则返回对应 valif self.buckets[j].key == key and self.buckets[j] != self.removed:return self.buckets[j].valdef put(self, key: int, val: str):"""添加操作"""# 当负载因子超过阈值时,执行扩容if self.load_factor() > self.load_thres:self.extend()index = self.hash_func(key)# 线性探测,从 index 开始向后遍历for i in range(self.capacity):# 计算桶索引,越过尾部返回头部j = (index + i) % self.capacity# 若遇到空桶、或带有删除标记的桶,则将键值对放入该桶if self.buckets[j] in [None, self.removed]:self.buckets[j] = Pair(key, val)self.size += 1return# 若遇到指定 key ,则更新对应 valif self.buckets[j].key == key:self.buckets[j].val = valreturndef remove(self, key: int):"""删除操作"""index = self.hash_func(key)# 线性探测,从 index 开始向后遍历for i in range(self.capacity):# 计算桶索引,越过尾部返回头部j = (index + i) % self.capacity# 若遇到空桶,说明无此 key ,则直接返回if self.buckets[j] is None:return# 若遇到指定 key ,则标记删除并返回if self.buckets[j].key == key:self.buckets[j] = self.removedself.size -= 1returndef extend(self):"""扩容哈希表"""# 暂存原哈希表buckets_tmp = self.buckets# 初始化扩容后的新哈希表self.capacity *= self.extend_ratioself.buckets = [None] * self.capacityself.size = 0# 将键值对从原哈希表搬运至新哈希表for pair in buckets_tmp:if pair not in [None, self.removed]:self.put(pair.key, pair.val)def print(self):"""打印哈希表"""for pair in self.buckets:if pair is not None:print(pair.key, "->", pair.val)else:print("None")

Go:

/* 链式地址哈希表 */
type hashMapOpenAddressing struct {size        int     // 键值对数量capacity    int     // 哈希表容量loadThres   float64 // 触发扩容的负载因子阈值extendRatio int     // 扩容倍数buckets     []pair  // 桶数组removed     pair    // 删除标记
}/* 构造方法 */
func newHashMapOpenAddressing() *hashMapOpenAddressing {buckets := make([]pair, 4)return &hashMapOpenAddressing{size:        0,capacity:    4,loadThres:   2 / 3.0,extendRatio: 2,buckets:     buckets,removed: pair{key: -1,val: "-1",},}
}/* 哈希函数 */
func (m *hashMapOpenAddressing) hashFunc(key int) int {return key % m.capacity
}/* 负载因子 */
func (m *hashMapOpenAddressing) loadFactor() float64 {return float64(m.size) / float64(m.capacity)
}/* 查询操作 */
func (m *hashMapOpenAddressing) get(key int) string {idx := m.hashFunc(key)// 线性探测,从 index 开始向后遍历for i := 0; i < m.capacity; i++ {// 计算桶索引,越过尾部返回头部j := (idx + 1) % m.capacity// 若遇到空桶,说明无此 key ,则返回 nullif m.buckets[j] == (pair{}) {return ""}// 若遇到指定 key ,则返回对应 valif m.buckets[j].key == key && m.buckets[j] != m.removed {return m.buckets[j].val}}// 若未找到 key 则返回空字符串return ""
}/* 添加操作 */
func (m *hashMapOpenAddressing) put(key int, val string) {// 当负载因子超过阈值时,执行扩容if m.loadFactor() > m.loadThres {m.extend()}idx := m.hashFunc(key)// 线性探测,从 index 开始向后遍历for i := 0; i < m.capacity; i++ {// 计算桶索引,越过尾部返回头部j := (idx + i) % m.capacity// 若遇到空桶、或带有删除标记的桶,则将键值对放入该桶if m.buckets[j] == (pair{}) || m.buckets[j] == m.removed {m.buckets[j] = pair{key: key,val: val,}m.size += 1return}// 若遇到指定 key ,则更新对应 valif m.buckets[j].key == key {m.buckets[j].val = val}}
}/* 删除操作 */
func (m *hashMapOpenAddressing) remove(key int) {idx := m.hashFunc(key)// 遍历桶,从中删除键值对// 线性探测,从 index 开始向后遍历for i := 0; i < m.capacity; i++ {// 计算桶索引,越过尾部返回头部j := (idx + 1) % m.capacity// 若遇到空桶,说明无此 key ,则直接返回if m.buckets[j] == (pair{}) {return}// 若遇到指定 key ,则标记删除并返回if m.buckets[j].key == key {m.buckets[j] = m.removedm.size -= 1}}
}/* 扩容哈希表 */
func (m *hashMapOpenAddressing) extend() {// 暂存原哈希表tmpBuckets := make([]pair, len(m.buckets))copy(tmpBuckets, m.buckets)// 初始化扩容后的新哈希表m.capacity *= m.extendRatiom.buckets = make([]pair, m.capacity)m.size = 0// 将键值对从原哈希表搬运至新哈希表for _, p := range tmpBuckets {if p != (pair{}) && p != m.removed {m.put(p.key, p.val)}}
}/* 打印哈希表 */
func (m *hashMapOpenAddressing) print() {for _, p := range m.buckets {if p != (pair{}) {fmt.Println(strconv.Itoa(p.key) + " -> " + p.val)} else {fmt.Println("nil")}}
}

多次哈希

顾名思义,多次哈希方法是使用多个哈希函数 f1(x)、f2(x)、f3(x)、… 进行探测。

  • 插入元素:若哈希函数 f1(x) 出现冲突,则尝试 f2(x) ,以此类推,直到找到空位后插入元素。
  • 查找元素:在相同的哈希函数顺序下进行查找,直到找到目标元素时返回;或遇到空位或已尝试所有哈希函数,说明哈希表中不存在该元素,则返回 None 。

与线性探测相比,多次哈希方法不易产生聚集,但多个哈希函数会增加额外的计算量。

Python 采用开放寻址。字典 dict 使用伪随机数进行探测。

Golang 采用链式地址。Go 规定每个桶最多存储 8 个键值对,超出容量则连接一个溢出桶;当溢出桶过多时,会执行一次特殊的等量扩容操作,以确保性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/73090.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

K210-CanMV IDE开发软件

K210-CanMV IDE开发软件 界面功能简介连接设备临时运行开机运行程序 界面功能简介 区域①菜单栏&#xff1a;操作文件&#xff0c;使用工具等。 区域②快捷按钮&#xff1a;区域①中的文件和编辑中部分功能的快捷方式。 区域③连接设备&#xff1a;连接设备和程序控制按钮。 …

2023-9-8 求组合数(二)

题目链接&#xff1a;求组合数 II #include <iostream> #include <algorithm>using namespace std;typedef long long LL; const int mod 1e9 7; const int N 100010;// 阶乘&#xff0c;阶乘的逆 int fact[N], infact[N];LL qmi(int a, int k, int p) {int res…

基于单片机压力传感器MPX4115检测-报警系统-proteus仿真-源程序

一、系统方案 本设计采用52单片机作为主控器&#xff0c;液晶1602显示&#xff0c;MPX4115检测压力&#xff0c;按键设置报警&#xff0c;LED报警。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 /***************************************…

分布式秒杀方案--java

前提&#xff1a;先把商品详情和秒杀商品缓存redis中&#xff0c;减少对数据库的访问&#xff08;可使用定时任务&#xff09; 秒杀商品无非就是那几步&#xff08;前面还可能会有一些判断&#xff0c;如用户是否登录&#xff0c;一人一单&#xff0c;秒杀时间验证等&#xff0…

OpenCV 03(数据结构--Mat)

一、Mat介绍 Mat是OpenCV在C语言中用来表示图像数据的一种数据结构.在python中转化为numpy的ndarray. Mat由header和data组成, header中记录了图片的维数, 大小, 数据类型等数据. 1.1 Mat拷贝 - Mat共享数据 在python中Mat数据对应numpy的ndarray, 使用numpy提供的深浅拷贝方…

C++——类与对象(下篇)

前言 前面已经介绍了类与对象&#xff08;上&#xff09;&#xff0c;类与对象&#xff08;中&#xff09;的两篇文章&#xff0c;下面是类与对象的最后一些重要知识点的介绍和总结。 目录 再谈构造函数Static成员友元内部类匿名对象拷贝对象时的一些编译器优化再次理解封装…

使用wkhtmltoimage实现生成长图分享

需求 用户可以选择以长图的形式分享本网页 方法 wkhtmltopdf wkhtmltopdf url filewkhtmltoimage url file java Runtime.getRuntime().exec() 下载 直接去官网下载对应的版本&#xff1a;官网 命令行使用WK > wkhtmltopdf https://www.nowcoder.com /opt/project/…

redis高可用——主从复制、哨兵模式、cluster集群

1、redis群集有三种模式 分别是主从同步/复制、哨兵模式、Cluster&#xff0c;下面会讲解一下三种模式的工作方式&#xff0c;以及如何搭建cIustr群集 主从复制:主从复制是高可用Redis的基础&#xff0c;哨兵和集群都是在主从复制基础上实现高可用的。主从复制主要实现了数据的…

功能测试常用的测试用例大全

登录、添加、删除、查询模块是我们经常遇到的&#xff0c;这些模块的测试点该如何考虑 1)登录 ① 用户名和密码都符合要求(格式上的要求) ② 用户名和密码都不符合要求(格式上的要求) ③ 用户名符合要求&#xff0c;密码不符合要求(格式上的要求) ④ 密码符合要求&#xff0c;…

WireShark抓包工具的安装

1.下载安装包 在官网或者电脑应用商城都可以下载 2.安装 打开安装包&#xff0c;点击next 点击next 选择UI界面&#xff0c;两种都装上 根据习惯选择 选择安装位置点击安装 开始安装安装成功

CMake+CLion+Qt配置

在这里我下载MSVC的工具包&#xff0c;并没有下载Visual Studio。 配置编译环境 下载Visual Studio&#xff0c;其中有MSVC编译工具&#xff0c;下载MSVC工具包&#xff0c; 工具包下载链接&#xff1a;https://visualstudio.microsoft.com/zh-hans/visual-cpp-build-tools/ …

SQL Server 跨库/服务器查询

这里写目录标题 1 SQL Server 跨库/服务器查询1.1 跨库查询1.2 跨服务器查询1.2.1 创建链接服务器1.2.2 跨库查询 1.3 拓展&#xff1a;SQL Server 中所有权和用户与架构的分离 1 SQL Server 跨库/服务器查询 1.1 跨库查询 在同一服务器下的跨库查询较为简单&#xff0c;示例…

macOS Ventura 13.5.2(22G91)发布,附黑/白苹果镜像下载地址

系统介绍&#xff08;下载请百度搜索&#xff1a;黑果魏叔&#xff09; 黑果魏叔 9 月 8 日消息&#xff0c;苹果今日向 Mac 电脑用户推送了 macOS 13.5.2 更新&#xff08;内部版本号&#xff1a;22G91&#xff09;&#xff0c;本次更新距离上次发布隔了 21 天。 本次更新查…

ABY2.0:更低的通信开销

参考文献&#xff1a; [ABY] Demmler D, Schneider T, Zohner M. ABY-A framework for efficient mixed-protocol secure two-party computation[C]//NDSS. 2015.[ABY3] Mohassel P, Rindal P. ABY3: A mixed protocol framework for machine learning[C]//Proceedings of the…

【数据结构初阶】二、 线性表里的顺序表

相关代码gitee自取&#xff1a; C语言学习日记: 加油努力 (gitee.com) 接上期&#xff1a; 【数据结构初阶】一. 复杂度讲解_高高的胖子的博客-CSDN博客 1 . 线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实…

WMS仓储管理系统的功能与WCS系统有什么区别

在物流行业的现代化管理中&#xff0c;WMS仓储管理系统和WCS仓库控制系统是两个重要的组成部分。虽然它们都是用于仓库管理的软件系统&#xff0c;但是它们的功能和应用场景有很大的区别。本文我们将详细阐述这两者的功能和区别。 一、WMS仓储管理系统 WMS是一种软件系统&…

科技资讯|苹果Vision Pro获得被动冷却系统及数字表冠控制界面专利

据patentlyapple报道&#xff0c;美国专利商标局正式授予苹果一项与头戴式设备&#xff08;Apple Vision Pro&#xff09;相关的专利11751366&#xff0c;该设备可以提供被动冷却系统&#xff0c;利用光学组件的表面来管理热量&#xff0c;而不会对用户显示的视觉信息产生不利影…

计算机竞赛 基于深度学习的动物识别 - 卷积神经网络 机器视觉 图像识别

文章目录 0 前言1 背景2 算法原理2.1 动物识别方法概况2.2 常用的网络模型2.2.1 B-CNN2.2.2 SSD 3 SSD动物目标检测流程4 实现效果5 部分相关代码5.1 数据预处理5.2 构建卷积神经网络5.3 tensorflow计算图可视化5.4 网络模型训练5.5 对猫狗图像进行2分类 6 最后 0 前言 &#…

[C++]杨辉三角

目录 题目 解题思路 代码实现 获取数字 打印函数 主函数 全部代码 运行结果 题目 给定一个非负整数numRows &#xff0c;生成「杨辉三角」的前numRows行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 解题思路 第k列的第i个数字的值第k-1列的(…

鸿鹄工程项目管理系统em Spring Cloud+Spring Boot+前后端分离构建工程项目管理系统

Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&#xff09;项目显示…