【数据结构与算法】初探哈希表

哈希表的存在是为了能够以O(1)平均时间复杂度插入和读取数据。


参考《STL源码剖析》和《数据结构与算法分析——C语言描述》两本书,总算让我对哈希表有了一个基本的认识。

谈到哈希表就会谈到冲突,两本书无疑都介绍了线性探测,二次探测等方法来解决冲突,不过在SGI STL库里,我们更常常用开放定址法(开链法)来解决冲突。

SGI STL 里面的 hashtable 的具体实现是:
1)由一个bucket数组组成;
2)每个bucket下面挂着一个hash_node组成的list;
3)每个hash_node由一个Val对象(存储真正元素)和一个hash_node指针(next指针)组成。


hashtable的工作过程是:
1)将key用HashFcn进行hash;
2)将hash的结果执行取模操作%n(其中n是hashtable中bucket的数目),定位到具体bucket的位置;
3)依次用EqualKey比较bucket中hash_node的key,找到与输入元素相同的node,返回;若找不到,则新添加一个node返回。


STL里实现了 hashtable ,并以它为基础实现了 hash_set, hash_map, hash_mutiset, hash_mutimap等,为了讲解简单,我们以 hash_set  为例讲解。hash_set 这个模板类有四个参数:

template<class Value,                                         //hast_set的key值就是value值
                class HashFcn = hash<Value>,                  //hash仿函数,用于执行真正的hash操作。有默认模板参数hash<Value>
                class EqualKey = equal_to<Value>,             //比较仿函数,用于执行hash冲突后,bucket内的find工作。有默认模板参数equal_to<Value>
                class Alloc = alloc>                          //内存分配器。有默认模板参数参数
class hash_set{...}


SGI STL 里提供了针对 char, short, int, long等整数型别以及它们的 signed 和 unsigned 版本( 其他类型不能处理,如string, float, double等,这些需要自定义哈希函数和比较函数)的哈希函数,不过这些函数其实什么也没做,只是忠实返回原值,但对于字符串类型(const char *),就设计了一个简单的转换函数如下:

//以下定义于<stl_hash_fun.h>
template <class Key> struct hash{  };
inline size_t __stl_hash_string(const char *s)
{
  unsigned long h = 0;
  for(; *s; ++s)
    h = 5 * h + *s;

  return size_t(h);
}

这样一看确实是有够简单的,考虑到ASCII表从0~127的范围,同一hash值可能对应不同字符串。此外,对于字符串类型,hash函数使用如上自带的可以,但比较仿函数不能使用自带的 eqial_to<T>,因为该方法只会直接对比指针是否相等,而不会一个字符一个字符的比较,所以我们可以使用 strcmp()函数制作如下比较方法:

struct cmp
{
  bool operator()(const char *s1, const char *s2) const
  {
    return strcmp(s1, s2) == 0;
  }
}

如此,查找函数可以利用find():

void lookup(const hash_set<const char *, hash<const char *>, cmp>&se, const char *word)
{
  hash_set<const char *, hash<const char *>, cmp>::const_iterator iter = se.find(word);
  cout << "  " << word << ": " << (iter != se.end() ? "present" : "not present") << endl;
}

下面举例如下:

#include <iostream>
#include <string.h>
#include <hash_set>
using namespace std;
using namespace __gnu_cxx;

struct cmp
{
	bool operator()(const char *s1, const char *s2) const
	{
		return strcmp(s1, s2) == 0;
	}
};


int main()
{
	cout << "------------------Test 1------------------" << endl;
	hash_set<const char*> se;
	hash_set<const char*>::const_iterator iter;
	
	se.insert("kiwi");
	char c1[] = "kiwi";
	char *c2 = "kiwi";
	iter = se.find(c1);
	if(iter != se.end())
	{
		cout << "Find c1" << endl;
	}
	iter = se.find(c2);
	if(iter != se.end())
	{
		cout << "Find c2" << endl;
	}

	cout << "------------------Test 2------------------" << endl;
	hash_set<const char*, hash<const char*>, cmp> newSe;
	hash_set<const char*, hash<const char*>, cmp>::const_iterator newIter;

	newSe.insert("kiwi");
	char c3[] = "kiwi";
	char *c4 = "kiwi";
	newIter = newSe.find(c3);
	if(newIter != newSe.end())
	{
		cout << "Find c3" << endl;
	}
	newIter = newSe.find(c4);
	if(newIter != newSe.end())
	{
		cout << "Find c4" << endl;
	}

	return 0;
}

输出结果如下:

------------------Test 1------------------
Find c2
------------------Test 2------------------
Find c3
Find c4

果然,缺省的equal_to<Value>确实是仅仅对比指针值,必须要使用strcmp()代替之。


下面演示下为 string 添加 hash 函数:

#include <iostream>
#include <string.h>
#include <hash_set>
using namespace std;
using namespace __gnu_cxx;

namespace __gnu_cxx{
template<> struct hash<string>
{
	size_t operator()(const string &str) const
	{
		size_t res = 0;
		for(int i = 0; i < str.size(); ++i)
			res = res * 5 + str[i];

		return res;
	}
};
}

int main()
{
	hash_set<string, hash<string>, equal_to<string> > seString; 
	hash_set<string, hash<string>, equal_to<string> >::const_iterator iter; 
	seString.insert(string("kiwi"));
	seString.insert(string("plum"));
	seString.insert(string("apple"));
	seString.insert(string("mango"));
	seString.insert(string("apricot"));
	seString.insert(string("banana"));

	for(iter = seString.begin(); iter != seString.end(); ++iter)
	{
		cout << *iter << endl;
	}

	return 0;
}


此hash函数与hash<const char *>相同,故以上输出与《STL源码剖析》P274的输出结果一样。



几点我也不太清楚的地方:

1、命名空间问题;

2、为什么有默认的 equal_to<string>


这里特别要注意的是在 hash_set 里保存的 node 的 key 是 const char *,这里就涉及到一个内存管理的问题,我们要确保之前insert时用的 const char* 不能失效,而且内容不能被更改,否则在bucket内比对key时就会出现严重的问题,轻则找不到元素,甚至dump掉。这个注意点参考:尽量不用char*作为hash_map的key

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
哈希表是一种常用的数据结构,它通过哈希函数将键映射到存储位置,以实现高效的数据查找和插入操作。哈希函数是一种提取数据特征的算法,根据不同的数据形式和场景,可以选择不同的哈希算法。常见的哈希算法包括MD5等。\[1\] 在哈希表中,哈希函数的优劣直接影响到哈希表的查找效率。优秀的哈希函数可以减少冲突的发生,提高查找效率。哈希函数的设计方法有多种,其中常见的包括直接寻址法、除留余数法、平方取中法等。不同的哈希函数适用于不同的数据类型和规律。\[3\] 哈希冲突是指不同的键经过哈希函数计算后得到相同的哈希值,导致数据存储位置冲突的情况。为了解决哈希冲突,常用的方法有开放寻址法和链地址法。开放寻址法是指当发生冲突时,通过一定的规则在哈希表中寻找下一个可用的位置来存储数据。链地址法是指在哈希表的每个位置上维护一个链,将哈希值相同的键值对存储在同一个链中。\[2\] 总结来说,哈希表是一种通过哈希函数将键映射到存储位置的数据结构,常用的哈希算法有多种,哈希函数的设计方法也有多种,而哈希冲突的处理方法包括开放寻址法和链地址法。这些算法和数据结构的选择取决于具体的应用场景和需求。 #### 引用[.reference_title] - *1* [详解哈希数据结构,手写哈希表](https://blog.csdn.net/CRMEB/article/details/120820682)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [数据结构之哈希表以及常用哈希的算法达(含全部代码)](https://blog.csdn.net/weixin_53050357/article/details/126666617)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [哈希表-数据结构(C语言)](https://blog.csdn.net/weixin_44681349/article/details/124782035)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值