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

文章目录

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

哈希表

哈希表,又称散列表,其通过建立键 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.rhkb.cn/news/125661.html

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

相关文章

excel功能区(ribbonx)编程笔记--3 editbox与状态按钮togglebutton控件

从上次发布编程笔记2后,反响还不错,短短一个星期,访问量就达到了1500,说明虽然这个只是有写古老,但是再实际的工作中,excel的编程功能还是有或多人关注的,还不是很小众,比如我就是平时的统计就是使用excle,为了更好的实现自动统计,会添加部分vba代码到里面,就像我的…

Server - PyTorch BFloat16 “TypeError: Got unsupported ScalarType BFloat16“ 解决方案

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/132665807 BFloat16 类型是 16 位的浮点数格式&#xff0c;可以用来加速深度学习的计算和存储。BFloat16 类型的特点是保留 32 位浮点数&#xff…

Activiti7工作流引擎:在线流程编辑器Activiti Modoler5.x

一&#xff1a;简介 有的时候我们的流程图需要业务人员自己绘制&#xff0c;然后使用自己绘制的流程图&#xff0c;此时就需要一个在线流程图编辑器需要集成到我们的web系统中。Activiti Modoler是Activiti官方推出的在线流程编辑器。 二&#xff1a;pom.xml <dependency…

07-Spring Cloud

1、如何设计一个注册中心&#xff1f; 高可用&#xff1a;通过集群的方式 高并发&#xff1a;减少响应时间、提高吞吐量 并发用户数等&#xff0c;通过增加服务器性能、 扩展服务实例的方式 高性能&#xff1a;程序处理速度 考虑 数据存储结构、通信机制、集群同步。 集群…

使用内网负载机(Linux)执行Jmeter性能测试

一、背景 ​ 在我们工作中有时候会需要使用客户提供的内网负载机进行性能测试&#xff0c;一般在什么情况下我们需要要求客户提供内网负载机进行性能测试呢&#xff1f; 遇到公网环境下性能测试达到了带宽瓶颈。那么这时&#xff0c;我们就需要考虑在内网环境负载机下来执行我们…

【数据结构】树的基础入门

文章目录 什么是树树的常见术语树的表示树的应用 什么是树 相信大家刚学数据结构的时候最先接触的就是顺序表,栈,队列等线性结构. 而树则是一种非线性存储结构,存储的是具有“一对多”关系的数据元素的集合 非线性 体现在它是由n个有限结点(可以是零个结点)组成一个具有层次关…

修改Docker镜像默认下载地址

1、安装完docker desktop后&#xff0c;先不要打开 2、新建目录 D:\ProgramData\Docker 3、在C:\Users\你的用户名\AppData\Local下&#xff0c;打开cmd或者powershell执行以下命令&#xff0c;命令语法略有不同。 powershell命令&#xff1a; cmd /c mklink /J Docker D:\Pro…

团队高效协作有多重要?介绍一些优秀的团队协作工具

不论企业大小&#xff0c;团队协作对企业来说是至关重要的&#xff0c;它可以对业务运营和组织效率产生积极影响。 当团队成员能够协同工作、分享信息和资源时&#xff0c;工作流程更加顺畅&#xff0c;决策更加快速且准确。分工合作和共享知识可以减少重复劳动&#xff0c;提…

Vision Transformer(VIT 网络架构)

论文下载链接&#xff1a;https://arxiv.org/abs/2010.11929 文章目录 引言1. VIT与传统CNN的比较2. 为什么需要Transformer在图像任务中&#xff1f; 1. 深入Transformer1.1 Transformer的起源&#xff1a;NLP领域的突破1.2 Transformer的基本组成1.2.1 自注意机制 (Self-Atte…

docker-compose deploy 高可用 elasticsearch TLS

文章目录 1.sysctl2. swap3. hosts4. 配置 instances.yaml5. 创建证书6. 部署7. 修改 kibanna 密码8. 清理 1.sysctl [rootgithub es_tls]# cat /etc/sysctl.conf # sysctl settings are defined through files in # /usr/lib/sysctl.d/, /run/sysctl.d/, and /etc/sysctl.d/…

1DM+下载器_11.2.1魔改增强版下载

1DM「原&#xff1a;IDM」下载器是一款安卓端的下载工具&#xff0c;多语言解锁版直安装版本&#xff0c;号称是目前 Android 平台最快、最先进的下载管理器应用「支持通过Torrent下载」&#xff0c;而这个版本是改线程的最新idm版本&#xff0c;可用来下载视频、音乐、电影、T…

【已更新建模代码】2023数学建模国赛B题matlab代码--多波束测线问题

一、 问题重述 1.1问题背景 海洋测深是测定水体深度与海底地形的重要任务&#xff0c;有两种主要技术&#xff1a;单波束测 深与多波束测深。单波束适用于简单任务&#xff0c;但多波束可提供更精确的地形数据。多 波束系统的关键在于覆盖宽度与重叠率的设计&#xff0c;以确保…

golang教程 beego框架笔记一

安装beego 安装bee工具 beego文档 # windos 推荐使用 go install github.com/beego/bee/v2master go get -u github.com/beego/bee/v2masterwindows使用安装bee工具时碰到的问题&#xff1b; 环境配置都没有问题&#xff0c;但是执行官网的命令&#xff1a;go get -u github…

科技云报道:生成式AI已成为企业新兴风险,但我们不应该因噎废食

科技云报道原创。 2023年&#xff0c;生成式AI技术破茧成蝶&#xff0c;引发了一场全球范围的数字革命。 从最初的聊天、下棋开始&#xff0c;到医疗、金融、制造、教育、科研等&#xff0c;生成式AI表现出了强大的创造力和无限潜力。据不完全统计&#xff0c;截至今年8月底&…

阿里云云主机免费试用三个月

试用链接如下&#xff1a; 阿里云云产品免费试用 云主机 费用试用三个月&#xff0c;每月750小时 实例规格 1核(vCPU) 2 GiB S6 系列机型 适用搭建网站等场景 网络带宽 1M 公网固定网络带宽 云盘40 GiB 真香&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&…

Python数据分析实战-依次遍历dataframe每一行,对某字段进行分析处理并新增一列(附源码和实现效果)

实现功能 依次遍历每一行&#xff0c;在某列包含某个元素时新增一列进行标记 实现代码 def province_distribution_of_colleges(self, file):df pd.read_excel(os.path.join(self.datapath, file))df1 dfhua_bei [北京市,天津市,河北省,山西省,内蒙古自治区]dong_bei [辽…

深入了解vue2没有在data中定义的属性非响应式的问题

关于vue2没有在data中定义的属性非响应式的问题 vue2 响应式的原理及实现vue2 解决此类的部分 vue2 响应式的原理及实现 vue2 响应式数据 是通过 es5 中的 Object.defineProperty 方法来实现&#xff0c;把 data 定义的所有属性&#xff0c;转换为 get/set 方法&#xff0c;使…

如何使用HTTP代理爬虫,防止对网站造成负面影响

在当今大数据时代&#xff0c;爬虫技术已经成为了获取数据的重要手段之一。但是&#xff0c;由于爬虫程序的高频访问容易对目标网站造成负面影响&#xff0c;如增加服务器负载、影响网站性能等&#xff0c;因此&#xff0c;如何使用HTTP代理爬虫防止对网站造成负面影响成为了一…

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

题目链接&#xff1a;求组合数 I #include <iostream> #include <algorithm>using namespace std;const int mod 1e9 7;int n; const int N 2010; int c[N][N];void init() {for(int i 0; i < N; i )for(int j 0; j < i; j)if(!j) c[i][j] 1;else c[i]…

Spring系列文章1:Spring入门程序

一、什么是spring 一个java框架、java语言开发&#xff0c;轻量级、开源框架、在j2se、j2ee中都可以使用。它是一个管理对象的容器&#xff0c;Spring 容器不装文本&#xff0c;数字。装的是java对象。 核心技术&#xff1a;ioc、aop 官网地址 https://spring.io 项目列表…