- 数据结构:哈希表:冲突处理有哪几种方法?其中链地址法有什么缺点吗?怎么改进?
链地址法缺点:1.时间复杂度较其他的高(因为可能要遍历链表)2.有的关键字得到的哈希地址频次交高,则该节点对于的链表就非常长?怎么改进?(再哈希法!)
首先你要知道哈希表是干嘛的啊?!
哈希表是一种查找表!哈希函数、地址冲突、平均查找长度、装填因子等概念!
一旦哈希表建立好了,一来一个关键字,我用哈希函数一算就可与算出其在表中的地址,就可与直接去该地址处看这个关键字对应的元素存在吗?如果存在就找到了,如果不存在则查找失败!
哈希函数表的定义:
根据设定的哈希函数H(key)和一种冲突处理方法将一组关键字映像到一个有限连续的地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储位置,这种表便是哈希表,这一映像过程称为哈希造表或散列,所得存储位置成为哈希地址或散列地址!
-
- 哈希函数的构造方法
- 直接定址法
- 数字分析法
- 平方取中法
- 折叠法
- 除留余数法
- 随机数法
选择哈希函数要考虑的因素:
- 哈希函数的复杂度(计算哈希函数所需要时间)
- 关键字的长度
- 哈希表的大小
- 关键字的分布情况
- 记录的查找频率
2.哈希表是一个压缩映像,所有会出现冲突。
假设哈希表的地址集为0~(n-1),冲突是指由关键字得到的哈希地址为j(0<=k<=n-1)的位置上已经存在有记录,则“处理冲突“就是为该关键字的记录找到另外一个”空“的哈希地址。在处理冲突的过程中可能得到一个地址序列Hi,i=1,2,...,k,(Hi属于[0,n-1]),依次类推,直到Hk不发生冲突为止,则Hk为记录在表中的地址。
处理冲突的方法:
1)开发定址法
Hi=(H(key)+di)MODm i=1,2,...,k(k<=m-1)
H()为哈希函数,k为哈希表长度,di为增量序列:
di=1,2,3,...,m-1,成为线性探测再散列法
di=12,-12,22,...,+-k2(k<=m/2)称为二次探测再散列法
di=伪随机数序列,则称为随机探测再散列法。
线性探测再散列法容易引起“二次聚集“现象。
2)再哈希法
Hi=RHi(key) i=1,2,...,k
RHi均是不同的哈希函数。
3)链地址法
将所有关键字为同义词的记录(具有相同函数值的关键字对该哈希函数来说称为同义词synonym)存储在同一线性链表中。假设某个哈希函数产生的哈希地址在区间[0,m-1]上,则设立一个指针型向量
Chain ChainHash[m];
其每个分量的初始状态都为空指针。凡哈希地址为i的记录都插入到头指针为ChainHash[i]的链表中,在链表中插入的位置要保证同义词在同一线性列表中按关键字有序(以便进行二分查找)
4)建立公共溢出区
HashTable[0..m-1]为基本表
OverTable[0...v]为溢出表
参考
[1] 严蔚敏, 吴伟民. 数据结构(C语言版)[J]. 计算机教育, 2012, No.168(12):62-62.
[2]STL的map、hash_map的使用https://blog.csdn.net/q_l_s/article/details/52416583
2.哈希表应该的一个例子:《剑指offer》第5章优化时间和空间效率
面试题50:求一字符串中只出现一次的字符
/*
《剑指offer》求一个字符串中第一个只出现一次的字符:
如输入"abaccdeff"则应该输出:'b'
*/
//法2:哈希表映射法:时间复杂度O(n),空间复杂度O(1)
//字符出现了多少次?:统计的思维(很优雅)
/*
我们定义哈希表的键是字符的ASCII值,值是该字符出现的次数。
只需扫描字符串两次:
第一次是统计每个字符出现的次数。
第二次是将第一个只出现一次的字符输出。
处理特殊情况:没有只出现一次的字符,每个字符都出现了两次或两次以上。
*/
char getFirstNotRepeatingChar(string str)
{
if (str.size() == 0)
return '\0';
//ASCII码总共有256个
const int hashTableSiez = 256;
unsigned int* hashTable = new unsigned int[hashTableSiez]();//数组元素全初始化为0
for (int i = 0; i < str.size(); i++)
{
hashTable[str[i]]++;
}
for (int i = 0; i < str.size(); i++)
{
if (hashTable[str[i]] == 1)
return str[i];
}
//处理特殊情况:没有只出现一次的字符,每个字符都出现了两次或两次以上。
return '\0';
}
另外附上暴力搜索法:苦逼的上下而求索啊!(思路简单,但难写更难看!)
/*
最直观的暴力扫描法:对于每一个字符,分别在其前、其后的子串中查找该字符,如都没有出现,则为只出现一次的字符,找到这样的第一个字串!
*/
char getFirstNotRepeatingCharBruteForce(string str)
{
if (str.size() == 0)
return '\0';
string::size_type pos_after;
string::size_type pos_before;
string substr = str;
string substr_after;
string substr_before;
for (int i = 0; i < str.size(); i++)
{
/*
size_type find(_Elem _Ch, size_type _Off = 0) const
{ // look for _Ch at or after _Off
return (find((const _Elem *)&_Ch, _Off, 1));
}
注意是at or after _Off!!
*/
if (i == 0)
{
substr = str.substr(i + 1);
pos_after = substr.find(str[i]);
if (string::npos == pos_after)
{//没找到就输出该字符
return str[i];
}
else
{
continue;
}
}
else if (i > 0 && i < str.size() - 1)
{
/*
_Myt substr(size_type _Off = 0, size_type _Count = npos) const
{ // return [_Off, _Off + _Count) as new string
return (_Myt(*this, _Off, _Count, get_allocator()));
}
*/
//在str[i]之前找
substr_before = str.substr(0, i);
pos_before = substr_before.find(str[i]);
//在str[i]之后找
substr_after = str.substr(i + 1, string::npos);
pos_after = substr_after.find(str[i]);
if (pos_before == string::npos&&pos_after == string::npos)
{
return str[i];
}
else
{
continue;
}
}
else//对于最后一个字符:
{
substr_before = str.substr(0, i);
pos_before = substr_before.find(str[i]);
if (pos_before == string::npos)
{
return str[i];
}
else
{
return '\0';
}
}
}
}
下面是几个测试例子:
#include "stdafx.h"
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
string str = "abaccdeff";
string str2 = "aabbccdde";
string str3 = "";
string str4 = "aabbcc";
char firstNotRepeatChat = getFirstNotRepeatingCharBruteForce(str4);
cout << "First Not Repeating Char in " << str4 << " is " << firstNotRepeatChat << endl;
system("pause");
return 0;
}
哈希表 哈希表应用 剑指offer