穿越数据迷宫:C++哈希表的奇幻旅程

在这里插入图片描述

文章目录

  • 前言
    • 📔一、unordered系列关联式容器
      • 📕1.1 `unordered` 容器概述
      • 📕1.2 哈希表在 `unordered` 容器中的实现原理
      • 📕1.3 `unordered` 容器的特点
    • 📔二、`unordered_set ` 和 `unordered_map` 的基本操作
      • 📕2.1 `unordered_set` 的基本用法
        • 📖1. 创建和初始化
        • 📖2. 插入元素
        • 📖3. 查找元素
        • 📖4. 删除元素
        • 📖5. 大小和清空
      • 📕2.2 `unordered_map` 的基本用法
        • 📖1. 创建和初始化
        • 📖2. 插入元素
        • 📖3. 查找元素
        • 📖4. 删除元素
        • 📖5. 大小和清空
    • 📔三、哈希表
      • 📕3.1 哈希表的基本原理
      • 📕3.2 哈希函数
      • 📕3.3 解决冲突的几种办法
        • 📖1. 开放地址法(Open Addressing)
          • 📄a. 线性探测(Linear Probing)
          • 📄b. 二次探测(Quadratic Probing)
          • 📄c. 双重哈希(Double Hashing)
        • 📖2. 拉链法(Chaining)
    • 📔四、哈希表的模拟实现
      • 📕4.1 开放地址法(线性探测)
        • 📖1. `DefaultHashFunc` 哈希函数类
        • 📖2. `HashData` 类
        • 📖3. `HashTable` 类
        • 📖4. 私有成员
      • 🏷️总结
      • 📕4.2 拉链法(哈希桶)
        • 📖1. `HashNode` 类
        • 📖2. `HashTable` 类
          • 📄成员变量
          • 📄构造函数和析构函数
          • 📄插入操作 `Insert`
          • 📄查找操作 `Find`
          • 📄删除操作 `Erase`
          • 📄打印操作 `Print`
        • 🏷️总结
  • 结语


前言

在C++的世界中,哈希表是一种高效、独特的数据结构,被广泛应用于需要快速查找、插入、删除的场景。通过哈希函数将数据映射到表中,它不仅提高了操作效率,还在解决冲突时展现了精巧的设计。哈希表在算法的应用中尤为重要,无论是缓存、字典处理还是集合操作,都离不开它的身影。本篇博客将带你一步步深入理解哈希表的实现原理及其应用,让你在编码之路上更上一层楼。


📔一、unordered系列关联式容器

在 C++ 标准库中,unordered 系列容器(如 unordered_mapunordered_set)是一类基于哈希表实现的关联式容器。与传统的 mapset 容器不同,unordered 容器通过哈希函数实现了无序存储,能够在均摊 O ( 1 ) O(1) O(1) 时间复杂度内完成插入、查找和删除操作。这使得 unordered 系列容器在处理需要快速查找的场景中非常高效。

📕1.1 unordered 容器概述

unordered 容器包含以下几种类型,主要用于不同类型的键值对或集合操作:

  1. unordered_set:一个不包含重复元素的集合,存储的是唯一的键。
  2. unordered_multiset:与 unordered_set 相似,但允许包含重复元素。
  3. unordered_map:一个键值对的容器,每个键只能出现一次,键是唯一的。
  4. unordered_multimap:一个键值对的容器,允许键重复。

与传统的有序容器(如 mapset)不同,unordered 容器中的元素没有特定的顺序。因此,unordered_mapunordered_set 更加适用于不关心元素顺序、仅关注快速访问的场景。

📕1.2 哈希表在 unordered 容器中的实现原理

unordered 容器的核心数据结构是哈希表,它利用哈希函数将键映射到表中的位置。在哈希表中,unordered 容器采用了一种**桶(bucket)**的机制来存储数据:

  • 哈希函数:将键值映射到特定的哈希值,这个哈希值会决定键值对存储的位置(即桶)。
  • :哈希表中的每个位置称为一个桶,键值对根据哈希值分布在不同的桶中。
  • 冲突处理:当多个键映射到同一个桶时,使用链表(或其他方法)来解决冲突。这种冲突解决方法通常称为拉链法

📕1.3 unordered 容器的特点

  • 均摊时间复杂度:在理想的情况下,插入、查找和删除的平均时间复杂度为 O ( 1 ) O(1) O(1)
  • 无序性unordered 容器不维护元素的顺序,元素顺序与插入顺序无关。
  • 哈希函数与相等比较器:可以指定自定义的哈希函数和比较器,适用于自定义类型和不同的哈希策略。

📔二、unordered_set unordered_map 的基本操作

📕2.1 unordered_set 的基本用法

unordered_set 是一个集合,用于存储唯一的元素,元素的顺序是无序的。主要用在需要快速查找元素的场景中。

📖1. 创建和初始化
#include <unordered_set>
#include <iostream>

int main() {
    std::unordered_set<int> uset = {1, 2, 3, 4, 5};  // 初始化

    // 遍历输出元素
    for (const int& num : uset) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    return 0;
}
📖2. 插入元素

使用 insert 方法插入元素。如果元素已存在,insert 不会插入重复元素。

uset.insert(6);   // 插入元素6
uset.insert(3);   // 3已存在,不插入
📖3. 查找元素

使用 find 方法查找元素,若找到则返回元素的迭代器,否则返回 end()

if (uset.find(3) != uset.end()) {
    std::cout << "3 存在于集合中" << std::endl;
}
📖4. 删除元素

使用 erase 方法删除指定元素,可以传入元素值或迭代器。

uset.erase(4);  // 删除元素4
📖5. 大小和清空
  • size() 获取集合的大小。
  • clear() 清空集合中的所有元素。
std::cout << "集合大小: " << uset.size() << std::endl;
uset.clear();

📕2.2 unordered_map 的基本用法

unordered_map 是一种键值对关联容器,使用哈希表实现。每个键是唯一的,可以通过键快速访问对应的值。

📖1. 创建和初始化
#include <unordered_map>
#include <iostream>
#include <string>

int main() {
    std::unordered_map<std::string, int> umap = {{"apple", 3}, {"banana", 5}, {"orange", 2}};  // 初始化

    // 遍历键值对
    for (const auto& kv : umap) {
        std::cout << kv.first << " -> " << kv.second << std::endl;
    }
    return 0;
}
📖2. 插入元素

使用 insertoperator[] 插入键值对。operator[] 如果键不存在,会插入新键并初始化值为默认值。

umap["grape"] = 10;  // 插入或更新键为 "grape" 的值
umap.insert({"melon", 8});  // 插入键值对 {"melon", 8}
📖3. 查找元素

使用 find 方法查找元素,若找到则返回指向键值对的迭代器,否则返回 end()

auto it = umap.find("banana");
if (it != umap.end()) {
    std::cout << "香蕉数量: " << it->second << std::endl;
}
📖4. 删除元素

使用 erase 方法删除指定键的元素。

umap.erase("orange");  // 删除键 "orange"
📖5. 大小和清空
  • size() 获取 unordered_map 的键值对数量。
  • clear() 清空所有元素。
std::cout << "键值对数量: " << umap.size() << std::endl;
umap.clear();

📔三、哈希表

哈希表是一种基于哈希函数的数据结构,用于快速存储和查找数据。它的核心思想是将键(Key)通过哈希函数转换为数组索引,从而实现快速的数据访问。哈希表被广泛用于字典、缓存、集合等需要高效查找的场景,是以上所介绍容器的底层结构。

📕3.1 哈希表的基本原理

哈希表包含以下几个核心概念:

  • 哈希函数:将任意类型的键映射到数组中的一个索引位置。
  • 桶(Bucket):哈希表的每个索引位置称为一个桶,存储一个或多个元素。
  • 冲突(Collision):当不同的键被映射到相同的索引时发生冲突。
  • 负载因子(Load Factor):表示哈希表的填充程度,用公式 负载因子 = 元素个数 / 桶的数量 表示。负载因子过高会影响性能,通常哈希表会在负载因子达到一定值时扩容。

哈希表的操作,如插入、删除、查找,在理想情况下的时间复杂度为 O ( 1 ) O(1) O(1)。这是因为哈希表可以通过哈希函数将键快速映射到对应的存储位置。

📕3.2 哈希函数

哈希函数是哈希表性能的核心,其目的是将键均匀地分布在哈希表的桶中,减少冲突的发生。好的哈希函数具有以下特性:

  • 均匀性:不同的键被分布在不同的桶中,避免过多的冲突。
  • 快速计算:哈希函数的计算速度应尽可能快,以提高哈希表操作的整体效率。

在 C++ 中,标准库提供了许多内置类型的哈希函数,如 std::hash<int>std::hash<std::string> 等。此外,用户也可以为自定义类型定义自己的哈希函数。

📕3.3 解决冲突的几种办法

📖1. 开放地址法(Open Addressing)

开放地址法是在哈希表中找一个新的空位来存储冲突元素。当发生冲突时,通过探测寻找下一个可用位置存放新元素。开放地址法常见的探测方式有:

📄a. 线性探测(Linear Probing)

在发生冲突时,线性探测通过固定步长(通常为 1)逐步查找下一个空位,直到找到可用位置。

  • 公式hash(key) + i,其中 i 是冲突次数。
  • 优点:实现简单。
  • 缺点:容易出现“聚集”现象,即相邻的桶很容易连续被填满,降低查找效率。
int linearProbing(int hashValue, int i, int tableSize) {
    return (hashValue + i) % tableSize;
}
📄b. 二次探测(Quadratic Probing)

二次探测通过平方增长的步长来查找下一个位置,避免线性探测的“聚集”问题。

  • 公式hash(key) + i^2
  • 优点:减少了聚集现象。
  • 缺点:在高负载因子下,探测序列可能重复循环导致找不到空位。
int quadraticProbing(int hashValue, int i, int tableSize) {
    return (hashValue + i * i) % tableSize;
}
📄c. 双重哈希(Double Hashing)

双重哈希采用两个哈希函数,在发生冲突时,根据第二个哈希函数的值决定步长。

  • 公式hash1(key) + i * hash2(key)
  • 优点:能有效降低聚集现象,冲突处理更加均匀。
  • 缺点:需要设计两个独立的哈希函数,且对负载因子要求较高。
int doubleHashing(int hashValue, int i, int tableSize, int hash2) {
    return (hashValue + i * hash2) % tableSize;
}
📖2. 拉链法(Chaining)

拉链法将每个哈希表位置(桶)设计为一个链表,所有被哈希到同一位置的元素都存储在该链表中。插入新元素时,将其添加到链表中,查找时则遍历该链表。

  • 优点
    • 实现简单,链表支持动态扩展,不受哈希表容量影响。
    • 插入、删除操作简单,尤其适合不定长数据结构(如链表、树等)。
  • 缺点
    • 冲突严重时,链表可能变长,影响查找效率,最坏情况为 O ( n ) O(n) O(n)
    • 需要额外的链表存储空间。

📔四、哈希表的模拟实现

📕4.1 开放地址法(线性探测)

📖1. DefaultHashFunc 哈希函数类

DefaultHashFunc 是一个通用的哈希函数模板类,用于将键转换为哈希值。代码对整型和字符串类型分别进行了不同的哈希计算:

  • 对于整型或其他非字符串类型,直接将键转换为 size_t 类型。
  • 对于 string 类型,定义了一个专门的特化版本,使用一种常见的哈希算法,将字符串中的每个字符逐步加入到哈希值中,并乘以一个质数(131)来增加分布的均匀性。
template<class K>
class DefaultHashFunc {
public:
    size_t operator()(const K& key) {
        return (size_t)key;
    }
};

template<>
class DefaultHashFunc<string> {
public:
    size_t operator()(const string& str) {
        size_t hash = 0;
        for (auto ch : str) {
            hash *= 131;
            hash += ch;
        }
        return hash;
    }
};
📖2. HashData

HashData 类表示哈希表中的一个数据节点,包含一个键值对 _kv 和一个 STATE 状态 _stateSTATE 枚举类定义了三个状态:

  • EXIST:表示该位置有数据。
  • EMPTY:表示该位置为空。
  • DELETE:表示该位置的数据已被删除。

这种状态管理方便在开放地址法中处理删除操作。

enum STATE {
	EXIST,
	EMPTY,
	DELETE
};

template<class K, class V>
class HashData {
public:
	pair<K, V> _kv;
	STATE _state = EMPTY;
};
📖3. HashTable

HashTable 是哈希表的主体类,支持以下操作:

  • 构造函数:初始化哈希表容量,默认大小为 10。
public:
    HashTable() {
        _table.resize(10);
    }
  • 插入操作 Insert:向哈希表插入一个键值对 kv。如果负载因子达到 0.7,进行扩容操作(即 resize)。
    • 扩容操作:创建一个新的哈希表,将所有旧数据重新插入到新表中。这样可以重新计算哈希值,以确保数据均匀分布。
    • 线性探测:若哈希值对应的桶已经存在数据,使用线性探测法查找下一个空闲位置,直到找到空位。
bool Insert(const pair<K, V>& kv) {
    // 扩容,扩容之后映射关系变了,需要重新映射
    if ((double)_n / _table.size() >= 0.7) {
        size_t  newSize = _table.size() * 2;
        // 遍历旧表,重新映射到新表
        HashTable<K, V, HashFunc> newHT;
        newHT._table.resize(newSize);

        for (size_t i = 0; i < _table.size(); ++i) {
            newHT.Insert(_table[i]._kv);
        }

        _table.swap(newHT._table);
    }

    // 线性探测
    // 起始位置,处理空间大小,是capacity还是size
    HashFunc hf;
    size_t hashi = hf(kv.first) % _table.size();
    while (_table[hashi]._state == EXIST) {
        ++hashi;
        hashi %= _table.size();
    }

    _table[hashi]._kv = kv;
    _table[hashi]._state = EXIST;
    ++_n;

    return true;
}
  • 查找操作 Find:通过哈希函数定位键在哈希表中的位置,若发生冲突,则使用线性探测逐步查找直到找到匹配的键或遇到空位置。
HashData<const K, V>* Find(const K& key) {
	// 线性探测
	HashFunc hf;
	size_t hashi = hf(key) % _table.size();
	while (_table[hashi]._state != EMPTY) {
		if (_table[hashi]._state == EXIST && _table[hashi]._kv.first == key) {
			return (HashData<const K, V>*) & _table[hashi];
		}

		++hashi;
		hashi %= _table.size();
	}

	return nullptr;
}
  • 删除操作 Erase:先通过 Find 方法查找键的位置,如果找到则将该位置的状态标记为 DELETE,并减少元素计数 _n
bool Erase(const K& key) {
	HashData<const K, V>* ret = Find(key);
	if (ret) {
		ret->_state = DELETE;
		--_n;
		return true;
	}

	return false;
}
📖4. 私有成员
  • _table:存储 HashData 的向量,作为哈希表的实际存储容器。
  • _n:存储有效数据的个数,用于计算负载因子并触发扩容操作。
private:
    vector<HashData<K, V>> _table;
    size_t _n = 0; // 存储有效数据的个数
};

🏷️总结

  • 哈希函数:代码定义了默认的 DefaultHashFunc,支持不同类型的键。
  • 开放地址法(线性探测):处理冲突的方式,当发生哈希冲突时,逐步向后探测下一个空位。
  • 扩容机制:当负载因子达到一定值时,动态扩展哈希表大小并重新分配数据,减少冲突。

📕4.2 拉链法(哈希桶)

这里我们继续沿用以上的DefaultHashFunc 哈希函数类。

📖1. HashNode

HashNode 类表示哈希表中的一个节点,包含一个键值对 _kv 和一个指向下一个节点的指针 _next。该类用于构成链表,以解决哈希冲突。

template<class K, class V>
class HashNode {
public:
    pair<K, V> _kv;
    HashNode<K, V>* _next;

    HashNode(const pair<K, V>& kv)
        : _kv(kv), _next(nullptr) {}
};
  • _kv:存储键值对。
  • _next:指向链表中的下一个节点。
📖2. HashTable

HashTable 使用拉链法来处理哈希冲突。每个桶存储一个链表的头节点,当多个键映射到相同的哈希位置时,通过链表存储多个节点。

📄成员变量
  • _tablestd::vector 的每个位置存储一个链表头节点指针,表示一个桶。
  • _n:存储哈希表中当前有效数据的数量。
private:
    vector<Node*> _table; // 指针数组
    size_t _n = 0; // 存储了多少有效数据
📄构造函数和析构函数
  • 构造函数:初始化哈希表大小为 10 个桶。
  • 析构函数:遍历每个桶的链表并释放所有节点的内存。
HashTable() {
    _table.resize(10, nullptr);
}

~HashTable() {
    for (size_t i = 0; i < _table.size(); ++i) {
        Node* cur = _table[i];
        while (cur) {
            Node* next = cur->_next;
            delete cur;
            cur = next;
        }
        _table[i] = nullptr;
    }
}
📄插入操作 Insert

插入操作首先检查键是否已存在,若存在则返回 false,避免重复插入。若键不存在,执行以下步骤:

  1. 扩容:若负载因子达到 1,则将哈希表容量扩大一倍。
    • 新建一个更大的表,并将旧表中的元素重新哈希并插入新表。
  2. 计算哈希值:根据哈希函数 HashFunc 计算哈希值。
  3. 插入到链表:将新节点插入到对应桶的链表头部(头插法),方便且高效。
bool Insert(const pair<K, V>& kv) {
    if (Find(kv.first)) {
        return false;
    }

    HashFunc hf;
    if (_n == _table.size()) {
        size_t newSize = _table.size() * 2;
        vector<Node*> newTable;
        newTable.resize(newSize, nullptr);

        for (size_t i = 0; i < _table.size(); ++i) {
            Node* cur = _table[i];
            while (cur) {
                Node* next = cur->_next;

                size_t hashi = hf(cur->_kv.first) % newSize;
                cur->_next = newTable[hashi];
                newTable[hashi] = cur;

                cur = next;
            }
            _table[i] = nullptr;
        }
        _table.swap(newTable);
    }

    size_t hashi = hf(kv.first) % _table.size();
    Node* newnode = new Node(kv);
    newnode->_next = _table[hashi];
    _table[hashi] = newnode;
    ++_n;

    return true;
}
📄查找操作 Find

查找操作使用哈希函数计算键的位置,遍历该位置的链表,查找对应键的节点。如果找到则返回节点指针,否则返回 nullptr

Node* Find(const K& key) {
    HashFunc hf;
    size_t hashi = hf(key) % _table.size();
    Node* cur = _table[hashi];
    while (cur) {
        if (cur->_kv.first == key) {
            return cur;
        }
        cur = cur->_next;
    }
    return nullptr;
}
📄删除操作 Erase

删除操作与查找类似,通过哈希值定位到对应的链表,找到目标节点后将其从链表中移除。

  1. 如果要删除的节点是链表头节点,直接更新桶头指针。
  2. 否则,将该节点从链表中删除。
bool Erase(const K& key) {
    HashFunc hf;
    size_t hashi = hf(key) % _table.size();
    Node* prev = nullptr;
    Node* cur = _table[hashi];
    while (cur) {
        if (cur->_kv.first == key) {
            if (prev == nullptr) {
                _table[hashi] = cur->_next;
            } else {
                prev->_next = cur->_next;
            }
            --_n;
            delete cur;
            return true;
        }
        prev = cur;
        cur = cur->_next;
    }
    return false;
}
📄打印操作 Print

打印哈希表中的内容,展示每个桶中的链表结构,用于观察哈希表的分布情况。

void Print() {
    for (size_t i = 0; i < _table.size(); ++i) {
        printf("[%d]->", i);
        Node* cur = _table[i];
        while (cur) {
            cout << cur->_kv.first << ":" << cur->_kv.second << "->";
            cur = cur->_next;
        }
        printf("NULL\n");
    }
    cout << endl;
}

🏷️总结
  • 哈希函数:使用默认的 HashFunc 计算键的哈希值,映射到对应桶位置。
  • 拉链法:每个桶使用链表来处理哈希冲突,链表结构灵活且易于扩展。
  • 扩容:当负载因子达到 1 时,哈希表自动扩容以减少冲突。

结语

哈希表不仅仅是一个工具,更是一种思想。它带给我们的不仅是高效的算法,更是对数据结构和算法优化的深刻理解。掌握了哈希表的设计与实现,你将拥有在数据密集型任务中快速处理信息的能力。希望本篇博客能帮助你在C++的学习道路上更进一步,感受到编程的魅力。
在这里插入图片描述

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,17的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是17前进的动力!

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构 -二叉搜索树

一.什么是二叉搜索树 树插入删除方便比线性数组 二.二叉搜索树的查找操作 尾递归可以用循环递归 三.二叉树的插入操作 35要挂在33上面必须记住33的位置 解决方法&#xff0c;要求递归函数返回一个 结点插到33的右子树 四.二叉搜索树的删除 要是删除的是叶子节点之间删除 只有一…

计算机三级 数据库技术

第一章 数据库应用系统开发方法 1.1 数据库应用系统生命周期 软件工程:软件工程的思想&#xff0c;即用工程的概念、原理、技术和方法对软件生产、开发的全过程进行跟踪和管理 软件开发方法:瀑布模型、快速原型模型、螺旋模型 DBAS生命周期模型 1.2 规划与分析 系统规划与定…

使用 AMD GPU 推理 Mixtral 8x22B

Inferencing with Mixtral 8x22B on AMD GPUs — ROCm Blogs 2024年5月1日&#xff0c;由 Clint Greene撰写。 简介 自从Mistral AI’s AI发布了Mixtral 8x7B以来&#xff0c;专家混合&#xff08;MoE&#xff09;在AI社区重新获得了关注。受此发展启发&#xff0c;多个AI公…

前后端、网关、协议方面补充

这里写目录标题 前后端接口文档简介前后端视角对于前端对于后端代码注册路由路由处理函数 关于httpGET/POST底层网络关于前端的获取 路由器网关路由器的IP简介公网IP(WAN IP)私网IP(LAN IP)无线网络IP(WIFI IP)查询路由器私网IP路由器公网IP LAN口与WIFI简介基本原理 手动配置电…

leetcode104:二叉树的最大深度

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

大语言模型理论基础

文章目录 前言大语言模型必需知识概述大语言模型目标模型上下文神经网络的神经元常见激活函数SigmoidTanhRelusoftmax 通用近似定理多层感知机&#xff08;MLP&#xff09;拟合最后 前言 你好&#xff0c;我是醉墨居士&#xff0c;我们接下来对大语言模型一探究竟&#xff0c;…

关于VUE NPM安装失败的问题

最近使用 npm install --registryhttps://registry.npmmirror.com 安装一个新项目的依赖&#xff0c;各种失败。 最后发现是package-lock里面有老的淘宝的域名&#xff0c;整体替换掉就行了

【数据结构】宜宾大学-计院-实验七

实验七 二叉树 一、实验目的&#xff1a;二、实验内容&#xff1a;三、实验结果&#xff1a;1,2&#xff1b;3,4,5;6.数组顺序存储的优缺点二叉链表存储的优缺点 一、实验目的&#xff1a; 掌握二叉树的顺序存储结构 掌握二叉树的链式存储结构 二、实验内容&#xff1a; 1&am…

游戏如何应对内存修改

据观察&#xff0c;近年来游戏黑灰产攻击角度多样化趋势显著&#xff0c;主要面临工作室、定制注入挂、模拟点击挂、内存修改挂、破解版等多方面安全问题。 据FairGuard数据统计&#xff0c;在游戏面临的众多安全风险中&#xff0c;「内存修改」攻击占比约为13%&#xff0c;主…

git重置的四种类型(Git Reset)

git区域概念 1.工作区:IDEA中红色显示文件为工作区中的文件 (还未使用git add命令加入暂存区) 2.暂存区:IDEA中绿色(本次还未提交的新增的文件显示为绿色)或者蓝色(本次修改的之前版本提交的文件但本次还未提交的文件显示为蓝色)显示的文件为暂存区中的文件&#xff08;使用了…

Clickhouse集群新建用户、授权以及remote权限问题

新建用户 create user if not exists user on cluster 集群名称 IDENTIFIED WITH plaintext_password BY 密码;给用户授查询、建表、删表的权限 GRANT create table,select,drop table ON 数据库实例.* TO user on cluster 集群名称 ;再其他节点下用户建本地表成功&#…

Exploring Defeasible Reasoning in Large Language Models: A Chain-of-Thought A

文章目录 题目摘要简介准备工作数据集生成方法实验结论 题目 探索大型语言模型中的可废止推理&#xff1a;思路链 论文地址&#xff1a;http://collegepublications.co.uk/downloads/LNGAI00004.pdf#page136 摘要 许多大型语言模型 (LLM) 经过大量高质量数据语料库的训练&…

应用程序部署(IIS的相关使用,sql server的相关使用)

数据服务程序&#xff08;API&#xff09;部署 1、修改配置文件 打开部署包中的web.config配置文件&#xff0c;确认数据库登录名和密码正确 修改ip为电脑IP&#xff08;winR输入cmd&#xff0c;输入ipconfig&#xff0c;IPv4对应的就是本机IP&#xff09; 2、打开IIS&#x…

RHCE-DNS域名解析服务器

一、DNS简介 DNS &#xff08; Domain Name System &#xff09;是互联网上的一项服务&#xff0c;它作为将域名和 IP 地址相互映射的一个分布式 数据库&#xff0c;能够使人更方便的访问互联网。 DNS 系统使用的是网络的查询&#xff0c;那么自然需要有监听的 port 。 DNS 使…

插入排序(sort)C++

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 时间限制&#xff1a;C/C/Rust/Pascal 1秒&#xff0c;其他语言2秒 空间限制&#xff1a;C/C/Rust/Pascal 512 M&#xff0c;其他语言1024 M 64bit IO Format: %lld 题目描述 插入排序是一种…

Vue2:脚手架 vue-cli

Vue2&#xff1a;脚手架 vue-cli 结构renderrefpropsmixinscoped 脚手架是Vue官方提供的Vue开发平台&#xff0c;.vue文件就需要通过脚手架来解析&#xff0c;所以对于单文件组件就依赖于脚手架。 安装&#xff1a; npm i -g vue/cli如果执行vue --version有输出&#xff0c;…

【MYSQL】主从复制机制(图解)

一、什么是主从复制 主从复制是一种通过binlog&#xff08;二进制日志&#xff09;进行操作的一直复制机制&#xff0c;它会有一个主数据库&#xff0c;还会有一个从数据库&#xff0c;根据binlog就可以把主数据库中的信息复制到从数据库之中。这个主从复制的好处就是如果在并发…

SpringCloud Gateway网关路由配置 接口统一 登录验证 权限校验 路由属性

介绍 Spring Cloud Gateway 根据请求的路径、HTTP 方法、头部等信息&#xff0c;将请求路由到对应的微服务实例。它支持基于动态路由规则的配置&#xff0c;可以根据请求的 URL、查询参数、请求头等条件&#xff0c;灵活地决定将请求转发到哪个微服务。Spring Cloud Gateway 提…

Java学习Day60:回家!(ElasticStatic)

1.what is ElasticStatic The Elastic Stack, 包括 Elasticsearch、 Kibana、 Beats 和 Logstash&#xff08;也称为 ELK Stack&#xff09;。能够安全可靠地获取任何来源、任何格式的数据&#xff0c;然后实时地对数据进行搜索、分析和可视化。 Elaticsearch&#xff0c;简称…

《进制转换:数字世界的奇妙变身术》

思维导图 一、什么是进制转换 在当今数字化飞速发展的时代&#xff0c;数字如同构建整个数字宇宙的基本粒子&#xff0c;无处不在且发挥着至关重要的作用。而在这个数字的魔法世界里&#xff0c;进制就像是不同的语言规则&#xff0c;每种进制都有着独特的构建方式和逻辑。 我…