哈希(Hash)

一、哈希表

        哈希表,也称为散列表,是一种数据结构,它利用散列函数将关键码值(通常是数据的某个特征)映射到表中一个位置来访问记录,这样可以加快查找和插入的速度

怎么去理解呢?举个简单的例子:

假设现在有个数组a如下:

712348520961

这是一个长度为10的数组,如果我们要查找key = 4是否存在数组a中,原始方法为:

#include <stdio.h>

int a[10] = {7,12,3,4,8,5,20,9,6,1};

int find_key(int a[], int n, int key){
    for(int i = 0; i < n; i++){
        if(key = a[1]){
            return 1;
        }
    }
    return 0;
}

        这种方式虽然简单直接,但是效率很低,如果数组长度为100,1000甚至10000呢,而且如果要同时查找多个数的话,如:

int main()
{
    int a[] = {7,12,3,4,8,5,20,9,6,1};
    //循环查找1,2,3到10,这十个数字是否存在于数值中
    for(int i = 1; i <=10; i++)
    {
        if(find key(a, 10, i)){
            printf('%d is in array.\n', i);
        }
        else{
            printf('%d is not in array.\n', i);
        }
    }
    return 0;
}

         从数组中判断1,2,3到10这10个数字是否存在,每次都需要调用find_key函数,都要比较n次,查询的时间复杂度为O(n),结合查询次数也为n,整体的复杂度就是O(n*2)

        那是否存在一种算法,每次查找的复杂度都为常数级0(1),也就是查找效率与待查表中的元素数量是无关的,这种方法就是哈希表:

60abd7357c2e41a68f5ee7e82fa8e06a.png

关于 哈希表的实现方法我们可以举个例子:

定义数值:a[100] = {0}

使用数组下标记录元素是否存在以及出现次数:
如:a[1] = 1,则1出现了一次

table:

数组参数000000000000...0
下标123456789101112...100

代码实现:

//建立哈希表的函数
void create_hash(int a[], int n, int table[])
{
	//循环遍历数组a中的所有元素
	for (int i = 0; i < n; i++)
	{
		//通过a[i]中的元素对应table数组的下标,并使其参数++
		table[a[i]]++;
	}
}
int main()
{
	int a[] = { 7,12,3,5,8,5,12,12,6,1 };
	//创建哈希表
	int table[100] = { 0 };
	create_hash(a, 10, table);

    //完成哈希表建立,遍历数组
	for (int i = 0;i < 100; i++)
	{
        //如果参数大于0则说明数组a出现过
		if (table[i] > 0)
		{
			printf("%d appear %d times.\n", i, table[i]);
		}
}
数组参数101021110003...0
下标123456789101112...100

代码2:查找哈希表中是否存在key值

int find_key(int table[], int key)
{
	return table[key] != 0;
}
int main()
{
	int a[] = { 7,12,3,4,8,5,20,9,6,1 };
	//创建哈希表
	int table[100] = { 0 };
	create_hash(a, 10, table);

	for (int i = 0;i < 100; i++)
	{
		if (find_key(table, i))
		{
			printf("%d is in a.\n", i );
		}
		else
		{
			printf("%d is not in a.\n", i);
		}
	}
}

二、哈希函数

         以上实例参数类型都为int,如果要使用浮点型,对象等更复杂的数据类型,这需要使用到哈希函数,它通过设计将输入数据映射到固定大小的输出值,这个过程通常被称为散列

意思就是将数据转换为哈希表长范围内的整数,将这个整数作为数组下标,访问哈希表。

如字符串的哈希函数:

#define MAX_TABLE_LEN 100

int string_func(const char* key)
{
	int sum = 0;
	while (*key)//遍历字符串
	{
		sum += *key;//将字符对应的ASCII表的数相加
		key++;
	}
	return sum % MAX_TABLE_LEN;//取余表长的到哈希值
}

如字符串‘hello’,每个字符对应的ASCII码值相加为:h: 104 e: 101 l: 108 l: 108 o: 111

总和为:532

假如表长为100(数组a长度100),532%100 = 32

得到下表为:32

数组参数000000000...1...0
下标12345678910...32...100

还有其他常见实用的哈希函数,简单了解:

  1. MD5数据填充:首先对输入的数据进行填充,确保其长度满足特定条件。具体来说,原始数据会被加上一个特定的值(通常是128位的长度字段和一个64位的初始值),使得填充后的数据长度模512的余数为448。分组处理:将填充后的数据分割成512位(64字节)的小组,每个小组再细分为16个32位的子组。压缩函数:针对每个子组,应用一系列位操作和模运算,这包括逻辑函数、加常数等操作。这些操作的结果会进一步参与下一轮的操作。输出生成:将上述过程中产生的16个32位的中间结果累加,最终得到一个128位的散列值。
  2. SHA-1:它可以产生一个160位的散列值。与MD5相比,它提供了更长的散列值,但同样由于安全性问题

还有SHA-256、SHA-3

        需要注意的是,为了避免内存资源的浪费,我们制定表长时合理制定。

设定哈希表长度的通用指导原则:

  • 避免频繁扩容:哈希表的长度通常是固定的,且在存储数据前应预先确定。合理的大小能够减少因频繁扩容造成的性能损耗。
  • 减少哈希冲突:理想情况下,哈希表的大小应当足够大,以减少数据项之间的冲突,从而提高存取效率。
  • 空间利用率:同时,过大的哈希表会浪费内存空间。因此,大小选择需要平衡冲突率与空间使用率,确保高效利用内存资源。
  • 素数选择:通常建议将哈希表的大小设为一个素数,因为当哈希表的总长度为素数时,能够帮助分散哈希值,降低冲突的可能性。

所以对于超出表长的哈希值,我们同样可以进行取余

二、哈希冲突

        从上面的例子我们可以发现一个问题,字串串‘hello’和整形532,或者字符串‘hello’和‘helol’,他们经过哈希函数得到的哈希值都是相同的,映射到同一个数组下标。这不就冲突了吗?

        这就是哈希冲突,即不同的数据项通过同一个哈希函数产生了相同的哈希值,从而映射到哈希表中相同的位置。

        解决冲突的方法有多种,常见的比如开放地址法,链地址法等,简单解释:

  1. 开发地址法:如果两个键的哈希值相同,那么后插入的元素会查找哈希表中下一个空闲的位置,查找的过程通常是线性探测(单向寻找)、二次探测(前后寻找)或双重散列。
  2. 链地址法: 将每个哈希表的槽位作为一个链表的头结点。当发生冲突时,具有相同哈希值的元素会被添加到相应槽位的链表中。

总结,哈希算法出了可以快速查找,还有其他实用的地方:

  1. 安全加密:哈希算法在安全加密领域得到了广泛应用。例如,用户密码的存储通常会使用MD5或SHA等哈希函数进行加密,由于其不可逆的特性,即使数据被盗取,也难以破解原始密码,从而保障了安全性。
  2. 唯一标识:哈希算法能够将数据映射为固定长度的字符串,这可以用来生成数据的唯一标识。例如,通过MD5处理URL或图片字段,可以得到32位长度的唯一标识,这有助于数据库索引构建和查询,以及快速判定重复文件等场景。
  3. 数据校验:在网络上下载文件时,常常会用到MD5值来校验数据的完整性。这样可以避免数据在传输过程中被篡改或劫持,确保用户下载到的文件是完整且未经修改的。
  4. 散列函数:在编程实践中,哈希算法常用于构建哈希表等数据结构,以实现快速的查找和访问。通过哈希函数将键转换为数组索引,可以快速定位到相应的值。
  5. 区块链技术:区块链的底层原理之一就是哈希算法。在区块链中,哈希算法用于确保每个区块的唯一性和完整性,同时也连接着区块链中的前后区块,形成了一个不可篡改的链条。
  • 23
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值