[C++] 哈希详解

2023-10-30

1. 哈希概念

  哈希是一种高效用来搜索的数据结构,与传统的查找方式进行比较,发现传统的方式都需要进行元素的比较,性能高低取决于元素的比较次数。让元素在查找时不进行比较,或者减少比较次数。

  顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(log2 N),搜索的效率取决于搜索过程中元素的比较次数。
  理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一对一映射的关系,那么在查找时通过该函数可以很快找到该元素。

2. 实现机制

2.1 插入时

1.通过函数计算插入位置,该函数成为哈希函数
2.通过计算出的存储位置进行元素的插入,通过这种方法构造出来结构称为哈希表(散列表)

2.2 查找时

1.通过哈希函数对关键码进行计算,得出元素的存储位置
2.根据存储位置在哈希表中,取出元素进行比较,若关键码相同,则查找成功。

这里使用除留余数法举例
在这里插入图片描述

2.3 缺陷

  对于两个数据元素的关键字 ki 和 kj (i != j),有 ki != kj,但有:Hash(ki) == Hash(kj),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

举例:
上述图片中,1的位置上已经插入了一,如果我们还想插入11,使用除留余数法11 % 10 = 1,插入位置还是 1 的位置,这就发生了哈希冲突。

2.4 常见哈希函数

2.4.1 直接定制法

原理:取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
优点:简单、均匀 缺点:需要事先知道关键字的分布情况
使用场景:适合查找比较小且连续的情况

2.4.2 除留余数法

原理:设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址。
注意: 一般情况下,p最好是一个不超过m的最大素数

2.4.3 平方取中法

原理:假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址; 再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址
场景:平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况

2.4.4 折叠法

原理:折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。
场景:折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况

注意

不论一个哈希函数设计的有多么精妙,都不能完全解决哈希冲突,只能是,哈希函数越精妙,产生的哈希冲突概率越低

3. 解决哈希冲突

3.1 闭散列

  闭散列也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去

3.1.1 线性探测

原理
  从发生冲突的位置逐个挨着一次往后查找,如果查找到空间的末尾,也没有发现空位置时,再折回空间起始位置继续查找,直到找到空位置插入进去。

缺陷
  容易产生数据堆积,如果发生了冲入,冲突的元素可能连成一片,因为查找下一个空位置的方式,是逐个向后查找到。

如何避免
不要挨着依次向后查找空位置,二次探测

3.1.2 二次探测

  解决数据堆积的问题,每次查找空位置时,都向后偏移不同的举例进行检测

查找下一个空位置的方法

H(i) = (H0 + i ^ 2) % cap / H(i) = (H0 - i ^ 2) % cap
所以: H(i + 1) = H(i) + 2 * i + 1

缺陷
  当表格中空位置比较少时,可能需要查找很多次

3.1.3 闭散列缺点

  当表格中数据较多时,或者哈希表中的空位置较少时,发生冲突的概率非常高,而且找下一个空位置的效率比较低,不论是线性探测还是二次探测都需要找好多次才能找到。

  当哈希表中有效元素比较多时,对哈希表的性能影响非常大。
  哈希期望查找时间复杂度是 O(1),但空位置比较少时,性能远远超过O(1).
哈希使用时间换空间

解决方法
  不要让表格存储太多元素,达到一定程度时就进行扩容,即: 哈希表中的元素一定不会存满

  负载因子 = 填入表的有效元素的个数 / 散列表的长度 线性探测为:70%左右,二次探测为:50%~60%,在这个区间内就需要扩容了,存的多了就会导致冲突概率上升,存的少了就会空间利用率降低。

3.2 开散列

  开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
  数组中的每个元素都是链表,每条链表中挂的都是发生哈希冲突的元素

在这里插入图片描述
操作
  插入或查找时,当中存在一个循环,即:遍历链表,检测data是否存在,表面上时间复杂度O(n),但实际认为操作时的时间复杂度为O(1),因为链表不会很长

3.2.1 容量问题

1. 如果元素特别多,或元素特别特殊,大部分元素都集中到了某一个链表中

  导致某些链表特别长,在哈希桶中查找某个元素,就退化成了在单链表中查找。

  如果每个哈希桶中的元素不是特别多的情况下,哈希桶的时间复杂度为O(1).
  如果哈希桶当中某些链表中挂的结点非常多,哈希桶就退化成了在链表中进行查找,时间复杂度变成了O(n)。

  所以在插入元素的过程中,需要避免上述的情况,在闭散列中,元素多到一定程度通过扩容来降低冲突概率,哈希桶中也是如此,通过扩容,把链表中的结点分散开来。

2. 什么情况下进行扩容?什么情况下哈希桶最优(空间利用率高、查找性能高)?
  最优状态: 每个桶只挂一个结点。当哈希桶中元素个数与桶的个数相同时就要考虑扩容。因为扩容之后,哈希桶的容量变了,哈希函数计算出来的结果就发生了变化,再将旧哈希桶中的元素往新哈希桶中插入,就可以基本到达较优。

3. 随着哈希桶中的元素不断增多,哈希桶的的容量也会不断增大,怎么解决?
  扩容之后,继续往哈希桶中插入元素,或者对方在向哈希表中插入元素是,每个插入哈希桶中的元素都是发生哈希冲突的,可能又会出现某些链表特别长的场景出现,扩容之后依旧不能达到目的。(哈希桶性能又下降了,但是还没有达到扩容的时机)。

解决方案
  当链表中的结点达到一定的阈值,还没有达到扩容的条件时,可以将链表转换成红黑树,当结点个数小于阈值就转换成链表。

4. 哈希函数
  除留取余法:最好模上一个素数,发生冲突的概率相对而言能稍微低一些,而元素的个数与表格容量相等时,需要扩容,按照两倍的方式扩容。可以用素数表将含有两倍关系的素数存下来。

const int PRIMECOUNT = 28;
const size_t primeList[PRIMECOUNT] =
{
	53ul, 97ul, 193ul, 389ul, 769ul,
	1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
	49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
	1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
	50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
	1610612741ul, 3221225473ul, 4294967291ul
};

5. 泛型存储
  哈希函数中任意类型都能存储,但是使用除留取余法,只能转换整数,所以还要传入一个模板参数,可以将其它类型进行转换,使之能够使用除留取余法。

4. 开散列的代码实现

#include <iostream>
using namespace std;

#include <vector>
#include "Common.h"

// 哈希桶:数组+链表(无头单链表)

template<class T>
struct HashBucketNode
{
	HashBucketNode<T>* next;
	T data;

	HashBucketNode(const T& x = T())
		: next(nullptr)
		, data(x)
	{}
};


// T 如果是整形家族
template<class T>
class T2IntDef
{
public:
	const T& operator()(const T& data)
	{
		return data;
	}
};


class Str2Int
{
public:
	size_t operator()(const string& s)
	{
		const char* str = s.c_str();
		unsigned int seed = 131; // 31 131 1313 13131 131313
		unsigned int hash = 0;
		while (*str)
		{
			hash = hash * seed + (*str++);
		}
		return (hash & 0x7FFFFFFF);
	}
};


// 假设:当前实现的哈希表中元素唯一
// T:哈希表中存储的元素的类型
// T2Int: 将T转换为整形的结果

template<class T, class T2Int = T2IntDef<T>>
class HashBucket
{
	typedef HashBucketNode<T> Node;
public:
	HashBucket(size_t capacity = 53)
		: table(GetNextPrime(capacity))    // n个值为data的构造方法在构造哈希桶
		, size(0)
	{}

	~HashBucket()
	{
		Destroy();
	}

	/*
	虽然从代码层面来看,Insert当中存在一个循环,即:遍历链表,检测data是否存在---->表面上Insert的时间复杂度O(N)
	但是实际认为Insert的时间复杂度仍旧为O(1)---->因为:在哈希桶中,每个桶对应的链表当中的节点的格式都不是非常多
	*/
	bool Insert(const T& data)
	{
		// 0. 检测是否需要扩容
		CheckCapacity();

		// 1. 通过哈希函数计算元素所在桶号
		size_t bucketNo = HashFunc(data);

		// 2. 检测元素是否存在
		Node* cur = table[bucketNo];
		while (cur)
		{
			if (data == cur->data)
				return false;

			cur = cur->next;
		}

		// 3. 插入元素
		cur = new Node(data);
		cur->next = table[bucketNo];
		table[bucketNo] = cur;
		++size;
		return true;
	}

	bool Erase(const T& data)
	{
		// 1. 先通过哈希函数计算元素所在的桶号
		size_t bucketNo = HashFunc(data);

		// 2. 找元素在bucketNo桶中是否处在,存在则删除
		// 核心操作--->删除链表当中只为data的节点
		//  该节点可能是链表中的第一个节点
		//  该节点可能是链表中的非第一个节点

		Node* cur = table[bucketNo];
		Node* prev = nullptr;   // 标记cur的前一个节点
		while (cur)
		{
			if (data == cur->data)
			{
				// 删除cur节点
				// 删除的节点如果是第一个节点
				if (nullptr == prev)
					table[bucketNo] = cur->next;
				else
					prev->next = cur->next;

				delete cur;
				--size;
				return true;
			}
			else
			{
				// 当前节点不是要找的data,则两个指针同时往后移动
				prev = cur;
				cur = cur->next;
			}
		}

		// 哈希桶中不存在值为data的元素,无法删除即删除失败
		return false;
	}

	Node* Find(const T& data)
	{
		// 1. 先通过哈希函数计算元素所在的桶号
		size_t bucketNo = HashFunc(data);

		// 2. 在检测该元素是否存在对应的链表中
		Node* cur = table[bucketNo];
		while (cur)
		{
			if (data == cur->data)
				return cur;

			cur = cur->next;
		}

		return nullptr;
	}

	size_t Size()const
	{
		return size;
	}

	bool Empty()const
	{
		return 0 == size;
	}

	/
	// 为了方便对哈希桶的理解,实现打印哈希桶的方法
	void Print()
	{
		for (size_t i = 0; i < table.capacity(); ++i)
		{
			Node* cur = table[i];

			cout << "table[" << i << "]:";

			while (cur)
			{
				cout << cur->data << "--->";
				cur = cur->next;
			}

			cout << "NULL" << endl;
		}
		cout << "=========================================" << endl;
	}

	void Swap(HashBucket<T, T2Int>& ht)
	{
		table.swap(ht.table);
		std::swap(size, ht.size);
	}

private:
	// 哈希函数---除留余数法
	size_t HashFunc(const T& data)
	{
		T2Int t2Int;
		return t2Int(data) % table.capacity();
	}

	void Destroy()
	{
		// 循环去销毁:table中的每个链表
		for (size_t i = 0; i < table.capacity(); ++i)
		{
			Node* cur = table[i];

			// 采用:头删法删除链表中的每个节点
			while (cur)
			{
				table[i] = cur->next;
				delete cur;
				cur = table[i];
			}
		}

		size = 0;
	}

	void CheckCapacity()
	{
		if (size == table.capacity())
		{
			// 扩容---将表格放大----然后将桶中的元素往新桶中插入
			HashBucket<T, T2Int> newHT(GetNextPrime(table.capacity()));

			// 将就哈希桶中的节点拆下来,移动到新哈希桶中
			for (size_t i = 0; i < table.capacity(); ++i)
			{
				Node* cur = table[i];
				while (cur)
				{
					// 1. 将cur节点拆下来
					table[i] = cur->next;

					// 2. 将cur节点往newHT中插入
					// 找位置
					size_t bucketNo = newHT.HashFunc(cur->data);

					// 头插法
					cur->next = newHT.table[bucketNo];
					newHT.table[bucketNo] = cur;
					newHT.size++;

					cur = table[i];
				}
			}

			// 已经将旧哈希桶中的元素全部移动到新哈希桶当中了
			this->Swap(newHT);
		}
	}
private:
	// table当中将来存放所有的链表--->实际只需要存放链表首节点的地址
	std::vector<Node*> table;
	size_t size;   // 有效元素的个数
};
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

[C++] 哈希详解 的相关文章

  • Qt 图表和数据可视化小部件

    我已经安装了 Qt 5 7 来尝试 Qt 图表和 Qt 数据可视化 但我在 Qt Designer 和 Qt Creator 中都找不到新的小部件 有什么建议我应该做什么才能让新的小部件出现在设计器中 我今天遇到了完全相同的问题 默认情况下
  • 具有相同参数类型但具有不同常量限定符的 std::vector 的转换

    问题很简单 静态转换 或其他一些转换 通常是安全的 std vector lt Foo gt to std vector lt const Foo gt 就二进制而言 我不明白为什么本机类型会有所不同 毕竟const是一种语言约束 不应影响
  • 结构体如何存储在内存中?

    我有一个struct iof header在我的代码中 我确定它的宽度是 24 字节 我执行 sizeof iof header 它返回 32 字节宽 问题1为什么是 32 字节宽而不是 24 字节宽 问题2包括其成员在内 结构体如何存储在
  • fopen_s 怎么会比 fopen 更安全呢?

    我正在处理遗留代码Windows平台 当我编译代码时VS2013 它给出以下警告 错误 C4996 fopen 该函数或变量可能不安全 考虑使用fopen s反而 要禁用弃用 请使用 CRT SECURE NO WARNINGS 详情请参见
  • 平滑手绘曲线

    我有一个允许用户绘制曲线的程序 但这些曲线看起来不太好 它们看起来摇摇欲坠 而且是手绘的 所以我想要一种能够自动平滑它们的算法 我知道平滑过程中存在固有的模糊性 因此它不会每次都完美 但这种算法似乎确实存在于多个绘图包中 并且它们工作得很好
  • 最新 .Net MongoDb.Driver 的连接问题

    我创建了一个 MongoLab 沙箱数据库 我与 MongoChef 连接 效果很好 我通过 Nuget 安装了 MongoDB Driver 2 2 2 我编写了一些简单的 C 演示代码 但就是无法使其工作 连接字符串是直接从 Mongo
  • 警告 C4800:“int”:强制值为 bool“true”或“false”(性能警告)

    我的代码中有这个问题 bool CBase isNumber return id MID NUMBER bool CBase isVar return id MID VARIABLE bool CBase isSymbol return i
  • 如何从 Qt 应用程序通过 ODBC 连接到 MySQL 数据库?

    我有一个新安装的 MySQL 服务器 它监听 localhost 3306 从 Qt 应用程序连接到它的正确方法是什么 原来我需要将MySQL添加到ODBC数据源 我在遵循这个视频教程后做到了这一点 https youtu be K3GZi
  • 嵌入资源文件的路径

    我的资源文件中有一个图标 我想引用它 这是需要图标文件路径的代码 IWshRuntimeLibrary IWshShortcut MyShortcut MyShortcut IWshRuntimeLibrary IWshShortcut W
  • 无法加载程序集问题

    我收到以下错误 无法加载程序集 错误详细信息 System BadImageFormatException 无法加载文件或程序集 文件 或其依赖项之一 该程序集是由比当前加载的运行时更新的运行时构建的 无法加载 该程序集是使用 Net Fr
  • Linq 合并列表

    我的课 public class Foo public int A get set public List
  • 在c#中获取没有时间的日期

    我的表上有一列 缺勤日期时间 日期 当我想要获取包含日期的行时 它返回 0 行 这是我的 C 代码 DateTime ClassDate DateTime Parse lblDate Content ToString var Abs dbs
  • 如何构建一棵与或树?

    我需要一个支持 与 和 或 的树结构 例如 给定一个正则表达式 如ab c d e 我想把它变成一棵树 所以 一开始我们有两个 或 分支 它可以向下ab or c d e 如果你低头ab分支 你得到两个节点 a and b or a其次是b
  • 如何在 C# 中更改公共 IP 地址

    我正在创建一个 C winform 应用程序 我想在其中更改公共 IP 地址 而不是像 Hotspot Shield ZenMate OpenVPN 等那样更改 IPv4 地址 我已经检查了以下链接 但没有找到足够的帮助 所以我发布了这个问
  • 连接到没有元数据的网络服务

    我想连接到此网络服务 https training api temando com schema 2009 06 server wsdl https training api temando com schema 2009 06 serve
  • C 变量声明的效率 [重复]

    这个问题在这里已经有答案了 例如 在 C 中声明一个变量需要多长时间int x or unsigned long long var 我想知道它是否会让我的代码在类似的事情中更快 for conditions int var 0 code 这
  • Xcode 7 调试器不会中断内联标头函数

    过去五年我一直在各种 C 项目中使用 Xcode 没有出现这个问题 今天 我打开了一个较旧的项目 大约 2 年前 并尝试通过在该函数中放置一个活动断点来调试头文件中的内联函数 由于某种原因 调试器不会中断此代码 但是 如果我在调用该函数的
  • 如何在c#中创建多线程

    我需要监听机器中的所有串行端口 假设我的机器有 4 个串行端口 我必须创建 4 个线程并开始分别使用附加线程监听每个端口 我使用此代码来获取我的机器中的端口数量 private SerialPort comPort new SerialPo
  • 如何在 C 中创建最低有效位设置为 1 的掩码

    这个功能如何运作 最低有效 n 位设置为 1 的掩码 Example n 6 gt 0x2F n 17 gt 0x1FFFF 我根本不明白这些 尤其是 n 6 gt 0x2F 另外 什么是面膜 通常的方法是采取1 并将其左移n位 这会给你类
  • 使用 CodeDOM 将程序集添加到 BuildManager 会导致间歇性错误

    我正在使用 CodeDOM 在运行时创建内存中程序集 如下所示 public Assembly Compile CodeCompileUnit targetUnit string path Path GetDirectoryName new

随机推荐

  • Web安全公开课-XSS-前端基础

    这节课分两个部分讲 1 HTML概述 2 javascript概述 什么是HTML呢 HTML是种超文本标记语言 英文名字叫 HyperText Markup Language 超级文本标记语言是 种规范 也是一种标准 它通过标记符号来标
  • 2.微服务项目实战---环境搭建,实现电商中商品、订单、用户

    使用的电商项目中的商品 订单 用户为案例进行讲解 2 1 案例准备 2 1 1 技术选型 maven 3 3 9 数据库 MySQL 5 7 持久层 SpingData Jpa 其他 SpringCloud Alibaba 技术栈 2 1
  • H.264 和 MPEG-4 基础

    H 264 和 MPEG 4 的关系 H 264 AVC Advanced Video Coding 标准 是 MPEG 4 的第 10 部分 MPEG 4的初衷是将DVD质量的图像码流从每秒6兆降低到1 5兆 将高清电视的码流从每秒几十兆
  • 圆周率π的取值

    const double pi acos 1 0 头文件是
  • react 接口调用

    这是你定义好的页面 在你需要调用接口的页面先引入你写好的界面 页面 去你写好的页面里找movieseatmodel 后面的名字 seat就是你要找的名字 api seat是你写好的接口 seat response是你下
  • awk '{print $2}' 这个命令

    2 表示第二个字段 print 2 打印第二个字段 awk print 2 fileName 一行一行的读取指定的文件 以空格作为分隔符 打印第二个字段 比如有个文件是testAWK txt 文件内容如下 11 22 33 44 55 66
  • Java I/O流实现文件复制

    Java I O流实现文件复制 1 文件复制原理 2 文件遍历算法 1 算法分析 2 算法源码 3 文件复制算法 1 算法分析 2 算法源码 1 文件复制原理 文件的类型有很多 从层次上分 有目录 即文件夹 与普通文件 从内容上分 有文本
  • virtualenv 设置python3环境简明教程

    一开始直接用的是python3环境 而没有安装virtualenv进行不同的python环境版本控制 一 为什么用到virtualenv centos7默认的python版本是2 7的 想在其上做python3的开发会遇到问题 比如要使用p
  • ibm服务器usb虚拟网卡,山石虚拟防火墙安装步骤

    山石虚拟防火墙 可以安装在vmware workstation上 非常适合动手操作实践 做实验等 非常好用 前提也非常容易 电脑支持64位 内存最小4G 山石虚拟防火墙桥接在物理网卡上 虚拟机和虚拟防火墙内网口都桥接在虚拟网卡上 步骤大概如
  • Differences between Thumb and ARM instruction sets

    本文转载至 http infocenter arm com help index jsp topic com arm doc dui0068b ch02s02s09 html The general differences between
  • 动态规划-最大子数组

    题目 给定一个整数数组 找出和最大的子数组 返回其和 例如 1 2 3 5 1 2 最大子数组 3 5 1 2 和为9 分析 利用动态规划 记Sum i 表示以A i 结尾的子数组中的和最大子数组 Sum i 1 呢 考虑Sum i 的情况
  • c++智能指针

    我们在编写c 程序时经常会面临内存泄漏的问题 例如 void f int p new int throw unknow exception delete p 在这个简单的函数中 我们申请了一块内存 但是因为抛出异常跳出函数执行catch函数
  • linux dc命令,Linux中dc命令起什么作用呢?

    摘要 下文讲述Linux中dc的功能说明 如下所示 dc命令是Linux下一个任意精度的计算器 dc命令功能 用于计算操作 dc命令注意事项 1 dc命令支持无限精度运算 2 dc命令可定义及调用宏 3 dc命令可从界面读取数据 也可从指定
  • 验证手机号 身份证号 邮箱号

    public class ValidateUtil 验证手机号格式是否正确 param phone return public static boolean isMobilePhone String phone if StringUtils
  • 导入ecplise项目No projects are found to import解决方案

    最近在clone一个git项目的时候遇到一些文件 首先导入直接显示 No projects are found to import 显然这是ecplise无法正确识别项目类型导致的 这是由于ecplise识别一个工程需要 classpath
  • VS2019安装Qt插件(附安装失败解决方案)

    方法1 1 进入官网下载 qt vsaddin msvc2019 2 4 3 vsix 然后点击运行 2 点击install安装插件 3 等待安装完成Close 打开VS2019 4 在扩展下面就会出现Qt VS Tools 然后进入Qt
  • X电容(差模电容)和Y电容(共模电容)简介

    性质 X电容和Y电容多应用于开关电源当中 都属于安规电容 安规电容是指用于这样的场合 即电容器失效后 不会导致电击 不危及人身安全 X电容 外观容值 多是黄色 方形 uF级别的电容 作用 X电容主要用于抑制差模干扰 位置 接在火线零线之间
  • Java中的LinkedList集合详解

    LinkedList集合 基础概念 可以在任何位置高效插入和删除的一个有序序列 简而言之就是数据结构里的链表 我们都清楚在链表很容易进行插入和删除 但是我们在使用c c 的时候需要新建一个链表项的结构体并且需要在里面设置指针 java不需要
  • IO流(一)

    IO概述 什么是IO java中I O的操作主要是靠java io包下的类和接口来实现的 IO分类 根据数据的流向 输入流和输出流 输入流 把数据从其他设备上读取到内存当中的流 输出流 把数据从内存当中写入到其他设备上的流 根据数据的类型分
  • [C++] 哈希详解

    目录 1 哈希概念 2 实现机制 2 1 插入时 2 2 查找时 2 3 缺陷 2 4 常见哈希函数 2 4 1 直接定制法 2 4 2 除留余数法 2 4 3 平方取中法 2 4 4 折叠法 注意 3 解决哈希冲突 3 1 闭散列 3 1