您的位置:首页 > 理论基础 > 数据结构算法

【数据结构】哈希表(线性探测法)

2018-03-28 22:41 375 查看
哈希表是一种搜索结构,当数据量大时,哈希搜索的效率高,平均时间复杂度O(1)。

【哈希查找】:

(1)在插入时,根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放。

(2)在搜索时,对元素的关键码进行同样的计算,把求得的函数值当作元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。
该方式即散列方法(Hash Method),在散列方法中使用的转换函数叫着散列函数(Hash function),构造出来的结构叫散列表(Hash Table)。用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快。

【哈希冲突】:

对于两个数据元素的关键字Ki和Kj(i != j),有Ki != Kj ( i != j) ,但HashFun( Ki ) ==HashFun( Kj ) ,将该种现象称为哈希冲突或哈希碰撞。

哈希表元素的插入:



【散列函数】:

常见的求哈希值的方法:

1.直接定址法:

取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B

优点:简单、均匀

缺点:需要事先知道关键字的分布情况
(适合查找比较小且连续的情况)

2.除留余数法:

设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数,按照哈希函数:Hash( key ) = key % p ( p <= m) ,将关键码转换成哈希地址。

3.平方取中法
4.折叠法
5.随机数法
6.数学分析法

【散列冲突处理方法】:
闭散列法:

在元素插入时遇到哈希冲突,我们可选择线性探查法处理冲突,还可以选择二次探查法处理冲突。
这里我们分析下线性探查法:

给出一组元素,它们的关键码为:37,25,14,36,49,68,57,11,散列表为HT[12],表的大小 m=12 ,假设采用Hash(key)= key % p ;(p=11)11是最接近m的质数,就有:



添加元素时,使用散列函数确定元素的插入位置,如果此空间有值:

1.该值是所要插入元素的关键码,不进行插入。

2.产生冲突,依次查看其后的下一个桶,如果发现空位置插入新元素

注意:
散列表的载荷因子:a = 插入元素个数 / 散列表的长度

a是散列表装满程度的标志因子。对于开放地址法,载荷因子非常重要,应严格限制在 0.7~0.8 以下。超过 0.8 ,查表时的CPU缓存按照指数曲线上升。

【代码实现】:

1.用vector开辟出一段空间来存放元素。

2.每个节点都有三种状态 EXIST(存在) 、DELETE(删除)、EMPTY(空) ,初始化时每个节点的状态设置为EMPTY

【Hash.h】

#include<iostream>
#include<vector>
#include<assert.h>
#include<utility>
#include<cstring>

using namespace std;

//定义仿函数
template<class K>
struct _HashFunc
{
size_t operator()(const K& key)
{
return key;
}
};

//特化string的版本
template<>
struct _HashFunc<string>
{
static size_t BKDRHash(const char* str)
{
size_t seed = 131;    // 31 131 1313 13131 131313
size_t hash = 0;
while (*str)
{
hash = hash*seed + (*str++);
}
return (hash & 0x7fffffff);
}
size_t operator()(const string& key)
{
return BKDRHash(key.c_str());    //c_str()返回的是一个const char*  类型的字符串
}
};

enum Status
{
EXIST,
DELETE,
EMPTY
};

template<class K,class V>
struct HashTableNode
{
//HashTableNode<K,V>* _pNode;
K _key;
V _value;
Status _status;
HashTableNode(const K& key=K(),const V& value=V())
:_key(key)
, _value(value)
, _status(EMPTY)
{ }

};

//素数表,表内为哈希表的容量,素数降低哈希冲突
const int _PrimeSize = 28;
static const unsigned long _PrimeList[_PrimeSize] =
{
53ul, 97ul, 193ul, 389ul, 769ul,1543ul, 3079ul, 6151ul,
12289ul, 24593ul,49157ul, 98317ul, 196613ul, 393241ul,
786433ul,1572869ul, 3145739ul, 6291469ul, 12582917ul,
25165843ul,50331653ul, 100663319ul, 201326611ul,
402653189ul,805306457ul,1610612741ul, 3221225473ul,
4294967291ul
};

template<class K,class V,class HashFunc=_HashFunc<K>>
class HashTable
{
typedef HashTableNode<K, V> Node;
public:
HashTable()
{}
HashTable(size_t size)
{
assert(size > 0);
_v.resize(size);
_size = 0;
}
//将K值转换成哈希值
size_t _HashTableFunc(const K& key)
{
HashFunc hf;    //定义一个HashFunc的变量hf
size_t hash = hf(key);  //用变量hf调用HashFunc的仿函数,返回对应的整型
return hash% _v.size(); //算出哈希值,并返回
}

pair<Node*, bool> Insert(const K& key, const V& value)
{
//检查是否需要扩容
CheckCapacity();

//对K值进行取余,判断插入位置
size_t index = _HashTableFunc(key);
//如果存在,则循环着继续找
while (_v[index]._status !=EMPTY)
{
index++;
if (index == _v.size())
index = 0;
}
_v[index]._key = key;
_v[index]._value = value;
_v[index]._status = EXIST;

_size++;
return make_pair<Node*, bool>(&_v[index], true);
}

Node* find(const K& key)        //查找位置
{
size_t index = _HashTableFunc(key);

//如果存在,则继续寻找
while (_v[index]._status == EXIST)
{
//若相等,判断状态是否是删除
//若删除,则没找到,返回空
//若没删除,则返回该位置的地址
if (_v[index]._key == key)
{
if (_v[index]._status == DELETE)
return NULL;

return &_v[index];
}
index++;
if (index == _size)
index = 0;
}
return NULL;
}

void Delete(const K& key)
{
//删除仅需要将状态修改
Node* delNode = find(key);

if (delNode)
delNode->_status = DELETE;
}
private:
//交换两个哈希表
void Swap(HashTable<K, V>& h)
{
swap(_v, h._v);
swap(_size, h._size);
}

void CheckCapacity()
{
//如果_v为空,则扩容到11
if (_v.empty())
{
_v.resize(11);
}

//如果超过比例系数,则需要扩容
if (_size * 10 / _v.size() >= 7)
{
size_t index = 0;
while (_PrimeList[index] < _v.size())
{
index++;
}
size_t newSize = _PrimeList[index];
HashTable<K, V> newh(newSize);    //新近一个哈希表
for (size_t i = 0; i < _v.size(); i++)  //将旧的哈希表中的元素重新插入到新的哈希表
{
if (_v[i]._status==EXIST)
newh.Insert(_v[i]._key, _v[i]._value);
}
//交换两个哈希表
Swap(newh);
}
}

private:
vector<Node> _v;
size_t _size;

};


【测试存入整型数据】

void test()
{
int arr[] = { 3, 7, 12, 23, 45, 67, 13, 43 };
int size = sizeof(arr) / sizeof(arr[0]);
HashTable<int, int> h1(11);
for (int i = 0; i < size; i++)
{
h1.Insert(arr[i], arr[i]+ 3);
}
}


【测试以字符串为关键码存入】

void test2()
{
HashTable<string, string> h2(11);
h2.Insert("abs", "1111");
h2.Insert("222","2222");
h2.Insert("223", "2223");
h2.Insert("224", "2224");
h2.Insert("225", "2225");
}


【test.cpp】

#include"Hash.h"
int main()
{
//test();
test2();
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: