一,字符串abcdabcdef 找出第一个出现的 一次的字符,你会这么做?
普通解法用指针遍历,定义一个指针P指向一个字符a,再定义一个指针Q 去遍历下面的指针,这样的解法是可以找到,但是时间复杂度为O(n^2);
那么有没有方法让时间复杂度为O(n)呢?
这里我们定义一个数组,
用一个方法去遍历,然后存入一个表(数组)中, 用模板定义为 K , V 类型, 比如我们用multimap 去实现 (a,2);
这种方法叫 计数排序法。 这里我们引入hashtable 来解决此类问题
二 哈希表
1,概念
HashTable-散列表/哈希表,是根据关键字(key)而直接访问在内存存储位置的数据结构。
它通过一个关键值的函数将所需的数据映射到表中的位置来访问数据,这个映射函数叫做散列函数,存放记录的数组叫做散列表。
2,构建
接着 一 的问题, 如果我们定义一个很大的数去存呢 ,例如:1000000~ 1000099,我们总不能去开辟1000099和数据吧,因此我们需要一个函数Hash(key)去取数组的下标。
构造哈希表的几种方法
1. 直接定址法--取关键字的某个线性函数为散列地址,Hash(Key)= Key 或 Hash(Key)= A*Key + B,A、B为常数。
2. 除留余数法--取关键值被某个不大于散列表长m的数p除后的所得的余数为散列地址。Hash(Key)= Key % P。
3. 平方取中法
4. 折叠法
5. 随机数法
6. 数学分析法
3,哈希冲突
问题:我们开辟十个位置, 然后根据key所产生的Hash(key) 去 寻找表(数组)的地址 ,但是会产生一个问题,那么这个地址已存在元素,产生了“哈希冲突”;
不同的Key值经过哈希函数Hash(Key)处理以后可能产生相同的值哈希地址,我们称这种情况为哈希冲突。任意的散列函数都不能避免产
生冲突。
处理哈希冲突的闭散列方法-开放定址法
1. 线性探测
2. 二次探测
如上图所示例, 是处理哈希重提的一种方法,称之为线性探测。 这里我们只要来实现这种方法。
4,哈希表如果为满?
一次一次的插入哈希表总会满,但是哈希表不光是为了存储,主要是为了查找。如果哈希表为满查找效率也就低了!
说了这么多,那么我们用什么做为这个表的基本元素呢? 存入值,插入多少,容量,这用 vector 最合适不过了。 可是写这个不光又插入,还有删除,删除的话就保证不了,存入元素的连续性,无法查找下一个元素了,那么这里我们需要用一个 状态来表示这个。 ”存在,空,被删除。”
5,字符串处理
哈希表不单单处理整形数字的处理, 那么我们 下标 = Hash(key) 怎么处理呢?
想必你已经想到 operator()了 ! 但是你又该怎么写出这个呢? 如adc ,cda 你该怎么处理呢?
这里给出一个很牛逼的科学研究 !
想必你已经知道了吧!
6,源码以前,了无秘密!
#pragma once
#include <vector>
#include <string>
#include <assert.h>
#include <iostream>
using namespace std;
enum Status
{
EXIST,
DELETE,
EMPTY,
};
template<class K, class V>
struct HashNode
{
K _key;
V _value;
Status _status;
HashNode(const K& key = K(), const V& value = V())
:_key(key)
,_value(value)
,_status(EMPTY)
{}
};
template<class K>
struct __HashFunc
{
size_t operator()(const K& key)
{
return key;
}
};
template<>
struct __HashFunc<string> //为了方便简洁,我们实现特化来解决 string 问题
{
size_t BKDRHash(const char* str)
{
register size_t hash = 0;
while (*str)
{
hash = hash * 131 + *str;
++str;
}
return hash;
}
size_t operator()(const string& s)
{
return BKDRHash(s.c_str());
}
};
template<class K, class V, class _HashFunc = __HashFunc<K>>
class HashTable
{
typedef HashNode<K, V> Node;
public:
HashTable(size_t size)
:_size(0)
{
assert(size > 0);
_tables.resize(size);
}
pair<Node*, bool> Insert(const K& key, const V& value) //闭散列方法-开放定址法 线性探测法
{
CheckCapcity();
size_t index = 0;
index = HashFunc(key);
while(_tables[index]._status == EXIST) //不用 != EMPTY 因为可能是删除后再插入成为DELETE
{
if (_tables[index]._key == key)
{
return make_pair(&_tables[index], true);
}
++index;
if (index == _tables.size())
{
index = 0;
}
}
_tables[index]._key = key;
_tables[index]._value = value;
_tables[index]._status = EXIST;
++_size;
return make_pair(&_tables[index], true);
}
bool Remove(const K& key)
{
Node* ret = Find(key);
if(ret)
{
ret->_status = DELETE;
--_size;
return true;
}
return false;
}
Node* Find(const K& key)
{
size_t index = HashFunc(key);
while (_tables[index]._status != EMPTY)
{
if (_tables[index]._key == key)
{
if(_tables[index]._status != DELETE)
return &_tables[index];
}
++index;
if (index == _tables.size())
index = 0;
}
cout<<"not find"<<endl;
return (Node*)NULL;
}
size_t HashFunc(const K& key)
{
_HashFunc hf;
size_t hash = hf(key);
return hash % _tables.size();
}
void CheckCapcity() //检查α因子
{
if (_tables.empty())
{
_tables.resize(7);
return ;
}
if ((_size * 10)/_tables.size() > 7)
{
size_t newsize = _tables.size() * 2;
HashTable<K, V, _HashFunc> temp(newsize);
for (size_t i = 0; i < _tables.size(); ++i)
{
if (_tables[i]._status == EXIST)
{
temp.Insert(_tables[i]._key, _tables[i]._value);
}
}
this->Swap(temp);
}
}
void Swap(HashTable<K, V>& temp)
{
_tables.swap(temp._tables);
swap(_size, temp._size);
}
protected:
vector<Node> _tables;
size_t _size;
};
void testhash()
{
HashTable<int, int> h1(7);
h1.Insert(5,0);
h1.Insert(8,0);
h1.Insert(7,0);
h1.Insert(6,0);
h1.Insert(9,0);
h1.Insert(1,0);
h1.Insert(2,0);
h1.Find(5);
h1.Remove(5);
h1.Find(10);
h1.Find(11);
h1.Find(8);
HashTable<string, string> h2(20);
h2.Insert("ab","cd");
h2.Find("ab");
h2.Find("cd");
}