哈希表初理解
本篇的解释非常非常小白,帮助理解入门。
1. 哈希表是什么
哈希表又叫散列表(Hash table),外国人给起的名字,别念反变成嘻哈表就OK了,哈哈。为了更好的理解哈希表,我们拿字典当例子。小时候大家都查过字典吧,有的同学按照拼音来查,有的同学按照偏旁部首来查,等到熟悉了之后,为了比比谁查的更快,偏旁拼音都不看了,直接凭着abcde…xyz的顺序,大概就能翻到想要的那一页。
咳咳,扯远了。哈希表,它就像一本字典一样,但是它像一维数组一样,是一维的,等到了哈希map,那就复杂了。同时它的功能也并不是简单的查询,而是能完成增删改查。先记住这两点吧
- 维度 :一维
- 功能:增删改查
2. 特别注意哪些参数
还是拿字典来举例子吧。我们在新版的《新华字典》中随机取几个汉字,假设它们所在的页数分别为如下表格:
k | 哈 | 希 | 表 | 很 | 难 |
---|---|---|---|---|---|
f(k) | 23 | 45 | 65 | 56 | 45 |
这里假设‘难希’都在45页。k和f(k)分别代表什么呢?要去新华字典查 希望的 希这个字的解释,假设你只能用拼音方法,Xi 那么X是不是你所要找到关键字呢。你在差字典的时候,字典的目录会返回给你希的页数,希-45。那么,k就是要查找的关键字,f(k) 就是返回给你的页数。在哈希表中,k是关键字,f(k)是返回的地址。
又明确了两点:
- k: 需要查找的关键字
- f(k): 返回地址
3. 哈希冲突
1. 什么是哈希冲突
字典可以一页排版好好几个汉字,在访问45页的时候,希和难同时出现,但是返回值要求只能有一个。那返回值取困难的难,还是取希望的希?这就是冲突。
2. 完全避免冲突的条件
条件有两个,不解释:
- 关键字的个数小于哈希表的长度
- 选择合适的散列函数f(k)
3. 冲突不可能完全避免的原因
因素非常多,主要原因是哈希表的特性,决定了关键字的个数通常要大于散列表的长度。
4. 影响冲突的因素
表的填满程度,f(k)函数
4. 既然有冲突,为什么还要使用哈希表
答:快!看上图表格,时间复杂度O(1)。并且,通过人的设计,冲突可以减少。
5. 散列函数的构造
就是f(k)的构造。
1. 选择标准
- 简单
- 均匀
2. 常见方法
- 平方取中法
- 除留余数法
- 相乘取整法
- 随机函数法
这些方法会在另外的博客上介绍,这里只留一个概念。
6. 代码分析
#include<stdio.h>
#include<stdlib.h>
#define HASHSIZE 12
#define NULLKEY -32768
typedef struct {
int* elem;
int count;
}HashTable;
int m = 0;
//初始化散列表
int InitHashTable(HashTable* H)
{
int i;
m = HASHSIZE;
H->count = m;
H->elem = (int*)malloc(m * sizeof(int));
for (i = 0; i < m; i++)
{
H->elem[i] = NULLKEY;
}
return 1;
}
//散列函数
int Hash(int key)
{
return key % m;
}
//插入关键字进散列表
void InsertHash(HashTable* H, int key)
{
int addr = Hash(key);
while (H->elem[addr] != NULLKEY)
{
addr = (addr + 1) % m;
}
H->elem[addr] = key;
}
//查找指定元素
int SearchHash(HashTable H, int key, int* addr)
{
*addr = Hash(key);
while (H.elem[*addr]!= key)
{
*addr = (*addr + 1) % m;
if (H.elem[*addr] == NULLKEY || *addr == Hash(key))
{
return -1;
}
}
return *addr;
}
int main()
{
int a[12] = { 12,67,56,16,25,37,22,29,15,47,48,34 };
HashTable H;
int i;
InitHashTable(&H);
for(i = 0; i < m; i++)
{
InsertHash(&H,a[i]);
}
printf("插入之后的哈希表为:");
for (i = 0; i < m; i++)
{
printf("%d,", H.elem[i]);
}
int addr, j;
j = SearchHash(H, a[5], &addr);
printf("搜索到a[5]的地址是:%d", j);
}
输出的结果:
插入之后的哈希表为:12,25,37,15,16,29,48,67,56,34,22,47,搜索到a[5]的地址是:2
这段代码先将哈希表初始化,然后将数组中的值通过哈希函数赋值给哈希表。最终返回的是哈希表中a[5],也就是37在表中的地址。散列函数是除留余数法。