[C国演义] 哈希的使用和开闭散列的模拟实现

news2024/11/23 18:37:15

哈希的使用和开闭散列的模拟实现

  • 1. 使用
    • 1.1 unordered_map的接口
    • 1.2 unordered_set的接口
  • 2. 哈希底层
    • 2.1 概念
    • 2.2 解决哈希冲突
  • 3. 实现
    • 3.1 开放寻址法
    • 3.2 拉链法

1. 使用

1.1 unordered_map的接口

  1. 构造
void test1()
{
    // 空的unordered_map对象
    unordered_map<int, int> m1(10);
    cout << "桶的实际个数->" << m1.bucket_count() << endl;

    // 用列表初始化来进行初始化
    unordered_map<int, int> m2{ {1,1}, {2,2}, {3,3} };
    cout << "列表初始化-> " << endl;
    for (const auto& e : m2)
    {
        cout << e.first << " " << e.second << endl;
    }
    cout << endl;

    // 迭代器区间初始化
    unordered_map<int, int> m3(m2.begin(), m2.end());
    cout << "迭代器区间初始化-> " << endl;
    for (const auto& e : m2)
    {
        cout << e.first << " " << e.second << endl;
    }
    cout << endl;
}

int main()
{
    test1();

    return 0;
}

运行结果 :

桶的实际个数->16
列表初始化->
1 1
2 2
3 3

迭代器区间初始化->
1 1
2 2
3 3
  1. 容量
  2. 元素访问
void test2()
{
    unordered_map<string, int> mat{ {"小呆呆", 1}, {"波比", 2} };
    cout << "初始化-> " << endl;
    for (const auto& e : mat)
    {
        cout << e.first << " " << e.second << endl;
    }
    cout << endl;

    mat["猪猪侠"];
    cout << "插入功能-> " << endl;
    for (const auto& e : mat)
    {
        cout << e.first << " " << e.second << endl;
    }
    cout << endl;

    mat["波比"] = 5;
    cout << "修改功能-> " << endl;
    for (const auto& e : mat)
    {
        cout << e.first << " " << e.second << endl;
    }
    cout << endl;

    mat["超人强"] = 6;
    cout << "插入 + 修改功能-> " << endl;
    for (const auto& e : mat)
    {
        cout << e.first << " " << e.second << endl;
    }
    cout << endl;
}

int main()
{
    test2();

    return 0;
}

运行结果 :

初始化->
小呆呆 1
波比 2

插入功能->
小呆呆 1
猪猪侠 0
波比 2

修改功能->
小呆呆 1
猪猪侠 0
波比 5

插入 + 修改功能->
小呆呆 1
猪猪侠 0
波比 5
超人强 6
  1. 查询
void test3()
{
    unordered_map<string, int> mat{ {"小呆呆", 1}, {"波比", 2} };
    mat["猪猪侠"] = 5;
    mat["猪猪侠"] = 7;
    size_t cnt = mat.count("猪猪侠");
    cout << cnt << endl;

    auto it = mat.find("小呆呆");
    if (it != mat.end())
    {
        cout << it->first << " is " << it->second << endl;
    }
    else
    {
        cout << "查无元素! " << endl;
    }
    cout << endl;

    auto git = mat.find("超人强");
    if (git != mat.end())
    {
        cout << git->first << " is " << git->second << endl;
    }
    else
    {
        cout << "查无元素! " << endl;
    }

}

int main()
{
    test3();

    return 0;
}

运行结果 :

1
小呆呆 is 1

查无元素!
  1. 修改
void test4()
{
    unordered_map<string, int> mat1{ {"小呆呆", 1}, {"波比", 2} };

    // 插入
    mat1.insert({ "小呆呆", 5 });
    mat1.insert({ "超人强", 6 });
    for (const auto& e : mat1)
    {
        cout << e.first << " " << e.second << endl;
    }
    cout << endl;

    // 删除
    mat1.erase("波比");
    mat1.erase("猪猪侠");
    for (const auto& e : mat1)
    {
        cout << e.first << " " << e.second << endl;
    }
    cout << endl;

    // 交换
    unordered_map<string, int> mat2{ {"迪迦",1}, {"戴拿",2}};
    mat1.swap(mat2);
    cout << "交换后的mat1-> " << endl;
    for (const auto& e : mat1)
    {
        cout << e.first << " is " << e.second << endl;
    }
    cout << endl;

    cout << "交换后的mat2-> " << endl;
    for (const auto& e : mat2)
    {
        cout << e.first << " is " << e.second << endl;
    }
}

int main()
{
    test4();

    return 0;
}

运行结果 :

小呆呆 1
波比 2
超人强 6

小呆呆 1
超人强 6

交换后的mat1->
迪迦 is 1
戴拿 is 2

交换后的mat2->
小呆呆 is 1
超人强 is 6
  1. 桶操作
void test5()
{
    unordered_map<string, int> mat1{ {"小呆呆", 1}, {"波比", 2}, {"迪迦", 3} };

    // 桶的个数
    cout << mat1.bucket_count() << endl << endl;

    // 每个桶的有效个数
    for (int i = 0; i < mat1.bucket_count(); i++)
    {
        printf("[%d] -> %d\n", i, mat1.bucket_size(i));
    }
    cout << endl;

    // 各个key所在的桶
    for (const auto& e : mat1)
    {
        cout << mat1.bucket(e.first) << endl;
    }
}

int main()
{
    test5();

    return 0;
}

运行结果 :

8

[0] -> 1
[1] -> 0
[2] -> 1
[3] -> 0
[4] -> 0
[5] -> 0
[6] -> 1
[7] -> 0

2
6
0

1.2 unordered_set的接口

unordered_set 和 unordered_map的接口大致一样, 但是没有 operator [ ]

2. 哈希底层

2.1 概念

unordered_set 和 unordered_map的效率高的原因 ⇒ 底层是哈希结构

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

使元素的存储位置和关键码建立一 一映射的关系, 这个方法称为 哈希(散列)方法,
通过一个函数使得存储位置和关键码建立一 一映射的关系, 这个函数称为 哈希函数,
最终形成的结构, 称为 哈希(散列)表, HashTable
不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为 哈希冲突或哈希碰撞

  • 先浅浅地看一下哈希结构, 来理解一下概念

  • 常见的哈希函数
    哈希冲突的一个重要原因就是 哈希函数设置的不好
    那么, 我们来了解一下最常见的两个哈希函数

    1. 直接寻址法 : 一个key对应一个位置
      前提 : 知道数据集合的大小 和 分布情况
      适合场景: 数据量小且均匀

    2. 除留余数法 : 准备一个基准值去估计数据量的多少, 设为m, 采用 hash(key) = key % m的方法去建立元素和下标的一 一 映射关系

  • 采用直接寻址法 — — 数据量小 且 集中
    字符串中第一个唯一字符

class Solution {
public:
    int firstUniqChar(string s) 
    {
        // <s中的每个字符, 个数>
        int hash[26] = {0};

        // 映射
        for(auto e : s)
        {
            hash[e-'a']++;
        }

        // 查找
        for(int i = 0; i < s.size(); i++)
        {
            if(hash[s[i] - 'a'] == 1)
            {
                return i;
            }
        }
        return -1;
    }
};
  • 采用除留余数法 — — 任何场景下都可
    下面的 哈希冲突解决 和 实现 都是采用的除留余数法

  • 哈希函数设置的越巧妙, 哈希冲突就越低, 但是 哈希冲突无法避免

2.2 解决哈希冲突

解决哈希冲突主要有两种方法 : 闭散列 和 开散列

  1. 闭散列
    闭散列, 也叫 开放寻址法,
    思路是 : 当冲突发生时, 必然有空位置, 那么把冲突的元素放到 "下一个空位置" 即可!
  • 插入逻辑

  • 查找逻辑

    • 这从另一方面也体现了 哈希表的有效数据不应该占比太大 ⇒ 否则就是遍历这个哈希结构, O(N)
      但是 也不能占比太少 ⇒ 浪费空间
      一般, 控制 有效个数 / 哈希结构的大小 在 [0.7, 0.8]的范围内是比较合理的
  • 删除逻辑
    首先, 能确定的是不能直接把这个位置去掉

    那么该位置要进行保留, 那么 值该怎么处理呢 ?
    改为 0, -1 … … 等无意义的数值?
    其实这些都是不行的, 你怎么知道你修改后的数据是无意义的呢 ⇒ 能确定的是 该位置的值也要进行保留
    该位置要进行保留, 值也要进行保留 && 不能影响后面的查找逻辑 那么该怎么把它删掉呢? ⇒ 引入每个下标的状态 : 删除状态, 空白状态, 存在状态

  • 由于删除逻辑而导致新的插入逻辑

  • 由于删除逻辑而导致新的查找逻辑

  1. 开散列
    开散列, 又叫作 拉链法
    上面的 开放地址法 解决哈希冲突的办法是 将经过哈希函数处理过的 相同的key, "延后落座"
    拉链法的解决思路是 将经过哈希函数处理的 相同的key 放到一个单链表中, 然后将每一个单链表的头结点放到一个数组里面. 本质是一个 指针数组
  • 这里的插入删除, 查找逻辑就是在 key那个桶进行单链表操作

🗨️ 有同学就会说, 这不是单链表操作吗, 不过如此!

  • 我们可以控制 有效数据个数 / 桶的大小 = 1 ⇒ 平均下来就是一个桶一个数据

3. 实现

这里都先实现 数据位pair<K, V>类型的

3.1 开放寻址法

  1. STATE类型
enum STATE
{
	EXIT,
	DELETE,
	EMPTY
};
  1. HashData类
template<class K, class V>
struct HashData
{

public:
	HashData()
	{}

	HashData(const pair<K, V>& kv)
		:_data(kv)
	{}

public:
	pair<K, V> _data;
	STATE _st = EMPTY;
};
  1. Hash类
template<class K, class V, class Com = DEFAULT<K>>
class hash
{

public:
	hash()
	{
		// 1. 先给4个空间
		// 2. size 和 capacity一样大
		_table.resize(4);
	}

	bool insert(const pair<K, V>& kv)
	{
		// 扩容逻辑
		if ((double)_sz / _table.size() >= 0.7)
		{
			size_t newsize = _table.size() * 2;
			hash<K, V> new_ht;
			new_ht._table.resize(newsize);

			// 挪动数据
			for (size_t i = 0; i < _table.size(); i++)
			{
				// 不用挪动删除状态的值
				if (_table[i]._st == EXIT)
				{
					new_ht.insert(_table[i]._data);
				}
			}

			std::swap(*this, new_ht);
		}

		// 线性探测
		for (const auto& e : _table)
		{
			if (kv.first == e._data.first)
			{
				return false;
			}
		}
		size_t hashi = com(kv.first) % _table.size();
		while (_table[hashi]._st == EXIT)
		{
			++hashi;
			hashi %= _table.size();
		}

		_sz++;
		_table[hashi] = kv;
		_table[hashi]._st = EXIT;

		return true;
	}
	
	// 返回有key,
	// 不允许用户在外面更改key,
	// 所以返回<const K, V>*
	HashData<const K, V>* find(const K& key)
	{
		size_t hashi = com(key) % _table.size();
		while (_table[hashi]._st != EMPTY)
		{
			if (_table[hashi]._st == EXIT &&  _table[hashi]._data.first == key)
			{
				return (HashData<const K, V>*)&_table[hashi];
			}

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

		return nullptr;
	}
	

	bool erase(const K& key)
	{
		// 复用find
		HashData<const K, V>* res = find(key);

		if (res)
		{
			res->_st = DELETE;
			_sz--;
			return true;
		}
		else
		{
			return false;
		}

		//for (auto e : _table)
		//{
		//	if (e._data.first == key)
		//	{
		//		e._st = DELETE;
		//		_sz--;
		//		return true;
		//	}
		//}

		//return false;
	}

private:
	vector<HashData<K, V>> _table;
	size_t _sz = 0;
	Com com;
};
  1. DEFAULT — 通过仿函数来解决 字符串 不能进行 % 的问题
// 通过仿函数来解决 字符串 不能进行 % 
template<class K>
struct DEFAULT
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

// 模版的特化 -- 全特化
// 解决 字符串问题
template<>
struct DEFAULT<string>
{
	size_t operator()(const string& key)
	{
		int res = 0;
		for (auto e : key)
		{
			res += e * 131;
		}

		return res;
	}
};

3.2 拉链法

  1. HashData类
template<class K, class V>
struct HashData
{
public:
HashData(const pair<K, V>& kv)
	:_data(kv)
{}

public:
pair<K, V> _data;
HashData<K, V>* _next;
};
  1. Hash类
template<class K, class V, class Com = DEFAULT<K>>
class hash
{
	typedef HashData<const K, V> Node;
public:

	hash()
	{
		_table.resize(4, nullptr);
	}

	Node* find(const K& key)
	{
		size_t hashi = com(key) % _table.size();
		Node* cur = _table[hashi];
		while (cur)
		{
			if (com(cur->_data.first) == com(key))
			{
				return cur;
			}

			cur = cur->_next;
		}

		return nullptr;
	}

	bool insert(const pair<K, V>& kv)
	{
		Node* res = find(kv.first);
		if (res)
		{
			return false;
		}

		// 扩容逻辑
		if (_sz == _table.size())
		{
			vector<Node*> new_table;
			new_table.resize(_table.size() * 2, nullptr);
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				
				// 顺手牵走这个桶的内容
				while (cur)
				{
					// 提前保存 next, 后面会改变的
					Node* next = cur->_next;

					size_t hashi = com(cur->_data.first) % new_table.size();
					// 先让cur链接上新表中该桶的内容
					cur->_next = new_table[hashi];
					// 再让cur成为新表中该桶的头节点
					new_table[hashi] = cur;

					cur = next;
				}
			}
			
			_table.swap(new_table);
		}

		// 插入逻辑
		size_t hashi = com(kv.first) % _table.size();
		Node* newnode = new Node(kv);

		newnode->_next = _table[hashi];
		_table[hashi] = newnode;

		++_sz;
		return true;
	}

	bool erase(const K& key)
	{
		Node* res = find(key);
		if (res == nullptr)
		{
			return false;
		}
		else
		{
			size_t hashi = com(key) % _table.size();
			Node* cur = _table[hashi];
			Node* prev = nullptr;
			while (cur)
			{
				if (cur->_data.first == key)
				{
					if (prev == nullptr)
					{
						_table[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}

				}

				prev = cur;
				cur = cur->_next;
			}

			--_sz;
			delete cur;
		}

		return true;
	}

	void print()
	{
		for (int i = 0; i < _table.size(); i++)
		{
			Node* cur = _table[i];
			printf("[%d]->", i);
			while (cur)
			{
				printf("%d", cur->_data.first);
				cur = cur->_next;
			}
			cout << "NULL" << endl;
		}
		cout << endl;
	}

private:
	vector<Node*> _table;
	size_t _sz = 0;
	Com com;
};
  1. DEFAUL — 通过仿函数来解决 字符串 不能 % 的问题
// 通过仿函数来解决 字符串 不能进行 % 
template<class K>
struct DEFAULT
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

// 模版的特化 -- 全特化
// 解决 字符串问题
template<>
struct DEFAULT<string>
{
	size_t operator()(const string& key)
	{
		int res = 0;
		for (auto e : key)
		{
			res += e * 131;
		}

		return res;
	}
};

无心买酒谒青春,对镜空嗟白发新。
花下少年应笑我,垂垂羸马访高人。
— — 岳飞 <过张溪赠张完>

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

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

相关文章

智能配电系统解决方案

智能配电系统解决方案是一种集成了先进技术和智能化功能的配电系统&#xff0c;它能够提高电力系统的效率、可靠性和安全性。力安科技智能配电系统解决方案依托电易云-智慧电力物联网&#xff0c;具体实施的方案如下&#xff1a; 智能化设备和传感器&#xff1a;采用智能化的开…

安全框架springSecurity+Jwt+Vue-1(vue环境搭建、动态路由、动态标签页)

一、安装vue环境&#xff0c;并新建Vue项目 ①&#xff1a;安装node.js 官网(https://nodejs.org/zh-cn/) 2.安装完成之后检查下版本信息&#xff1a; ②&#xff1a;创建vue项目 1.接下来&#xff0c;我们安装vue的环境 # 安装淘宝npm npm install -g cnpm --registryhttps:/…

招聘小程序源码 人才招聘网源码

招聘小程序源码 人才招聘网源码 求职招聘小程序源码系统是一种基于微信小程序的招聘平台&#xff0c;它可以帮助企业和求职者快速、方便地进行招聘和求职操作。 该系统通常包括以下功能模块&#xff1a; 用户注册和登录&#xff1a;用户可以通过微信小程序注册和登录&#…

H5ke11--1登录界面一直保存--用本地localStorage存储

目录 代码详解 localStage优点 :一直保存着 注意事项: storage属性们 代码详解 ke8学校陈老师H5-CSDN博客文章浏览阅读76次。实现H5中新增的三个元素&#xff1a;forEach的使用方法。https://blog.csdn.net/m0_72735063/article/details/134019012即此之后 当然可以分为按…

Linux inotify 文件监控

Linux 内核 2.6.13 以后&#xff0c;引入了 inotify 文件系统监控功能&#xff0c;通过 inotify 可以对敏感目录设置事件监听。这样的功能被也被包装成了一个文件监控神器 inotify-tools。 使用 inotify 进行文件监控的过程&#xff1a; 创建 inotify 实例&#xff0c;获取 i…

【从入门到起飞】JavaSE—IO流(1)字节输入流字符输出流

&#x1f38a;专栏【JavaSE】 &#x1f354;喜欢的诗句&#xff1a;天行健&#xff0c;君子以自强不息。 &#x1f386;音乐分享【如愿】 &#x1f384;欢迎并且感谢大家指出小吉的问题&#x1f970; 文章目录 &#x1f33a;概述&#x1f33a;作用&#x1f33a;分类&#x1f33…

如何去开发一个springboot starter

如何去开发一个springboot starter 我们在平时用 Java 开发的时候&#xff0c;在 pom.xml 文件中引入一个依赖就可以很方便的使用了&#xff0c;但是你们知道这是如何实现的吗。 现在我们就来解决这一个问题&#xff01; 创建 SpringBoot 项目 首先我们要做的就是把你想要给别…

Wireshark TS | 应用传输缓慢问题

问题背景 沿用之前文章的开头说明&#xff0c;应用传输慢是一种比较常见的问题&#xff0c;慢在哪&#xff0c;为什么慢&#xff0c;有时候光从网络数据包分析方面很难回答的一清二楚&#xff0c;毕竟不同的技术方向专业性太强&#xff0c;全栈大佬只能仰望&#xff0c;而我们…

【Spring篇】使用注解进行开发

&#x1f38a;专栏【Spring】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【如愿】 &#x1f970;欢迎并且感谢大家指出小吉的问题 文章目录 &#x1f33a;原代码&#xff08;无注解&#xff09;&#x1f384;加上注解⭐两个注…

20231117在ubuntu20.04下使用ZIP命令压缩文件夹

20231117在ubuntu20.04下使用ZIP命令压缩文件夹 2023/11/17 17:01 百度搜索&#xff1a;Ubuntu zip 压缩 https://blog.51cto.com/u_64214/7641253 Ubuntu压缩文件夹zip命令 原创 chenglei1208 2023-09-28 17:21:58博主文章分类&#xff1a;LINUX 小工具 文章标签命令行压缩包U…

打不开github网页解决方法

问题&#xff1a; 1、composer更新包总是失败 2、github打不开&#xff0c;访问不了 解决方法&#xff1a;下载一个Watt Toolkit工具&#xff0c;勾选上&#xff0c;一键加速就可以打开了。 下载步骤&#xff1a; 1、打开网址&#xff1a; Watt Toolkit 2、点击【下载wind…

Python (十一) 迭代器与生成器

迭代器 迭代器是访问集合元素的一种方式&#xff0c;可以记住遍历的位置的对象 迭代器有两个基本的方法&#xff1a;iter() 和 next() 字符串&#xff0c;列表或元组对象都可用于创建迭代器 字符串迭代 str1 Python str_iter iter(str1) print(next(str_iter)) print(next(st…

原型网络Prototypical Network的python代码逐行解释,新手小白也可学会!!-----系列2

文章目录 一、原始代码二、每一行代码的详细解释 一、原始代码 labels_trainData ,labels_testData load_data() wide labels_trainData[0][0].shape[0] length labels_trainData[0][0].shape[1] for label in labels_trainData.keys():labels_trainData[label] np.reshap…

FastJsonAPI

maven项目 pom.xml <dependencies><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.26</version></dependency><dependency><groupId>junit</groupId>&l…

vmware17 虚拟机拷贝、备份、复制使用

可以在虚拟机运行的情况下进行拷贝 查看新安装的虚拟机位置 跳转到上一级目录 复制虚拟机 复制虚拟机整个目录 删除lck文件&#xff0c;不然开机的时候会报错 用vmware 打开新复制的虚拟机 lck文件全部删除 点击开机 开机成功

软磁交流测试仪系统磁参量指标

1. 主要应用 2. 软磁交流测试仪磁参量指标 被测参数 最佳测量不确定度 ( k 2 ) 1 kHz 最佳测量重复性 主要动态磁特性参数 Ps 2.0% 1.0% μa 3.0% 1.0% Bm 1.0% 0.5% Hm 1.0% 0.5% δ 5.0% 1.5% 其他磁特性参数供参考 Br 2.0% 1.0% Hc 3.0% 1.0% μ…

振南技术干货集:比萨斜塔要倒了,倾斜传感器快来!(6)

注解目录 1、倾斜传感器的那些基础干货 1.1 典型应用场景 &#xff08;危楼、边坡、古建筑都是对倾斜敏感的。&#xff09; 1.2 倾斜传感器的原理 1.2.1 滚珠式倾斜开关 1.2.2 加速度式倾斜传感器 1)直接输出倾角 2)加速度计算倾角 3)倾角精度的提高 &#xff08;如果…

微积分在神经网络中的本质

calculus 在一个神经网络中我们通常将每一层的输出结果表示为&#xff1a; a [ l ] a^{[l]} a[l] 为了方便记录&#xff0c;将神经网络第一层记为&#xff1a; [ 1 ] [1] [1] 对应的计算记录为为&#xff1a; a [ l ] &#xff1a; 第 l 层 a [ j ] &#xff1a; 第 j 个神经…

How to import dgl-cu113 如何导入 dgl-cu113

参考这个 从How to import dgl-cu113 如何导入 dgl-cu113https://discuss.dgl.ai/t/how-to-import-dgl-cu113/3381https://discuss.dgl.ai/t/how-to-import-dgl-cu113/3381

vscode 推送本地新项目到gitee

一、gitee新建仓库 1、填好相关信息后点击创建 2、创建完成后复制 https&#xff0c;稍后要将本地项目与此关联 3、选择添加远程存储库 4、输入仓库地址&#xff0c;选择从URL添加远程存储仓库 5、输入仓库名称&#xff0c;确保仓库名一致