【数据结构】之散列

一、定义与基本术语

(一)、定义

散列(Hash)是一种将键(key)通过散列函数映射到一个固定大小的数组中的技术,因为键值对的映射关系,散列表可以实现快速的插入、删除和查找操作。在这里分辨一下散列表和哈希表(Hash table和Hash Map):

  • 散列表:可能是一个更通用的概念,不一定强调键值对的映射关系。
  • 哈希表:通常强调键值对的映射关系,类似于 HashMap 或 Dictionary

不过呢,散列表和哈希表在本质上是相同的,我们平时使用一般也不会做很详细的区分。都是基于散列函数的数据结构,用于高效地存储和查找数据。

(二)、基本术语

  • 哈希函数(Hash Function):也叫散列函数,将键映射到散列表中的索引位置的函数。

哈希函数的作用是将键(key)转换为一个索引值,这个索引值用于在底层的数组(或其他数据结构)中定位存储值(value)的位置。这个过程可以概括为以下几个步骤:

  1. 键到哈希值的转换:哈希函数接收一个键作为输入,并输出一个哈希值。这个哈希值通常是整数。

  2. 哈希值到索引的映射:哈希值通过某种映射机制(如取模运算)转换成数组的索引。这个索引决定了键值对在哈希表中的具体存储位置。

  3. 存储和检索:在存储数据时,键值对被放置在由键通过哈希函数和映射机制确定的索引处。在检索数据时,同样的键通过哈希函数和映射机制计算出索引,然后直接访问该索引处的值。

常见的散列(哈希)函数:

  • 除留余数法:hash(key) = key % table_size
  • 乘留余数法:hash(key) = (key * A) % table_size,其中 A 是一个常数。
  • 平方取中法:适用于键是字符串的情况。
  • 哈希冲突(Hash Collision):两个不同的键通过散列函数映射到同一个索引位置。

由于哈希值的范围通常远小于键的总数,不同的键可能会经过哈希函数计算后得到相同的索引值,这种现象称为哈希冲突。为了解决哈希冲突,常用的方法包括:

  • 链地址法:在每个数组索引位置维护一个链表,所有映射到该索引的键值对都存储在这个链表中。(是不是可以想到操作系统里的地址表)

  • 开放寻址法:当发生冲突时,使用某种探测序列在数组中寻找下一个空闲位置。

  • 再哈希法:使用另一个哈希函数计算一个新的索引值。

  • 负载因子(Load Factor):散列表中元素数量与表的容量的比值,衡量哈希表的空间利用率,>0.75会引发重哈希。

如果负载因子过高,意味着更多的键被映射到同一个桶中,这会增加哈希冲突的概率,降低哈希表的性能;

相反,如果负载因子过低,意味着哈希表的存储空间没有得到充分利用,导致额外的空间浪费。

实例 1:理想的哈希函数

假设我们有一个哈希表,总桶数量为10,哈希函数设计得非常完美,将5个键均匀地分布在这10个桶中。

  • 已使用的桶的数量:5

  • 总桶的数量:10

  • 负载因子:105​=0.5

在这种情况下,负载因子为0.5,表示哈希表的使用率为50%,冲突的概率较低,性能较好。

实例 2:不理想的哈希函数

假设我们有同样的哈希表,总桶数量为10,但哈希函数设计得不理想,将5个键都映射到了前5个桶中。

  • 已使用的桶的数量:5

  • 总桶的数量:10

  • 负载因子:105​=0.5

尽管负载因子仍然是0.5,但由于键的分布不均匀,实际上前5个桶已经满了,而后5个桶是空的。这种情况下,虽然负载因子显示哈希表的使用率不高,但实际上已经出现了性能问题。

实例 3:高负载因子

假设我们有一个哈希表,总桶数量为5,但存储了8个键。

  • 已使用的桶的数量:8

  • 总桶的数量:5

  • 负载因子:8/5​=1.6

在这种情况下,负载因子为1.6,表示哈希表的使用率超过了100%,这意味着平均每个桶中有两个键,哈希冲突非常频繁,性能会显著下降。

实例 4:动态扩容

假设我们有一个动态扩容的哈希表,初始总桶数量为10,存储了15个键。

  • 已使用的桶的数量:15

  • 总桶的数量:10

  • 负载因子:15/10​=1.5

当负载因子达到一定阈值(例如1.0)时,哈希表会自动扩容,比如将桶的数量增加到20。扩容后,原有的键会重新通过哈希函数计算新的桶位置,从而降低负载因子,减少冲突。

  • 扩容后的总桶的数量:20

  • 负载因子:15/20​=0.75

通过动态扩容,负载因子降低,哈希表的性能得到改善。

二、特点

  1. 高效性

    • 平均时间复杂度为 O(1) 的插入、删除和查找操作。
    • 在最坏情况下(如所有键都映射到同一索引),时间复杂度为 O(n)。
  2. 灵活性

    • 可以处理任意类型的键(如整数、字符串等)。
    • 可以通过选择合适的散列函数和冲突解决方法优化性能。
  3. 空间利用率

    • 散列表通常需要预留一定的空间来降低冲突概率。
    • 负载因子通常控制在 0.5 到 0.8 之间。

三、基本操作实现

基本操作就包括:

定义、插入,删除,查找

1. 散列表的基本操作

class HashTable{
private:vector<int> table;int size;// 哈希函数int hashFunction(int key) {return key % size;}public:// 构造函数HashTable(int s) : size(s) {table.resize(size, -1);}// 插入元素void insert(int key) {int index = hashFunction(key);while (table[index] != -1) {index = (index + 1) % size;}table[index] = key;}// 查找元素bool search(int key) {int index = hashFunction(key);int start = index;while (table[index] != -1) {if (table[index] == key) {return true;}index = (index + 1) % size;if (index == start) {break;}}return false;}// 删除元素void remove(int key) {int index = hashFunction(key);int start = index;while (table[index] != -1) {if (table[index] == key) {table[index] = -1;return;}index = (index + 1) % size;if (index == start) {break;}}}// 打印哈希表void printTable() {for (int i = 0; i < size; ++i) {cout << "Index " << i << ": ";if (table[i] != -1) {cout << table[i];} else {cout << "Empty";}cout << endl;}}
};int main() {HashTable hashTable(10);// 插入元素hashTable.insert(12);hashTable.insert(22);hashTable.insert(3);// 打印哈希表cout << "After insertion:" << endl;hashTable.printTable();// 查找元素cout << "\nSearching for 22: " << (hashTable.search(22) ? "Found" : "Not found") << endl;cout << "Searching for 10: " << (hashTable.search(10) ? "Found" : "Not found") << endl;// 删除元素hashTable.remove(22);cout << "\nAfter deletion of 22:" << endl;hashTable.printTable();return 0;
}

2. 使用内置函数

以上都是为了帮助理解,在实际的代码中,std::unordered_map 和std::unordered_set 都属于标准库中的哈希表实现,借助它我们可以直接运用其内置函数来完成基本操作,无需手动实现哈希表。两者适用的问题不同:

  • std::unordered_map:存储的是键值对(key - value),每个元素由一个键和一个与之关联的值组成。键是唯一的,通过键可以快速查找对应的值。例如,我们可以用 unordered_map 来存储学生的学号和对应的姓名,学号作为键,姓名作为值。Leetcode例题参考:​​​​​​
  • std::unordered_set:只存储单一的元素,每个元素都是唯一的。它更侧重于判断某个元素是否存在于集合中。例如可以用 unordered_set 来存储一组不重复的单词。Leetcode例题参考:202. 快乐数

具体来说,常用的内置函数包括:

1. 插入元素

  • insert:把键值对插入到 unordered_map 中。若键已存在,则不会插入新元素。
  • emplace:原位构造并插入一个新元素,若键已存在,则不插入。
  • operator[]:若键存在,返回对应的值;若键不存在,则插入该键,并默认初始化其值。

2. 查找元素

  • find:查找指定键的元素,若找到则返回指向该元素的迭代器;若未找到,则返回 end() 迭代器。(所以我们判断指定键的元素在不在表中一般用的代码类似:if(seen.find(n)==seen.end()) )
  • count:返回指定键的元素数量,由于 unordered_map 中键是唯一的,所以返回值要么是 0(键不存在),要么是 1(键存在)。

3. 删除元素

  • erase:删除指定键的元素,可接受键或迭代器作为参数。

4. 其他常用函数

  • size:返回表中元素的数量。
  • empty:判断表是否为空。
  • clear:清空表中的所有元素。

在这里也给出一个应用的例子:

#include <iostream>
#include <unordered_map>
#include <unordered_set>int main() {// 使用 std::unordered_mapstd::unordered_map<int, std::string> myMap;myMap[1] = "apple";myMap[2] = "banana";auto itMap = myMap.find(1);if (itMap != myMap.end()) {std::cout << "Value for key 1 in map: " << itMap->second << std::endl;}// 使用 std::unordered_setstd::unordered_set<std::string> mySet;mySet.insert("apple");mySet.insert("banana");auto itSet = mySet.find("apple");if (itSet != mySet.end()) {std::cout << "Element 'apple' found in set." << std::endl;}return 0;
}

四、练习

1. 基本概念练习

首先明确单射、满射的含义:
理想的单射哈希函数:每个键都映射到一个唯一的索引,没有任何两个键共享同一个索引。这可以完全避免哈希冲突,使得每个键都可以直接映射到一个唯一的桶或数组位置。

理想的满射哈希函数:所有的桶或数组索引至少被一个键映射到。这确保了数组的空间被充分利用,没有浪费的桶或索引。

但以上都属于理想情况,在实际设计哈希函数时,目标是尽量减少冲突(接近单射),同时尽可能均匀地分布键到所有可用的索引(接近满射)。

由此分析: 

A:错误。由于 ∣A∣<∣S∣,即地址空间小于词条空间,不可能每个词条都映射到一个唯一的地址,因此 h 不可能是满射。

B:正确。从A的分析就能知道,由于 ∣A∣<∣S∣,不同的词条必须映射到相同的地址以避免某些词条无法映射,因此 h 不可能是单射。

C:错误。虽然 ∣A∣<∣S∣,但 h 仍然可以是满射,只要每个地址都至少被一个词条映射到。

D:错误。单射要求每个地址只能映射到一个词条。在哈希表中,由于冲突的存在,一个地址可能映射到多个词条,因此 h 不一定是单射。

我们从构造散列函数的目的进行分析,可以找出正确和错误的说法。

  • 单射:在散列表的上下文中,单射并不是一个好的特性,因为不同的键映射到同一个索引是不可避免的,这是由于散列表的大小有限。

  • 值域覆盖:一个好的散列函数应该尽可能覆盖散列表的所有地址,这样可以提高空间利用率和减少冲突。

  • 一致性:同一个词条每次计算得到的哈希值应该是相同的,这样才能保证数据的一致性。

  • 效率:散列函数的计算应该简单快速,以便于快速定位和检索数据。

  • 均匀分布:散列函数应该能够将不同的词条均匀地分布在散列表中,避免某些区域过于拥挤,这有助于减少冲突。

  • 冲突几率:一个好的散列函数应该设计得能够最小化冲突的概率,这样可以提高散列表的性能。

 

对于 h1(key)=key%12,计算每个元素的哈希值:

  • 0%12=0

  • 8%12=8

  • 16%12=4

  • 24%12=0 (与0冲突)

  • 32%12=8 (与8冲突)

  • 40%12=4 (与16冲突)

  • 48%12=0 (与0, 24, 32冲突)

  • 56%12=8 (与8, 32, 48冲突)

  • 64%12=4 (与16, 40冲突)

不同的哈希值有:{0,4,8},共3个不同的元素。

对于 h2(key)=key%11,计算每个元素的哈希值:

  • 0%11=0

  • 8%11=8

  • 16%11=5

  • 24%11=2

  • 32%11=10

  • 40%11=7

  • 48%11=4

  • 56%11=1

  • 64%11=9

所有的哈希值都是不同的,因此有9个不同的元素。

A. 错误。独立链法中,每个桶(数组的每个位置)都链接到一个链表,冲突的词条存放在对应的链表中,而不是桶数组内部。

B. 正确。开放定址法中,当发生冲突时,词条会尝试在数组中找到下一个空闲位置存放,因此实际存放位置可能与散列函数计算出的位置不同。

C. 错误。开放定址法不会使用链表存放词条,而是在数组中寻找下一个空闲位置。

D. 错误。即使散列函数设计得再好,由于散列表的大小有限,不同的键仍然可能映射到同一个索引,需要冲突解决策略。

h(4)=(3×4+5)%11=17%11=6

也就是说词条4应该被放在数组索引6的位置,但是我们发现索引6处非空闲,存储的是15,需要使用线性试探来寻找下一个空闲位置。线性试探的公式通常是 hi​=(h+i)%size,其中 i 是探测次数,从0开始。

  • 第一次试探:h0​=6(已占用)

  • 第二次试探:h1​=(6+1)%11=7(已被26占用)

  • 第三次试探:h2​=(6+2)%11=8(空)

所以键4的实际存放位置是数组索引8的位置,即 A[8]。

开放定址法是一种处理哈希表冲突的方法,其中平方试探法是开放定址法的一种。在平方试探法中,如果发生冲突,我们按照 hi​=(h+i2)%size 的公式来探测下一个可能的空槽位。

当使用平方试探法时,为了保证哈希表中至少有一个空槽位,装填因子load factor也就是负载因子不能超过 0.5。

这是因为在最坏的情况下,如果装填因子超过 0.5,那么在探测过程中可能会遇到一个“循环”,即所有的槽位都被占用,没有空槽位可以插入新的词条。

(1) H(9)=9%11=9

所以关键字为9的节点应该被存储在索引为9的位置。先对给出的散列表中元素进行插入:

给定的关键字已经按顺序插入散列表,我们先计算它们的哈希值并确定它们的存储位置:

  • 15: 15%11=4 → 存储在位置 4

  • 31: 31%11=9 → 存储在位置 9

  • 27: 27%11=5 → 存储在位置 5

  • 14: 14%11=3 → 存储在位置 3

  • 10: 10%11=10 → 存储在位置 10

  • 16: 16%11=5 → 与关键字 27 冲突,线性探测到位置 6

  • 11: 11%11=0 → 存储在位置 0

查找后发现索引为9的位置已被占用,那就线性探测继续找下一个空位。

  • 位置 10 已被关键字 10 占用

  • 位置 11 为空

因此,关键字 9 将被存储在位置 11。

A={11,31,∗,14,∗,0,15,26,16,5,9,∗}

(2) 平均成功查找长度

  • 15: 直接在位置 4 找到,长度为 1

  • 31: 直接在位置 9 找到,长度为 1

  • 27: 直接在位置 5 找到,长度为 1

  • 14: 直接在位置 3 找到,长度为 1

  • 10: 直接在位置 10 找到,长度为 1

  • 16: 在位置 5 冲突,探测到位置 6,长度为 2

  • 11: 直接在位置 0 找到,长度为 1

  • 9: 在位置 9 冲突,探测到位置 11,长度为 2

总长度 = 1+1+1+1+1+2+1+2=10 ,关键字数量 = 8

ASL(成功) =10/8 =5/4

(3) 失败的平均查找长度

采用线性探测法处理冲突时,若该地址有元素,从该地址开始,依次探测下一个地址,直到找到空地址。将每个散列地址查找失败的比较次数相加,再除以散列地址的个数,就能得到失败的平均查找长度。

A={11,31,∗,14,∗,0,15,26,16,5,9,∗}

位置0:依次探测0,1,2,在索引为2处找到空桶,比较次数3;

位置1:依次探测1,2,在索引为2处找到空桶,比较次数2;

位置2:在索引为2处找到空桶,比较次数1;

位置3:依次探测3,4,在索引为4处找到空桶,比较次数2;

位置4:在索引为4处找到空桶,比较次数1;

位置5:依次探测5,6,7,8,9,10,11,在索引为11处找到空桶,比较次数为7;

位置6:依次探测6,7,8,9,10,11,在索引为11处找到空桶,比较次数为6;

位置7:依次探测7,8,9,10,11,在索引为11处找到空桶,比较次数为5;

位置8:依次探测8,9,10,11,在索引为11处找到空桶,比较次数为4;

位置9:依次探测9,10,11,在索引为11处找到空桶,比较次数为3;

位置10:依次探测10,11,在索引为11处找到空桶,比较次数为2;

位置11:在索引为11处找到空桶,比较次数为1;

总长度 = 3+2+1+2+1+7+6+5+4+3+2+1=11 空桶数量 = 3

ASL(失败) = 11

五、Leetcode练习题汇总

题号题目名称难度等级关键词
1两数之和简单哈希表、数组
202快乐数简单哈希表、数学、双指针
217存在重复元素简单哈希表、数组、排序
219存在重复元素 II简单哈希表、数组
242有效的字母异位词简单哈希表、字符串、排序
349两个数组的交集简单哈希表、双指针、二分查找、排序
350两个数组的交集 II简单哈希表、双指针、二分查找、排序
409最长回文串简单哈希表、字符串、贪心
451根据字符出现频率排序中等哈希表、字符串、排序、堆(优先队列)
560和为 K 的子数组中等哈希表、数组、前缀和
705设计哈希集合简单哈希表、设计
706设计哈希映射简单哈希表、设计
771宝石与石头简单哈希表、字符串
811子域名访问计数简单哈希表、字符串
953验证外星语词典简单哈希表、字符串
1207独一无二的出现次数简单哈希表、数组
1365有多少小于当前数字的数字简单哈希表、数组、排序、计数
1481不同整数的最少数目中等哈希表、贪心、排序、堆(优先队列)

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

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

相关文章

How AI could empower any business - Andrew Ng

How AI could empower any business - Andrew Ng References 人工智能如何为任何业务提供支持 empower /ɪmˈpaʊə(r)/ vt. 授权&#xff1b;给 (某人) ...的权力&#xff1b;使控制局势&#xff1b;增加 (某人的) 自主权When I think about the rise of AI, I’m reminded …

微服务的服务调用详解以及常见解决方案对比

微服务服务调用详解 1. 服务调用分类 服务调用根据通信方式、同步性、实现模式可分为以下类型&#xff1a; 按通信协议分类 类型典型协议/框架特点RPC&#xff08;远程过程调用&#xff09;Dubbo、gRPC、Apache Thrift高性能、二进制协议、强类型定义HTTP/RESTSpring RestTe…

MySQL:B+树索引

InnoDB索引方案 为了使用二分法快速定位具体的目录项&#xff0c;假设所有目录项都可以在物理存储器上连续存储&#xff0c;有以下问题&#xff1a; InnoDB使用页为管理存储空间的基本单位&#xff0c;最多只能保证16KB的连续存储空间&#xff0c;记录数据量多可能需要非常大…

THCON 2025

Crypto OTPas_ouf 用10个字符异或加密的jpg图片&#xff0c;通过头得到key再恢复原图 Mammoths Personnal Slot Machine 梅森旋转恢复 from pwn import * from randcrack import RandCrack from tqdm import trange context.log_level errorp remote(74.234.198.209, 33…

3.8 字符串的常用函数

重点&#xff1a;字符串的常用函数 #1.测试转换大小写 lower:大写->小写 upper&#xff1a;小写->大写 swapcase&#xff1a;自动将大写转小写小写转大写 print("ABC".lower()) #abcprint("abc".upper()) #ABCprint…

Docker:SkyWalking 链路追踪的技术指南

1、简述 Apache SkyWalking 是一个开源的 APM(应用性能监控)工具,能够实现分布式系统的全链路监控、性能分析以及服务依赖关系分析。SkyWalking 支持多种语言的探针,提供强大的可视化监控和分析能力,是微服务架构下性能调优和问题排查的利器。 样例代码: https://gitee.…

[Lc] 最长公共子序列 | Fenwick Tree(树状数组):处理动态前缀和

目录 LCR 095. 最长公共子序列 题解 Fenwick Tree&#xff08;树状数组&#xff09;&#xff1a;处理动态前缀和 一、问题背景&#xff1a;当传统方法遇到瓶颈 二、Fenwick Tree核心设计 2.1 二进制索引的魔法 2.2 关键操作解析 更新操作&#xff08;O(log n)&#xff0…

python3.13.0环境安装及python-docx库安装指南

1. Python环境安装 1.1 Windows系统安装Python 下载Python安装包 • 访问Python官网 • 点击"Download Python 3.x.x"&#xff08;推荐使用3.8及以上版本&#xff09; 2. 运行安装程序 • 双击下载的安装包 • 重要&#xff1a;勾选"Add Python to environmen…

前端VUE框架理论与应用(4)

一、计算属性 模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如: <div id="example">{{ message.split().reverse().join() }}</div> 在这个地方,模板不再是简单的声明式逻辑。你…

MySQL:存储函数和存储过程

系列文章目录 1.MySQL编程基础 2.程序控制流语句 3.存储过程 4.游标 5.嵌入式SQL 文章目录 系列文章目录前言一、程序控制流语句&#xff1a;二、存储函数&#xff1a; 1.存储函数的特点&#xff1a;2.存储函数的定义&#xff1a;3.调用存储函数 三、存储过程&#xff1a;…

基础贪心算法集合2(10题)

目录 1.单调递增的数字 2.坏了的计算器 3.合并区间 4.无重叠区间 5. 用最少数量的箭引爆气球 6.整数替换 解法1&#xff1a;模拟记忆化搜索 解法2位运算贪心 7.俄罗斯套娃信封问题 补充.堆箱子 8.可被3整除的最大和 9.距离相等的条形码 10.重构字符串 1.单调递增的数字…

RaabitMQ 快速入门

&#x1f389;欢迎大家观看AUGENSTERN_dc的文章(o゜▽゜)o☆✨✨ &#x1f389;感谢各位读者在百忙之中抽出时间来垂阅我的文章&#xff0c;我会尽我所能向的大家分享我的知识和经验&#x1f4d6; &#x1f389;希望我们在一篇篇的文章中能够共同进步&#xff01;&#xff01;&…

语音识别——根据声波能量、VAD 和 频谱分析实时输出文字

SenseVoiceSmall网络结构图 ASR(语音识别)是将音频信息转化为文字的技术。在实时语音识别中,一个关键问题是:如何决定将采集的音频数据输入大模型的最佳时机?固定时间间隔显然不够灵活,太短可能导致频繁调用模型,太长则会延迟文字输出。有没有更智能的方式?答案是肯定…

AI大模型如何重塑科研范式:从“假说驱动”到“数据涌现”

📝个人主页🌹:慌ZHANG-CSDN博客 🌹🌹期待您的关注 🌹🌹 一、引言:科研进入“模型共研”时代 传统科研范式通常以“假设→实验→验证→理论”的方式推进,这一经典路径建立在人类的认知能力与逻辑推理基础上。然而,随着数据规模的爆炸式增长与知识系统的高度复杂…

使用Python写入JSON、XML和YAML数据到Excel文件

在当今数据驱动的技术生态中&#xff0c;JSON、XML和YAML作为主流结构化数据格式&#xff0c;因其层次化表达能力和跨平台兼容性&#xff0c;已成为系统间数据交换的通用载体。然而&#xff0c;当需要将这类半结构化数据转化为具备直观可视化、动态计算和协作共享特性的载体时&…

面试题:Eureka和Nocas的区别

Eureka 与 Nacos 核心区别对比 一、功能定位与核心能力 ‌维度‌‌Eureka‌‌Nacos‌‌核心功能‌专注服务注册与发现&#xff0c;无配置管理功能‌:ml-citation{ref“1,3” data“citationList”}集成服务注册、发现、配置管理、动态DNS等‌:ml-citation{ref“1,3” data“c…

2025年4月15日 百度一面 面经

目录 1. 代理相关 从静态代理到动态代理 2. cglib可以代理被final修饰的类吗,为什么 3. JVM 体系结构 4. 垃圾回收算法 5. 什么是注解 如何使用 底层原理 6. synchronized和reentrantlock 7. 讲一下你项目中 redis的分布式锁 与java自带的锁有啥区别 8. post 请求和 ge…

AI改变生活

AI改变生活 人工智能&#xff08;AI&#xff09;在我们生活中的应用越来越广泛&#xff0c;深刻地改变了我们的工作和生活方式。以下是一些AI实际应用的实例&#xff0c;以及它们如何影响我们的日常生活。 1. 智能助手 智能助手如Siri、Alexa和Google Assistant等&#xff0…

信奥赛之c++基础(取模运算与数位分离)

🎮 数字拆解大冒险——取模运算与数位分离魔法课 🍬 第一章:糖果分装术——取模运算 🍭 分糖果游戏 7颗糖每人分3颗: 每人得到:7 / 3 = 2颗剩余糖果:7 % 3 = 1颗(%就是取模符号) 就像把糖果装袋后剩下的零散糖粒!🔧 取模运算说明书 算式比喻结果10 % 310颗糖分…

揭秘大数据 | 21、软件定义计算

老夫先将这个小系列的前两篇内容链接奉上&#xff0c;方便感兴趣的朋友一气读之。 揭秘大数据 | 19、软件定义的世界-CSDN博客 揭秘大数据 | 20、软件定义数据中心-CSDN博客 今天&#xff0c;书接上文&#xff0c;开聊软件定义计算的那些事儿&#xff01; 虚拟化是软件定义…