哈希技术解析:从哈希函数到哈希桶迭代器的全面指南

文章目录

  • 引言
  • 一、哈希表与哈希函数
    • 1、哈希表的基本原理
    • 2、哈希函数的作用与特点
    • 3、哈希冲突的处理方法
  • 二、哈希桶及其迭代器
    • 1、 哈希桶
      • a.定义哈希桶结构
      • b.哈希函数
      • c.哈希桶的插入、查找、删除
    • 2、 哈希桶的迭代器
      • a.类型定义与成员变量
      • b.构造函数
      • c.解引用与比较操作
      • d.递增操作
  • 三、总结

引言

哈希表(Hash Table)是一种非常重要的数据结构,它基于哈希函数将键(Key)映射到值(Value)上,从而实现对数据的快速存储、查找和删除。在哈希表中,数据并不是按顺序存储的,而是根据哈希函数计算出的键的哈希值,确定数据在表中的位置。由于哈希函数的设计使得键与位置之间存在直接对应关系,因此哈希表在查找、插入和删除操作上的平均时间复杂度可以达到O(1),即常数时间复杂度。这使得哈希表在处理大规模数据时具有极高的效率。

在C++标准库中,unordered_mapunordered_set是两种基于哈希表实现的关联容器。它们利用哈希表的优势,提供了快速的数据访问能力。unordered_map存储的是键值对(Key-Value Pair),允许我们根据键快速查找、插入或删除对应的值。而unordered_set则只存储键,用于快速判断某个元素是否存在于集合中。我们将在下一篇文章根据本文内容介绍unordered_mapunordered_set

unordered系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构。哈希是一种查找的方法,不是数据结构。


一、哈希表与哈希函数

哈希表与哈希函数是计算机科学中非常重要的概念,它们在数据结构的构建和算法优化中发挥着关键作用。以下是关于哈希表的基本原理、哈希函数的作用与特点,以及哈希冲突处理方法的详细介绍。

1、哈希表的基本原理

哈希表,又称为散列表,是一种根据关键码值(key-value)直接进行访问的数据结构。它通过一个哈希函数将键映射到表中的位置,以便快速查找、插入和删除元素。哈希表的主要优势在于其高效的查找性能,理想情况下,其查找时间复杂度可以达到O(1),即常数时间复杂度。

在哈希表中,每个键都唯一对应一个哈希值,这个哈希值就是该键在表中的位置。当需要插入一个新元素时,通过哈希函数计算键的哈希值,然后将元素存储在对应的位置。当需要查找一个元素时,同样通过哈希函数计算键的哈希值,然后直接定位到表中的位置进行查找。

2、哈希函数的作用与特点

哈希函数是哈希表的核心,它负责将输入的键转换为哈希值。哈希函数的主要作用是将任意长度的数据映射为固定长度的哈希值,这个哈希值在哈希表中用作元素的索引。

哈希函数具有以下特点:

  1. 一致性:相同的输入键总是产生相同的哈希值。
  2. 高效性:计算哈希值的过程应该是高效的,以便快速定位元素。
  3. 雪崩效应:输入键的微小变化应该导致哈希值的显著变化,这有助于减少哈希冲突的可能性。
  4. 抗碰撞性:极难找到两个不同的输入键产生相同的哈希值,即哈希冲突的概率应该很低。

常见的哈希函数有:

  1. 直接定址法

    • 这种方法通过取关键字的线性函数作为散列地址,计算简单且结果分布均匀。
    • 假设我们要为一组学生的学号分配哈希地址,学生的学号范围是1到100。我们可以使用直接定址法,哈希函数为 Hash(key) = key。例如,学生学号为23,则哈希地址为23。
    • 但其缺点是需要事先知道关键字的分布情况,且数据范围不能相差太大,因此适用于数据范围较小且连续的情况。
  2. 除留余数法

    • 这种方法通过取关键字除以一个质数p的余数作为哈希地址,其中p不大于散列表中允许的地址数m。
    • 假设我们有一个长度为10的哈希表,我们希望用除留余数法为0到99之间的数字分配哈希地址。选择一个小于等于10的质数,比如7,作为除数。哈希函数为 Hash(key) = key % 7。例如,数字23的哈希地址是 23 % 7 = 2。
    • 除留余数法可以管理范围相差较大的数据,是实际应用中非常常用的哈希函数构造方法。
  3. 平方取中法

    • 平方取中法适用于事先不知道关键字的分布,且关键字的位数不是很大的情况。假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址
    • 假设我们要为一组四位数分配哈希地址,我们可以使用平方取中法。哈希函数为取数字平方的中间三位。例如,数字1234的平方是1522756,中间三位是227,所以1234的哈希地址是227。
    • 通过取关键字平方后的中间几位作为哈希地址,可以使得分布更加均匀,减少冲突。
  4. 折叠法

    • 折叠法适用于关键字位数较多,且事先不需要知道关键字分布的情况。
    • 假设我们要为一组六位数分配哈希地址,哈希表长度为100。我们可以将六位数分割成两部分,每部分三位,然后将这两部分相加,取后两位作为哈希地址。哈希函数为 Hash(key) = (key的前三位 + key的后三位) % 100。例如,数字123456的哈希地址是 (123 + 456) % 100 = 579 % 100 = 79。
    • 通过将关键字分割成若干部分,并将这些部分叠加求和,可以得到一个哈希地址。这种方法在处理长关键字时特别有效。
  5. 随机数法

    • 当关键字的长度不等时,可以采用随机数法。
    • 通过选择一个随机函数,将关键字的随机函数值作为哈希地址。这种方法可以使得哈希地址的分布更加随机,减少冲突的可能性。
  6. 数学分析法

    • 数学分析法是根据关键字的每一位上符号的分布情况来选择哈希地址。
    • 如果某些位上符号分布均匀,那么这些位就可以被选为散列地址。这种方法需要对数据的分布情况有深入的了解。

总的来说,这些哈希函数方法各有其特点和适用场景。在实际应用中,需要根据具体的数据特点和需求来选择合适的哈希函数,以达到最优的哈希效果。同时,也需要注意哈希函数的冲突处理问题,通过合理的冲突解决策略来减少冲突的发生,提高哈希表的性能。

3、哈希冲突的处理方法

尽管哈希函数的设计旨在使哈希冲突的可能性尽可能低,但在实际应用中,哈希冲突仍然可能发生。
解决哈希冲突两种常见的方法是:闭散列和开散列。

  1. 链地址法(Chaining):当两个或多个键的哈希值相同时,将这些键对应的元素存储在一个链表中。链表的头节点存储在哈希表的对应位置。当查找元素时,首先计算键的哈希值,然后遍历对应位置上的链表以找到元素。在 SGI STL 源代码中,称表格内的元素为桶子(bucket),意为:表格内的每个单元,涵盖的不只是个节点,可能是一“桶”节点。即开散列。

    在这里插入图片描述

  2. 开放地址法(Open Addressing):当哈希冲突发生时,尝试在哈希表的其他位置查找一个空闲槽来存储元素。根据具体实现方式的不同,开放地址法又可分为线性探测、二次探测和双重散列等。即闭散列

    • 线性探测:当哈希冲突发生时,顺序检查哈希表中的下一个槽,直到找到一个空闲槽。如果到达尾端,就绕道头部继续寻找。例如:假设 x 是任意整数, tablesize 是数组大小,则 x % tablesize 得到的整数范围在 [ 0, tablesize - 1] 。刚好可以作为数组的索引:
      在这里插入图片描述

      置于元素的删除,只能标记删除记号,实际删除则待表格重新整理时再进行,这是因为表中的每个元素不仅仅表述它自己,也关系到其它元素的排列。

    • 二次探测:当哈希冲突发生时,根据一个二次方程来计算新的槽位置,以减少聚集现象。F(i) = i2 。如果使用哈希函数计算得到的位置是 H 。而该位置已被使用,那么我们一次尝试 H2+ 12 ,H2+ 22,H2+ 32,….,H2+ i2
      在这里插入图片描述

这些方法都有各自的优缺点,需要根据具体的应用场景和需求来选择合适的方法。在实际应用中,链地址法因其实现简单和灵活性而得到广泛应用。然而,在处理大量哈希冲突时,链表的长度可能会变得很长,从而影响查找性能。因此,在设计哈希表时,需要综合考虑哈希函数的选择、哈希表的大小以及冲突处理策略等因素,以实现高效的数据存储和访问。

二、哈希桶及其迭代器

哈希桶(Hash Bucket)是哈希表的一个基本组成单位,用于存储具有相同哈希值的键值对。哈希桶通常与链表或其他数据结构结合使用,以解决哈希冲突。 此部分内容与后面unordered_xxx的实现有关!

1、 哈希桶

a.定义哈希桶结构

  • 确定哈希桶中存储的元素类型:
template<class T>
struct HashNode {HashNode<T>* _next;T _data;HashNode(const T& data) :_next(nullptr), _data(data) {}
};
  • 设计哈希桶的基本结构:
template<class K, class T, class KeyOfT, class Hash >
class HashTable {typedef HashNode<T> Node;template<class K, class T, class KeyOfT, class Hash>friend struct __HTIterator;
public:typedef __HTIterator<K, T, KeyOfT, Hash> iterator;//...
private:vector<Node*> _tables;size_t _n;KeyOfT kot; Hash hs;
};

根据HashNode结构体的定义,这个结构体表示哈希表中每个槽位上的链表中的一个节点。每个HashNode对象包含以下部分:

  • _next:一个指向下一个HashNode的指针。当多个键值对具有相同的哈希值时,它们会被链接到这个链表上,通过_next指针来遍历这些节点。
  • _data:存储实际的键值对信息。这里,_data只存储了值T,而没有直接存储键K。这意味着我们将通过KeyOfT函数对象从值中提取。

HashTable类中,_tables是一个向量(vector),它存储了指向HashNode的指针。每个槽位(即_tables中的一个元素)可以是一个空指针(表示该槽位没有存储任何键值对),或者是一个指向链表头节点的指针(表示该槽位上有一个或多个具有相同哈希值的键值对)。

当向哈希表中插入一个键值对时,哈希函数hs以及提取键的kot会被用来计算键的哈希值,这个哈希值随后被用来确定键值对应该放在_tables的哪个槽位上。如果那个槽位已经有一个链表,新的键值对将被作为一个新的HashNode添加到链表的末尾;如果槽位是空的,则创建一个新的HashNode并放在那里。

查找操作也类似:计算键的哈希值,找到对应的槽位,然后遍历链表来查找具有匹配键的节点。

请注意,由于HashNode只存储了值T而没有直接存储键KHashTable类中的KeyOfT函数对象就变得非常重要了。它负责从值T中提取出键K,以便在插入、查找和删除操作中能够正确地比较键。

b.哈希函数

哈希函数是用于将任意长度的数据映射为固定长度的数值的函数。在哈希表等数据结构中,哈希函数对于性能至关重要,因为它决定了数据如何在哈希表中分布:

template<class K>
struct HashFunc {size_t operator()(const K& key) { return (size_t)key; }
};
template<>
struct HashFunc<string> {size_t operator()(const string& s) {size_t hashi = 0;for (auto& e : s) {hashi += e;hashi *= 31;}return hashi;}
};

两个HashFunc模板特化定义展示了如何为不同类型的键实现哈希函数。

  1. 第一个模板定义是一个通用版本,它将键K直接转换为size_t类型并返回。这适用于那些其数值本身就可以作为哈希值的简单类型,例如整数或浮点数。然而,这种转换对于复杂的类型(如自定义类或字符串)可能并不适用,因为这些类型的值可能不适合直接用作哈希值。

  2. 第二个模板特化是为string类型定义的。它使用了一个常见的字符串哈希算法,即所谓的“Rabin-Karp”哈希算法的一个简化版本。这个算法通过遍历字符串中的每个字符,并累加一个与字符值相关的值(在这里是通过将字符值加到hashi变量中),然后将结果乘以一个常数(在这里是31)。这个算法的目的是生成一个与字符串内容紧密相关的哈希值,同时保持一定的随机性以减少哈希冲突。

这种哈希函数的一个缺点是它可能不是均匀分布的,特别是在处理长字符串时,哈希值可能会快速溢出,导致分布不均匀。此外,如果字符串中包含的字符很多,累加和乘法的结果可能会受到整数溢出的影响,从而导致不可预测的行为。

c.哈希桶的插入、查找、删除

Find 函数:这个函数用于在哈希表中查找具有给定键的节点。

iterator Find(const K& key) {size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];while (cur) {if (kot(cur->_data) == key)return iterator(cur, this);cur = cur->_next;}return iterator(nullptr, this);
}
  • 首先计算键的哈希值,并找到该键应该在的槽位(通过取模操作hs(key) % _tables.size())。
  • 然后,遍历该槽位上的链表,查找具有匹配键的节点。
  • 如果找到匹配的节点,它返回一个指向该节点的迭代器。
  • 如果没有找到匹配的节点,它返回一个指向nullptr的迭代器,表示未找到。

Insert 函数:这个函数用于在哈希表中插入一个新的键值对。

pair<iterator,bool> Insert(const T& data) {iterator it = Find(kot(data));if (it != end())return { it,this };if (_n == _tables.size()) {vector<Node*> newTables(_tables.size() * 2, nullptr);for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur) {Node* next = cur->_next;size_t hashi = hs(kot(cur->_data)) % newTables.size();cur->_next = newTables[hashi];newTables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newTables);}size_t hashi = hs(kot(data)) % _tables.size();Node* newnode = new Node(data);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return { iterator(newnode, this),true };
}
  • 首先,调用Find函数来检查是否已存在具有相同键的节点。
  • 如果找到了匹配的节点,说明键已存在,函数返回一个包含该节点迭代器的pair,并标记为未插入(false)。
  • 如果键不存在,它首先检查是否需要扩展哈希表(即,如果当前存储的键值对数量_n等于哈希表的大小_tables.size())。
    • 如果需要扩展,它创建一个新的、大小是原来两倍的哈希表,并将旧哈希表中的所有节点重新哈希并插入到新哈希表中。
    • 然后,它交换新旧哈希表,使得_tables指向新的、更大的哈希表。
  • 接下来,它计算新键值对的哈希值,创建一个新的Node,并将其插入到正确的槽位上。
  • 最后,它更新已存储的键值对数量_n,并返回一个包含新节点迭代器的pair,并标记为已插入(true)。

Erase 函数:这个函数用于从哈希表中删除具有给定键的节点。

bool Erase(const K& key) {size_t hashi = hs(key) % _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur) {if (kot(cur->_data) == key) {// 删除if (prev)prev->_next = cur->_next;else_tables[hashi] = cur->_next;delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;
}
  • 首先计算键的哈希值,找到正确的槽位。
  • 然后,遍历该槽位上的链表,查找具有匹配键的节点。
  • 如果找到匹配的节点,更新链表以删除该节点(通过调整前一个节点的_next指针),释放该节点的内存,并更新已存储的键值对数量_n
  • 最后,返回true表示删除成功。
  • 如果没有找到匹配的节点,返回false表示删除失败。

上述内容不难理解,不做赘述。


2、 哈希桶的迭代器

哈希桶迭代器用于遍历哈希表中的所有元素。hashtable的迭代器没有后退操作,也没有定义所谓的逆向迭代器。且以哈希桶为底层的unordered_xxx都是单向迭代器。

单向迭代器在大多数情况下已经足够满足哈希桶的遍历需求。哈希桶主要用于存储和快速查找键值对,而单向迭代器能够按顺序遍历桶中的元素,满足基本的遍历需求。如果需要反向遍历或进行更复杂的操作,通常可以在外部逻辑中处理,而不必要求迭代器本身支持这些功能。

a.类型定义与成员变量

template<class K, class T, class KeyOfT, class Hash>
struct  __HTIterator {typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> HT;typedef __HTIterator<K, T, KeyOfT, Hash> Self;Node* _node;HT* _ht;
};
  • 类型定义:
    • Node: 指向HashNode<T>类型,表示哈希表中的一个节点。
    • HT: 指向HashTable<K, T, KeyOfT, Hash>类型,表示整个哈希表。
    • Self: 指向迭代器自身的类型,方便在迭代器内部引用自身。
  • 成员变量:
    • _node: 指向当前哈希节点的指针。
    • _ht: 指向哈希表的指针,用于迭代器内部的操作,比如寻找下一个非空桶。

b.构造函数

  • 构造函数:
    • __HTIterator(Node* node, HT* ht): 接收一个指向节点的指针和一个指向哈希表的指针,用于初始化迭代器。

c.解引用与比较操作

  • 解引用操作:

    T& operator*() { return _node->_data; }
    T* operator->() { return &_node->_data; }
    
    • T& operator*(): 返回当前节点存储的数据的引用。
    • T* operator->(): 返回当前节点存储数据的指针,允许使用箭头操作符访问节点的数据成员。
  • 比较操作:

    bool operator!=(const Self& s) { return _node != s._node; }
    bool operator==(const Self& s) { return _node == s._node; }
    
    • bool operator!=(const Self& s): 比较当前迭代器和另一个迭代器是否不等,通过比较它们的节点指针实现。
    • bool operator==(const Self& s): 比较当前迭代器和另一个迭代器是否相等,不仅比较节点指针,还比较它们所属的哈希表指针,确保它们在同一个哈希表中。

d.递增操作

  • 递增操作:

    Self& operator++() {if (_node->_next) {_node = _node->_next;}else {KeyOfT kot;Hash hs;size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();hashi++;while (hashi < _ht->_tables.size()) {if (_ht->_tables[hashi]) {_node = _ht->_tables[hashi];break;}hashi++;}if (hashi == _ht->_tables.size()) {_node = nullptr;}}return *this;
    }
    
    • Self& operator++(): 实现迭代器的递增功能,即移动到下一个节点。
      • 如果当前节点有下一个节点(即_node->_next不为空),则迭代器直接移动到下一个节点。
      • 如果没有下一个节点,迭代器需要找到下一个非空桶。这里,它首先计算当前节点数据的哈希值,并尝试找到下一个哈希值对应的桶。然后,它遍历哈希表,直到找到一个非空桶,或者遍历完整个哈希表。如果遍历完整个哈希表都没有找到非空桶,则将_node设置为nullptr,表示迭代器已经到达哈希表的末尾。

三、总结

这个迭代器设计适用于基于哈希表的关联容器,能够按照桶的顺序遍历元素。当桶内部存在链表来处理哈希冲突时,迭代器能够正确地在链表内部进行遍历。

  • 哈希函数与键提取函数的局部声明:在operator++中,通常,KeyOfTHash这些函数作为模板参数传递给哈希表的,并在哈希表内部使用。
  • 哈希值的计算KeyOfT提取了键,哈希函数Hash通过提取出来的键来计算哈希值。
  • 迭代器访问哈希桶内私有变量:我们通过将迭代器声明为哈希桶类的友元函数。

HashTable模板类中,_tables是一个私有成员变量,它存储了指向HashNode<T>类型对象的指针。这个_tables通常表示哈希表的内部存储结构,也就是所谓的“桶”(buckets),每个桶可能包含一个链表或其他结构来处理哈希冲突。

__HTIterator是一个模板结构体,它作为HashTable的迭代器。由于迭代器需要访问HashTable的私有成员(特别是_tables),通常迭代器会被声明为HashTable的友元(friend)。这样,__HTIterator就可以访问HashTable的私有成员变量,包括_tables,以便能够正确地遍历哈希表中的元素。

HashTable模板类中,通过以下方式声明了__HTIterator为友元:

template<class K, class T, class KeyOfT, class Hash>  
friend struct __HTIterator;

这意味着对于任何HashTable的特定实例,其对应的__HTIterator实例都可以访问该HashTable的私有成员。

iterator__HTIterator<K, T, KeyOfT, Hash>的一个类型别名,定义在HashTable的公共部分。这样,用户可以使用HashTable::iterator来引用迭代器类型,而不需要写出完整的模板实例化。

现在,关于迭代器如何访问哈希桶内的私有变量:

  1. 通过友元关系:由于__HTIteratorHashTable的友元,它可以直接访问_tables等私有成员。
  2. 遍历桶和节点:迭代器内部可能有一个指向当前桶的指针和一个指向当前桶内链表中节点的指针。迭代器通过这些指针来遍历桶内的链表,从而访问哈希表中的元素。
  3. 不直接修改桶结构:尽管迭代器可以访问桶,但它通常不应该直接修改桶的结构(如添加或删除桶)。这些操作应该由HashTable类的成员函数来执行,以确保哈希表的一致性和正确性。
  4. 访问节点数据:迭代器通过访问桶内的节点来访问存储的元素。每个节点通常包含一个指向实际数据的指针或引用,迭代器通过节点的公有接口(如getter方法)来获取这些数据。

下面我给出哈希桶及其迭代器的完整实现:

namespace hash_bucket
{template<class T>struct HashNode {HashNode<T>* _next;T _data;HashNode(const T& data) :_next(nullptr), _data(data) {}};template<class K, class T, class KeyOfT, class Hash >class HashTable;template<class K, class T, class KeyOfT, class Hash>struct  __HTIterator {typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> HT;typedef __HTIterator<K, T, KeyOfT, Hash> Self;Node* _node;HT* _ht;__HTIterator(Node* node, HT* ht) :_node(node), _ht(ht) {}T& operator*() { return _node->_data; }T* operator->() { return &_node->_data; }bool operator!=(const Self& s)const { return _node != s._node; }bool operator==(const Self& s) const { return _node == s._node; }Self& operator++() {if (_node->_next) {_node = _node->_next;}else {KeyOfT kot;Hash hs;size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();hashi++;while (hashi < _ht->_tables.size()) {if (_ht->_tables[hashi]) {_node = _ht->_tables[hashi];break;}hashi++;}if (hashi == _ht->_tables.size()) {_node = nullptr;}}return *this;}};template<class K, class T, class KeyOfT, class Hash >class HashTable {typedef HashNode<T> Node;template<class K, class T, class KeyOfT, class Hash>friend struct __HTIterator;public:typedef __HTIterator<K, T, KeyOfT, Hash> iterator;iterator begin(){for (size_t i = 0; i < _tables.size(); i++)if (_tables[i])return iterator(_tables[i], this);return end();}iterator end() { return iterator(nullptr, this); }HashTable()//:kot(KeyOfT()),hs(Hash()){_tables.resize(10, nullptr);_n = 0;kot = KeyOfT();hs = Hash();} ~HashTable() {for (size_t i = 0; i < _tables.size(); i++) {Node* cur = _tables[i];while (cur) {Node* next = cur->_next;delete cur;cur = next;}_tables[i] = nullptr;}}pair<iterator,bool> Insert(const T& data) {iterator it = Find(kot(data));if (it != end())return { it,this };if (_n == _tables.size()) {vector<Node*> newTables(_tables.size() * 2, nullptr);for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur) {Node* next = cur->_next;size_t hashi = hs(kot(cur->_data)) % newTables.size();cur->_next = newTables[hashi];newTables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newTables);}size_t hashi = hs(kot(data)) % _tables.size();Node* newnode = new Node(data);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return { iterator(newnode, this),true };}bool Erase(const K& key) {size_t hashi = hs(key) % _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur) {if (kot(cur->_data) == key) {// 删除if (prev)prev->_next = cur->_next;else_tables[hashi] = cur->_next;delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;}iterator Find(const K& key) {size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];while (cur) {if (kot(cur->_data) == key)return iterator(cur, this);cur = cur->_next;}return iterator(nullptr, this);}private:vector<Node*> _tables;size_t _n;KeyOfT kot; Hash hs;};
}

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

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

相关文章

Liunx进程间通信

进程间通信 进程间通信进程间通信的基本概念进程间通信的目的 管道匿名管道进程池 命名管道 system V进程间通信system V进程间通信基本概念system V共享内存共享内存和管道的对比 system V 信号量信号量同步和互斥 进程间通信 进程间通信的基本概念 进程间通信就是在不同进程…

6-LINUX-- C 程序的编译与调试

一.环境搭建 1.gcc的安装 1>.切换到管理员模式 sudo su ----> 输入密码 2>.apt install gcc //C语言的编译环境 3>.apt install g //c编译环境的搭建 4>.install update //软件升级 2.gcc分步编译链接 &#xff08;1&#xff09;预编译 gcc -E…

计算机生物科技在基因编辑中的应用及其前景

一、引言 基因编辑&#xff0c;作为一种能够精准修改生物体基因组的技术&#xff0c;近年来受到了广泛的关注。 而计算机生物科技作为连接计算机科学与生物学的桥梁&#xff0c;为基因编辑技术的快速发展提供了强大的支持。通过利用计算机算法和数据分析方法&#xff0c;研究人…

Dashe Media全球新闻稿发布协助您实现传播目标-海外媒体宣发

亚太区新闻稿发布网络 Dashe Media 是唯一一家于亚太区拥有专有记者网络和网上新闻媒体发布网络的全球新闻通讯社 Dashe Media 在该地区的 26 个国家拥有 200,000 名记者和编辑数据库&#xff0c;涵盖 500 个新闻类别、68,000 个新闻媒体和 1,500 个在线新闻媒体合作伙伴&…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:Refresh)

可以进行页面下拉操作并显示刷新动效的容器组件。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 支持单个子组件。 从API version 11开始&#xff0c;Refresh子组件会跟随手势下拉而下移…

HarmonyOS 通知意图

之前的文章 我们讲了 harmonyos 中的 基础和进度条通知 那么 今天 我们来说说 任何给通知添加意图 通知意图 简单说 就是 当我们点击某个通知 如下图 然后 就会拉起某个 应用 就例如说 我们某个微信好友发消息给我们 我们 点击系统通知 可以直接跳到你们的聊天界面 好 回到…

JavaScript中的继承方式详细解析

什么是继承 继承是面向对象编程中的一个重要概念&#xff0c;它指的是一个对象&#xff08;或类&#xff09;可以获得另一个对象&#xff08;或类&#xff09;的属性和方法。在继承中&#xff0c;被继承的对象通常称为父类&#xff08;或基类、超类&#xff09;&#xff0c;继…

(css)vue 自定义背景 can‘t resolve

(css)vue 自定义背景 can’t resolve 旧写法&#xff1a; background-image: url(/assets/images/step-bg.jpg);background-size: 100% 100%; 新写法&#xff1a; background-image: url(~/assets/images/step-bg.jpg);background-size: 100% 100%; 解决参考&#xff1a;https…

【RabbitMQ | 第七篇】RabbitMQ实现JSON、Map格式数据的发送与接收

文章目录 7.RabbitMQ实现JSON、Map格式数据的发送与接收7.1消息发送端7.1.1引入依赖7.1.2yml配置7.1.3RabbitMQConfig配置类——&#xff08;非常重要&#xff09;&#xff08;1&#xff09;创建交换器方法&#xff08;2&#xff09;创建队列方法&#xff08;3&#xff09;绑定…

代码随想录算法训练营第27天|93.复原IP地址、78.子集、90.子集二

目录 一、力扣93.复原IP地址1.1 题目1.2 思路1.3 代码1.4 总结 二、力扣78.子集2.1 题目2.2 思路2.3 代码2.4 总结 三、力扣90.子集二3.1 题目3.2 思路3.3 代码3.4 总结 一、力扣93.复原IP地址 &#xff08;比较困难&#xff0c;做起来很吃力&#xff09; 1.1 题目 1.2 思路 …

【数据结构练习题】栈——1.括号匹配 2.逆波兰表达式求值 3.出栈入栈次序匹配 4.最小栈

♥♥♥♥♥个人主页♥♥♥♥♥ ♥♥♥♥♥数据结构练习题总结专栏♥♥♥♥♥ 文件目录 前言1.括号匹配1.1问题描述1.2解题思路1.3画图解释1.4代码实现2.逆波兰表达式求值 2.1问题描述2.2解题思路2.3画图解释2.4代码解释3.出栈入栈次序匹配 3.1问题描述3.2思路分析3.3画图解释3.…

【No.13】蓝桥杯二分查找|整数二分|实数二分|跳石头|M次方根|分巧克力(C++)

二分查找算法 知识点 二分查找原理讲解在单调递增序列 a 中查找 x 或 x 的后继在单调递增序列 a 中查找 x 或 x 的前驱 二分查找算法讲解 枚举查找即顺序查找&#xff0c; 实现原理是逐个比较数组 a[0:n-1] 中的元素&#xff0c;直到找到元素 x 或搜索整个数组后确定 x 不在…

CPU设计实战—异常处理指令

异常类型以及精确异常的处理 异常有点像中断&#xff0c;处理完还要回到原来的状态&#xff0c;所以需要对之前的状态进行保存。本CPU主要实现对以下异常的处理&#xff1a; 1.外部硬件中断 2.复位异常 3.系统调用异常&#xff08;发生在译码阶段&#xff09; 4.溢出异常&…

做好外贸网站SEO优化,拓展海外市场

随着全球贸易的发展和互联网的普及&#xff0c;越来越多的外贸企业将目光投向了网络&#xff0c;希望通过建立网站来拓展海外市场。然而&#xff0c;在竞争激烈的外贸市场中&#xff0c;要让自己的网站脱颖而出&#xff0c;吸引更多的目标客户&#xff0c;就需要进行有效的SEO优…

openGauss学习笔记-246 openGauss性能调优-SQL调优-经验总结:SQL语句改写规则

文章目录 openGauss学习笔记-246 openGauss性能调优-SQL调优-经验总结&#xff1a;SQL语句改写规则246.1 使用union all代替union246.2 join列增加非空过滤条件246.3 not in转not exists246.4 选择hashagg246.5 尝试将函数替换为case语句246.6 避免对索引使用函数或表达式运算2…

面试算法-50-二叉树的最大深度

题目 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3 解 class Solution {public int maxDepth(TreeNo…

《算法设计与分析第二版》100行 C语言实现 广度度优先算法 BFS——最短距离

抄录自课本P157页。 #include <stdio.h> #define MAXQ 100 // 队列大小 #define MAxN 10 // 最大迷宫大小 int n8; // 迷宫大小 char Maze [MAxN][MAxN] {{O,X,X,X,X,X,X,X,},{O,O,O,X,O,X,O,X,},{X,X,O,O,O,X,O,X,},{X,X,O,X,O,X,X,X,},…

HDFS概述及常用shell操作

HDFS 一、HDFS概述1.1 HDFS适用场景1.2 HDFS优缺点1.3 HDFS文件块大小 二、HDFS的shell操作2.1 上传2.2 下载2.3 HDFS直接操作 一、HDFS概述 1.1 HDFS适用场景 因为HDFS里所有的文件都是维护在磁盘里的 在磁盘中对文件的历史内容进行修改 效率极其低(但是追加可以) 1.2 HDF…

走近 AI Infra 架构师:在高速飞驰的大模型“赛车”上“换轮子”的人

如果把大模型训练比作 F1 比赛&#xff0c;长凡所在的团队就是造车的人&#xff0c;也是在比赛现场给赛车换轮子的人。1% 的训练提速&#xff0c;或者几秒之差的故障恢复时间&#xff0c;累积起来&#xff0c;都能影响到几百万的成本。长凡说&#xff1a;“大模型起来的时候&am…

算法详解——选择排序和冒泡排序

一、选择排序 选择排序算法的执行过程是这样的&#xff1a;首先&#xff0c;算法遍历整个列表以确定最小的元素&#xff0c;接着&#xff0c;这个最小的元素被置换到列表的开头&#xff0c;确保它被放置在其应有的有序位置上。接下来&#xff0c;从列表的第二个元素开始&#x…