赞
踩
顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(N*logN),搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素
当向该结构中:
该方式即为哈希(散列)方法,哈希方法中使用的 转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)
注意:哈希表的效率取决于哈希函数
的设计和哈希表的装载因子
。
哈希函数
应该能够将元素均匀地映射到不同的位置,从而最小化冲突的发生。装载因子
则是指哈希表中元素的个数与哈希表大小的比值,过高的装载因子会导致冲突的发生概率增加,从而影响搜索效率。因此,在设计哈希表时需要综合考虑哈希函数的选择和装载因子的控制,以达到较好的性能。
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key%p(p<=m)
,将关键码转换成哈希地址。
如果在该集合中插入44,会将不同的值映射到同一个位置上,会出现冲突。
对于两个数据元素的关键字 k i k_i ki与 k j k_j kj,有 k i ≠ k j k_i ≠ k_j ki=kj,但有: H a s h ( k i ) = H a s h ( k j ) Hash(k_i) = Hash(k_j) Hash(ki)=Hash(kj),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。
发生哈希冲突
该如何处理呢?
答:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突,一个好的哈希函数
应该能够将元素均匀地映射到不同的位置,从而最小化冲突的发生。
解决哈希冲突两种常见的方法是:闭散列和开散列
也叫 开放定址法
,当发生哈希冲突时,若哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。那如何寻找下一个空位置呢?
线性探测
:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
HashData
的类型而不是直接使用pair/T
,HashData
中存放pair/T
和state
。//给哈希表中的每一个位置一个状态
//Empty表示该位置是kon,Erase表示该位置的元素被删除,Exit表示该位置有元素
namespace CloseHash
{
enum State{EMPTY, EXIST, DELETE};
template<class T>
struct HashData
{
T _kv;
State _state = EMPTY; //默认为空
}
template<class K, class T>
class HashTable
{
typedef HashData<T> Node;
private:
vector<Node> _table;
size_t n = 0; //有效数据个数
}
}
线性探测:模出来的映射位置已经有值,那就向后线性找一个空位置,存数据。
二次探测:线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题
找下一个空位置的方法为:
H
i
=
(
H
0
+
i
2
)
H_i = (H_0 + i^2)
Hi=(H0+i2)%
m
m
m,其中:
i
=
1
,
2
,
3
…
i = 1,2,3…
i=1,2,3…, 是通过散列函数Hash(x)
对元素的关键码 key
进行计算得到的位置,m
是表的大小
负载因子
= 存储的有效数据的个数/空间的大小 = n / _table.size()
(这里除以_table的size是因为vectot的 [] 只允许访问_size内的数据)
越大
,冲突
的概率越高
,增删查改的效率越低越小
,冲突
的概率越小
,增删查改的效率越高,但是空间利用率低,浪费空间负载因子大于0.7时扩容,扩容后需要重新计算每个数据在新_table中的位置
因此闭散列最大的缺陷就是空间利用率比较低
,这也是哈希的缺陷。
即只可以存储<int, int> 类型的,其它类型的存储如何解决?
所以我们在这里提供将Key转换为整形的方法:仿函数
仿函数(Functor)
是可以像函数一样被调用的对象。它实际上是一个类
,重载了函数调用运算符 operator()
,使得该类或结构体的对象可以像函数一样被调用
// 哈希函数采用处理余数法,被模的key必须要为整形才可以处理,此处提供将key转化为整形的方法
// 整形数据不需要转化
template<class T>
class HashFun
{
public:
size_t operator()(const T& val)
{
//对整形数据不处理
return val;
}
};
// 模版特化
template<>
class Hashfun<string>
{
public:
size_t operator()(const string& str)
{
size_t val = 0;
for(string::iterator it = str.begin(); it!=str.end(); it++)
{
val += *it;
val *= 131;
}
return val;
}
};
注意不可以使用地址来作为它的int值,因为地址会变,相同的字符串会有不同的地址。
我们需要使用这个哈希函数来封装unorder_map
和unorder_set
, 他们的存储的数据是 pair<K,T>和K
,在插入数据的时候是通过他们的 K转化为int 来进行,查找数据是通过 Key 来完成的;他们在表中的位置都需要由 K 决定:通过HashFun(K)来得到他们的地址,我们如何得到pair中的 K 呢?
答:使用仿函数,对于unorder_set
传入的是K,就直接使用;对于unorder_map
传入的是pair<K, V>, 使用仿函数对象 Kot 取出对象中的K,才可以使用HashFun(Kot(_kv))
。
其中KeyofT
我们放在unorder_map
和unorder_set
中分别进行实现
template<class K>
struct set_KeyofT
{
//set中
const K& operator()(const K& key)
{
return key;
}
}
template<class K,class T>
struct map_KeyofT
{
//map中
//在map中T实际上就是pair<K,V>
const K& operator()(const T& kv)
{
return kv.first;
}
};
template<class K, class T, class KeyofT, class HashFunc = HashFun< K >>
Find( )
函数的参数是 Key
,而我们在使用find
的时候不会输入pair<>
,所以模版参数重才需要一个Key
。对于map
不可以用pair
中的第一个参数类型代替,因我们无法取出pair
中的第一个参数类型,只可以取出pair对象
的第一个参数数据。map
中是pair<K, V>
;并且Insert( )
数据时参数是pair
,插入参数的方式有两种:1是使用piar<string,int>()
构造匿名对象;2是使用make_pair()
构造。T
中的Key
; 因为V
的类型不同,通过value
取key
的方式就不同Key
转换为int
类型从而确定存储位置在这里插入代码片
哈希表整体框架
概述: 这里采用线性探测的方式构建哈希表,下面是整体框架,其中模板参数第一个是key
关键字,第二个是哈希表存储的元素的数据类型,可以是K
,也可以是pair<K,V>
类型,主要就是为了同时实现K模型
和KV模型
。第三个参数就是一个仿函数
,为了获取T中K
的值,这里要实现两个仿函数,一个是对K模型,一个是对KV模型。哈希表底层我们借用vector容器来实现。
整体框架如下:
enum State
{
EMPTY,
EXITS,
DELETE
};
template<class T>
struct HashData
{
T _data;
State _state;
};
template<class K>
class HashFun
{
public:
size_t operator()(const K& key)
{
return key;
}
};
template<>
class HashFun<string>
{
public:
size_t operator()(const string& str)
{
size_t val = 0;
//const类型的str需要const类型的迭代器
for(string::const_iterator it = str.cbegin(); it != str.cend(); it++)
{
val += *it;
val *= 131;
}
return val;
}
};
//使用尖括号 <> 可以表示模板的实例化。在这里,HashFun<K> 是被实例化为一个具体的类型
template<class K, class T, class KeyofT, class HashFunc=HashFun<K>>
class HashTable
{
typedef HashData<T> Node;
private:
vector<Node> _table;
size_t _num = 0;// 记录已经存放了多少个数据
};
代码实现如下:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
namespace CloseHash
{
enum State
{
EMPTY,
EXIT,
DELETE,
};
template<class T>
struct HashData
{
T _data;
State _state = EMPTY;
//默认的拷贝构造足够
};
//注意仿函数定义为公有
template<class K>
class HashFun
{
public:
size_t operator()(const K& key)
{
return key;
}
};
template<>
class HashFun<string>
{
public:
size_t operator()(const string& str)
{
size_t val = 0;
//const类型的str需要const类型的迭代器
for(string::const_iterator it = str.cbegin(); it != str.cend(); it++)
{
val += *it;
val *= 131;
}
return val;
}
};
/*
//------KeyofT交给map和set的类里面去实现,不用在Hash中实现
template<class T>
class KeyofT
{
T operator()(const T& key)
{
return key;
}
};
*/
//template<class K, class T, class KeyofT, class HashFunc = HashFun<K>>
//开散列因为需要传入table才需要这些参数,需要找到桶的位置才需要,闭散列需要它来判断end的地方
//前置声明,因为迭代器和哈希表相互调用
template<class K, class T, class KeyofT, class HashFunc>
class HashTable;
template<class K, class T, class KeyofT, class HashFunc = HashFun<K>>
class _HIterator
{
typedef HashData<T> Node;
typedef _HIterator<K,T,KeyofT,HashFunc> Self;
typedef HashTable<K,T,KeyofT,HashFunc> HF;
//传入哈希表是为了确定_table的大小,从而确定end的位置
public:
_HIterator(Node* node = nullptr,HF* phf = nullptr)
{
_node = node;
_phf = phf;
}
Self& operator++()
{
//遍历无序
//只有存在才停止
// 找到当前迭代器所指向表的下标
KeyofT kot;
HashFunc hf;
size_t index = hf(kot(_node->_data)) % (_phf->_table.size());
//寻找下一个位置 需要先指向下一个位置
index++;
cout<<"index:"<<index<<endl;
//查看该位置是否越界
if(index >= _phf->_table.size())
{
// 越界则nullptr
_node = nullptr;
return *this;
}
// 没有越界则使_node指向下一个数据结点
_node++;
while(_node->_state!=EXIT)
{
// 在没有越界的情况下循环查找下一个数据的位置
// 注意:不可以将第一次index++加入循环中,否则会返回自身
// 注意:第一次需要单独判断
// 除去第一次需要在外面判断,其余在循环内判断是否越界
if(index >= _phf->_table.size())
{
// 如果在寻找的过程中没有找到,越界
_node = nullptr;
return *this;
}
// 没有越界则找下一个位置
index++;
_node++;
// 测试代码
cout<<"index:"<<index<<endl;
}
// 循环退出,表示找到 ExIT 了
return *this;
}
bool operator != (const Self& it)const
{
return _node != it._node;
}
bool operator == (const Self& it)const
{
return _node == it._node;
}
T& operator*()
{
return _node->_data;
}
T* operator->()
{
// _data是一个pair对象,存在k和v
// 重载->可以访问k和v
// 如:it->k
return &_node->_data;
}
Node* _node;
HF* _phf; //指向哈希表的指针
};
template<class K, class T, class KeyofT, class HashFunc = HashFun<K>>
class HashTable
{
typedef HashData<T> Node;
typedef HashTable<K,T,KeyofT,HashFunc> Self;
//-------------------------------
//类模板作为类模板的友元时,模板参数不可以取一样的!!!!!
template<class K1, class T1, class KeyofT1, class HashFunc1>
friend class _HIterator; //迭代器访问了HT的私有变量_table,需要友员
//友元表示迭代器可以方法HT的私有变量
public:
typedef _HIterator<K,T,KeyofT,HashFunc> iterator;
// 这里目前不实现哈希的 const_iterator
HashTable() = default;
//拷贝构造函数必须使用引用传参,否则无穷递归
HashTable(const HashTable& ht)
{
_n = ht._n;
_table.resize(ht.size());
//在编译到这里的时候vec没有生成具体的类,所以无法在vector中找迭代器类,加typename解决
typename vector<Node>::iterator it = _table.begin();
typename vector<Node>::const_iterator cit = ht._table.cbegin();
while(cit != ht._table.end())
{
// 赋值完成后才进行递增
*it++ = *cit++;
}
}
//赋值运算符重载返回引用可以减少拷贝构造,因为函数结束时会创建一个临时的 HashTable 对象来存储返回的值。
//参数不能使用引用,使用引用会导致原HT被修改
//通过返回 *this,即当前对象的引用,可以实现链式赋值操作
Self& operator=(const HashTable hf)
{
_table.swap(hf._table);
_n = hf._n;
return *this;
}
iterator begin()
{
size_t index = 0;
while(index < _table.size())
{
if(_table[index]._state==EXIT)
{
return iterator(&_table[index],this);
}
index++;
}
return end();
}
//~HashTable()
iterator end()
{
return iterator(nullptr,this);
}
size_t size()
{
return _table.size();
}
//查找
iterator Find(const K& key)
{
if(_table.size() == 0)
return end();
HashFunc hf; //计算转为整型的哈希函数
KeyofT kot; //使用kot来取_table中data的key
int index = hf(key) % _table.size();
//因为有对index取余,所以index不会超过_table的大小
// 注意:如果当前index对应的位置为空,并不表示没有找到元素
// 而是表示在哈希表中没有该键对应的元素
while(_table[index]._state != EMPTY)
{
if(_table[index]._state == EXIT && kot(_table[index]._data)==key)
{
return iterator(&_table[index],this);
}
index++;
//----------------
index %= _table.size();
}
return end();
}
//插入
pair<iterator,bool> Insert(const T& data)
{
HashFunc hf;
KeyofT kot;
//int index = hf(kot(data));
iterator ret = Find(kot(data));
//注意判断方式,ret无法独自作为判断依据
if(ret!=end())
return make_pair(ret,false);
if(_table.size()==0 || _n/_table.size() > 0.7)
{
//不可以在原来table的基础上resize,因为要把旧数据重新放入新表中,使用同一个表会导致数据丢失
vector<Node> newtable;
size_t newsize = _table.size()==0? 10:_table.size()*2;
newtable.resize(newsize);
for(int i = 0; i < _table.size(); i++)
{
//旧表中存在数据重新计算新位置
//删除 的数据并不需要重新计算
if(_table[i]._state == EXIT)
{
size_t newindex = hf(kot(_table[i]._data)) % newsize;
newtable[newindex] = _table[i];
}
}
_table.swap(newtable);
}
_n += 1;
size_t i = 1; //二次探测
size_t newindex = hf(kot(data)) % _table.size();
size_t start = newindex;
while(_table[newindex]._state == EXIT) //如果数据存在就一直向后探测
{
newindex = start + i*i;
newindex %= _table.size();
i++;
}
_table[newindex]._data = data;
_table[newindex]._state = EXIT;
return make_pair(iterator(&_table[newindex],this),true);
}
//删除
bool Erase(const K& key)
{
iterator ret = Find(key);
if(ret==end())
{
return false;
}
else
{
//ret->_node._state = DELETE;
//迭代器里面的_node指针指向hashdata中的_state
(ret._node)->_state = DELETE;
_n--;
return true;
}
/*
HashFunc hf;
KeyofT kot;
size_t index = hf(key) % _table.size();
size_t start = index. i = 1;
while(kot(_table[index]._data) != key)
{
index = start + i*i;
index %= _table.size();
i++;
}
*/
}
private:
vector<Node> _table;
size_t _n = 0; //有效数据个数
};
} //命名空间后无封号
int test1()
{
CloseHash::HashTable<int,int,KOT<int>> HT;
HT.Insert(2);
auto ret = HT.Find(2);
if(ret!=HT.end())
cout<<"存在"<<'\n';
cout<<HT.size()<<endl;
HT.Erase(2);
cout<<HT.size()<<endl;
//cout<<HT._n<<endl;
ret = HT.Find(2);
if(ret==HT.end())
cout<<"不存在"<<'\n';
HT.Insert(17);
HT.Insert(12);
HT.Insert(3);
HT.Insert(8);
HT.Insert(6);
HT.Insert(21);
CloseHash::HashTable<int,int,KOT<int>>::iterator it = HT.begin();
for(;it!=HT.end();++it)
{
cout<<*it<<" ";
cout<<it._node<<endl;
}
//结果21 12 3 6 17 8;是无序的
return 0;
}
存在
10
10
不存在
size:10
21 0x131ac28
index:2
12 0x131ac30
index:3
3 0x131ac38
index:4
index:5
index:6
6 0x131ac50
index:7
17 0x131ac58
index:8
8 0x131ac60
index:9
index:10
在C++中,typename
的作用主要有以下几点:
typename
用来指定后面的名称为类型。在C++模板编写中,如果要在模板内定义一个嵌套从属于模板参数的类型时,需要使用typename关键字说明。如:template<class T>
class X {
public:
typedef typename T::size_type size_type;
};
这里typename
是用来声明T::size_type
是一个类型,而不是一个静态成员。
typename
关键字告诉编译器,它后面的名字应当被视为类型名。例如:template<class T>
class X {
public:
void fun() {
typename T::SubType * ptr; // 这里告诉编译器,T::SubType 是一个类型名
}
};
注意,typename不可以用在基类列表或者成员初始化列表里面,也不能用于指定模板参数。这些地方必须得用class。
在C++中,当函数返回时,会进行以下操作:
返回操作涉及对返回值的拷贝、局部变量的清理和调用栈的恢复。当函数返回一个引用时,实际上返回的是对某个对象的别名或引用。这意味着返回的引用与被引用的对象共享相同的内存地址。返回引用类型
可以避免额外的拷贝构造函数调用和内存拷贝操作。
常量对象(constant object)
是指被声明为 const
的对象。也就是说,常量对象在声明后不可被修改。常量对象可以是基本数据类型(如 int
、double
等),也可以是自定义的类对象。
常量对象值不可修改:常量对象的值在声明后不能被修改。任何试图修改常量对象的操作都会引发编译错误。
常量对象主要用于以下情况:
以下是一个示例:
class Point {
public:
int getX() const { return x; }
int getY() const { return y; }
private:
int x;
int y;
};
void printPoint(const Point& p) {
std::cout << "Point: (" << p.getX() << ", " << p.getY() << ")" << std::endl;
}
int main() {
const int num = 10; // 声明一个常量整数对象
const double PI = 3.14159; // 声明一个常量浮点数对象
const Point origin {0, 0}; // 声明一个常量 Point 对象
// 试图修改常量对象的值会导致编译错误
// num = 20; // 错误:常量对象值不可修改
printPoint(origin); // 通过常量引用参数传递常量 Point 对象
return 0;
}
在上述示例中,num
和 PI
是常量对象,它们的值在声明之后不可修改。origin
是一个常量 Point 对象,也就是常量对象的一个实例。这些常量对象在程序执行过程中是只读的,并且可以被传递给常量引用参数的函数使用。
在函数后面使用 const
关键字修饰的作用是将函数声明为常量成员函数。常量成员函数不会修改对象的状态,并且可以在常量对象上调用。具体作用如下:
举例说明:
class MyClass {
public:
int getData() const { // 声明为常量成员函数
// 不修改对象的成员变量
return data;
}
void setData(int newData) { // 非常量成员函数
// 修改对象的成员变量
data = newData;
}
private:
int data;
};
在上述例子中,getData()
函数被声明为常量成员函数,而 setData()
函数没有被声明为常量成员函数。
getData()
调用对象,可以在常量对象
和 非常量对象
上直接调用 getData()
;setData()
调用对象,则只可以对非常量
对象上调用setData()
,不能在常量对象
上调用 setData()
。如果没有使用 const
修饰符,则表示该函数可以修改对象的状态,并且无论是否是常量对象,都可以在其上调用该函数。这样可能导致在非常量对象上调用该函数时修改对象的状态,可能引入意外的副作用。总之,通过在函数后面使用 const
关键字修饰,可以明确函数的行为约定,并限制函数对对象的修改。
常量成员函数的重载:两个成员函数,名字和参数表都一样,但是一个是const,一个不是,那么是算是重载。
成员函数的重载是指在同一个类中创建多个同名函数,但它们的参数列表不同。在调用这些函数时,编译器会根据提供的参数来确定应该调用哪个函数。这使得我们可以根据不同的参数类型或数量来执行不同的操作。const成员函数与非const成员函数可以被重载。当我们在类中定义一个成员函数时,可以为同一个函数创建一个const版本和一个非const版本
class Sample
{
public:
Sample() { m_value = 1; }
int GetValue() const { return m_value; } // 常量成员函数
int GetValue() { return 2*m_value; } // 普通成员函数
int m_value;
};
int main()
{
const Sample obj1;
std::cout << "常量成员函数 " << obj1.GetValue() << std::endl;
Sample obj2;
std::cout << "普通成员函数 " << obj2.GetValue() << std::endl;
}
执行结果
常量成员函数 1
普通成员函数 2
引用前面可以加const
关键字,成为常引用。不能通过常引用修改其引用的变量的,常引用(Constant Reference)可以让我们以只读方式访问对象的值。
对象作为函数的参数时,生产该对象参数是需要调用复制构造函数的,这样效率就比较低。用指针作为参数,代码又不好看,如何解决呢?
可以用对象的引用作为参数,防止引发复制构造函数,如:
class Sample
{
...
};
void Func(Sample & o) // 对象的引用作为参数
{
...
}
但是有个问题,对象引用作为函数的参数有一定的风险性,若函数中不小心修改了形参0,则实参也会跟着变,这可能不是我们想要的,如何避免呢?
可以用对象的常引用作为参数,如:
class Sample
{
...
};
void Func(const Sample & o) // 对象的常引用作为参数
{
...
}
这样函数中就能确保不会出现无意中更改o值的语句了。
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
开散列中每个桶中放的都是发生哈希冲突的元素
桶的个数是一定的,随着元素的不断插入,每个桶中元素的个数不断增多,极端情况下,可能会导致一个桶中链表节点非常多,会影响的哈希表的性能,因此在一定条件下需要对哈希表进行增容,那该条件怎么确认呢?开散列最好的情况是:每个哈希桶中刚好挂一个节点,再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以给哈希表增容。
size_t GetNextPrime(size_t prime)
{
const int PRIMECOUNT = 28;
static const size_t primeList[PRIMECOUNT] =
{
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
};
size_t i = 0;
for (; i < PRIMECOUNT; ++i)
{
if (primeList[i] > prime)
return primeList[i];
}
return primeList[i];
}
应用链地址法处理溢出,需要增设链接指针,似乎增加了存储开销。事实上: 由于开地址法必须保持大量的空闲空间以确保搜索效率,如二次探查法要求装载因子a <= 0.7,而表项所占空间又比指针大的多,所以使用开散列的效率比闭散列高。
所以实际中哈希桶的结构更加实用
union
来减省空间。迭代器实现函数:
// 构造
// 拷贝构造
Self operator++()
Ref operator*()
Ptr operator->()
bool operator!=(const Self& s)
bool operator==(const Self& s)
哈希桶实现函数:
// 构造
HashTable() = default;
// 拷贝构造
HashTable(const HashTable& ht)
// 析构
~HashTable()
// 赋值重载
Self& operator=(const HashTable hf)
// 普通迭代器begin
iterator begin()
// const迭代器begin // 对begin的重载
const_iterator begin()const
// 普通迭代器end
iterator end()
// const迭代器end // 对end的重载
const_iterator end() const
size_t size()
size_t GetNextPrime(size_t prime)
iterator Find(const K& key)
pair<iterator,bool> Insert(const T& data)
bool Erase(const K& key)
具体实现:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
namespace OpenHash
{
template<class T>
struct HashNode
{
T _data;
HashNode<T>* _next;
HashNode(const T& data)
{
_data = data;
_next = nullptr;
}
};
//注意仿函数定义为公有
template<class K>
class Hash
{
public:
size_t operator()(const K& key)
{
return key;
}
};
template<>
class Hash<string>
{
public:
size_t operator()(const string& str)
{
size_t val = 0;
//const类型的str需要const类型的迭代器
for(string::const_iterator it = str.cbegin(); it != str.cend(); it++)
{
val += *it;
val *= 131;
}
return val;
}
};
//前置声明
template<class K,class T,class KeyofT,class HashFunc>
class HashTable;
template<class K,class T,class Ref,class Ptr,class KeyofT,class HashFunc = Hash<K>>
class _HIterator
{
typedef _HIterator<K,T,Ref,Ptr,KeyofT,HashFunc> Self;
typedef HashTable<K,T,KeyofT,HashFunc> HT;
//********typedef HashNode<T>* pNode; //vector中数据的类型
typedef HashNode<T> Node;
public:
_HIterator(Node* node = nullptr,const HT* ht=nullptr)
{
_node = node;
_pht = ht;
}
__HashIterator(const Iterator& it)
:_node(it._node)
, _ht(it._ht)
{}
//----------------------------------------------------------------------------------
//vector中存储的是结点的指针,而数据是存在每一个结点上的,我们不需要让迭代器在vector上行走
//只需要让迭代器在每一个结点上行走,所以我们不需要Node**, 只需要Node*
//vector只是让我们找到下一个结点(桶)的工具
Self& operator++()
{
KeyofT kot;
HashFunc hf;
if(_node->_next == nullptr) //如果当前桶只有一个数据,就找表中的下一个位置
{
size_t index = hf(kot(_node->_data)) % (_pht->_table.size());
++index;//下一个桶的下标
_node = _pht->_table[index];
//如果下一个位置为空,就继续找洗一个,直到不为空
while(_node==nullptr)
{
index++;
if(index >= _pht->_table.size())
{
_node = nullptr;
return *this;
}
_node = _pht->_table[index];
}
}else
{
_node = _node->_next;
}
return *this;
}
//Self operator++(int)
//T& operator*() 与下方的是相同的,引入ref是为了写const T&
Ref operator*()
{
return _node->_data;
}
//T* operator->()
Ptr operator->()
{
return &(_node->_data);
}
bool operator!=(const Self& it)const
{
return _node != it._node;
}
bool operator==(const Self& it)const
{
return _node == it._node;
}
//pNode* _pnode; //指向vector中数据的指针
Node* _node;
const HT* _pht;
};
template<class K,class T,class KeyofT,class HashFunc = Hash<K>>
class HashTable
{
typedef HashNode<T> Node;
typedef HashTable<K,T,KeyofT,HashFunc> Self;
template<class K1,class T1,class Ref1,class Ptr1,class KeyofT1,class HashFunc1>
friend class _HIterator;
public:
typedef _HIterator<K,T,T&,T*,KeyofT,HashFunc> iterator;
typedef _HIterator<K,T,const T&,const T*,KeyofT,HashFunc> const_iterator;
HashTable() = default;
HashTable(const HashTable& ht)
{
_n = ht._n;
_table.resize(ht.size());
typename vector<Node*>::iterator it = _table.begin();
typename vector<Node*>::const_iterator cit = ht._table.cbegin();
while(cit != ht.end())
{
*it++ = *cit++;
}
}
~HashTable()
{
for (auto& cur : _tables)
{
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
cur = nullptr;
}
}
Self& operator=(const HashTable hf)
{
_table.swap(hf._table);
_n = hf._n;
return *this;
}
//------------------------------
iterator begin()
{
size_t index = 0;
while(_table[index] == nullptr)
{
++index;
if(_table[index]!=nullptr)
{
return iterator(_table[index],this);
}
}
return end();
}
// const对象只能使用const迭代器
const_iterator begin()const
{
size_t index = 0;
while(_table[index] == nullptr)
{
++index;
if(_table[index]!=nullptr)
{
return const_iterator(_table[index],this);
}
}
return end();
}
iterator end()
{
return iterator(nullptr,this);
}
const_iterator end() const
{
return const_iterator(nullptr, this);
}
size_t size()
{
return _table.size();
}
//------------------------------
size_t GetNextPrime(size_t prime)
{
const int PRIMECOUNT = 28;
static const size_t primeList[PRIMECOUNT] =
{
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
};
size_t i = 0;
for (; i < PRIMECOUNT; ++i)
{
if (primeList[i] > prime)
return primeList[i];
}
return primeList[i];
}
iterator Find(const K& key)
{
if(_table.size()==0)
return end();
KeyofT kot;
HashFunc hf;
//先找到桶的下标
size_t index = hf(key) % _table.size();
//if(_table[index]) //如果对应位置存在数据,就在这个桶中找
Node* cur = _table[index]; // 桶中存放的是指针
//如果桶中有数据就进入循环,否则返回end()
//cur不是nullptr就表示该桶中有数据
while(cur!=nullptr)
{
if(kot(cur->_data)==key)
{
return iterator(cur,this);
}else
{
// 在这个桶中继续找下一个
cur = cur->_next;
}
}
// 桶中没有数据了
return end();
}
pair<iterator,bool> Insert(const T& data)
{
KeyofT kot;
HashFunc hf;
auto ret = Find(kot(data));
// 找到了就表示可以插入
// 没找到就表示K不合法,不可以插入
if(ret!=end())
return make_pair(ret,false);
_n++;
if(_table.size() == 0 || (double)_n / (double)_table.size() >= 1)
{
vector<Node*> newtable;
newtable.resize(GetNextPrime(_table.size()));
//将旧表中的结点移动到新表中
size_t index = 0;
for(;index < _table.size(); index++)
{
//如果旧表中有数据的话
//如果当前桶有数据
if(_table[index])
{
Node* cur = _table[index];
// 直到当前桶没有数据
while(cur)
{
// 先记录下下一个结点
Node* Next = cur->_next;
size_t newindex = hf(kot(_table[index]->_data)) % newtable.size();
//移动结点
//头插
//错误,这样写就把newtable中的新结点丢掉了 : newtable[newindex]->_next = cur;
cur->_next = newtable[newindex]; //让新的结点的后面连接以前的结点
newtable[newindex] = cur;
//这样写虽然旧表中依然指向那些结点但是最后把他们值null就好了
cur = Next; //移动到旧结点的cur的下一个结点
}
}
_table[index] = nullptr;
}
_table.swap(newtable);
}
size_t index = hf(kot(data)) % _table.size();
Node* newNode = new Node(data);
newNode->_next = _table[index];
_table[index] = newNode;
return make_pair(iterator(newNode,this),true);
}
bool Erase(const K& key)
{
HashFunc hf;
KeyofT kot;
size_t index = hf(key) % _table.size();
Node* prev = nullptr;
Node* cur = _table[index];
while(cur)
{
if(kot(cur->_data)==key)
{
//(1)头删除
if(_table[index]==cur)
{
_table[index] = cur->_next;
}
//(2)
else
{
prev->_next = cur->_next;
}
--_n;
delete cur;
return true;
}else
{
prev = cur;
cur = cur->_next;
}
}
return false;
}
private:
vector<Node*> _table;
size_t _n = 0; //有效数据个数
};
};
template<class K>
class KOT
{
public:
size_t operator()(const K& key)
{
return key;
}
};
int test2()
{
OpenHash::HashTable<int,int,KOT<int>> HT;
HT.Insert(2);
cout<<"ssss";
auto ret = HT.Find(2);
if(ret!=HT.end())
cout<<"存在"<<'\n';
cout<<HT.size()<<endl;
HT.Erase(2);
cout<<HT.size()<<endl;
//cout<<HT._n<<endl;
ret = HT.Find(2);
if(ret==HT.end())
cout<<"bu存在"<<'\n';
HT.Insert(17);
HT.Insert(12);
HT.Insert(3);
HT.Insert(8);
HT.Insert(6);
HT.Insert(24);
HT.Insert(22);
HT.Insert(45);
HT.Insert(11);
HT.Insert(10);
HT.Insert(36);
HT.Insert(62);
HT.Insert(67);
OpenHash::HashTable<int,int,KOT<int>>::iterator it = HT.begin();
for(;it!=HT.end();++it)
{
cout<<*it<<" ";
}
//结果3 6 8 62 10 11 12 67 17 22 24 36 451是无序的
return 0;
}
注意:如果不引入Ref
和Ptr
就会出现下面的情况,无法区分普通迭代器和const
迭代器
typedef _HIterator<K,T,KeyofT,HashFunc> iterator;
typedef _HIterator<K,T,KeyofT,HashFunc> const_iterator;
在扩容的时候,新创建节点,将新结点写入值并添加到新的哈希表中,实现如下:
pair<iterator, bool> Insert(const T& data)
{
KeyOfT kot;
HashFunc hf;
iterator it = Find(kot(data));
if (it != end())
{
return make_pair(it, false);
}
++_n;
if (_table.size() == 0 || (double)_n / (double)_table.size() >= 1)
{
size_t newsize = GetNextPrime(_tables.size());
HashTable<K,T,KeyofT,HashFunc> newht;
newht._tables.resize(newsize);
// 在旧表中循环循环每一个桶
for (auto cur : _tables)
{
//如果该桶中指针不是空
//就表示该桶有数据,循环把该桶中的数据插入到新表中
//其中cur是_table中的每一个元素,即(Node*类型)的指针
while (cur)
{
newht.Insert(cur->_data);
cur = cur->_next;
}
}
_tables.swap(newht._tables);
}
size_t index = hf(kot(data)) % _tables.size();
// 头插
Node* newnode = new Node(data);
newnode->_next = _tables[index];
_tables[index] = newnode;
return make_pair(iterator(newnode, this), true);;
}
如果这样写的话,原本hashtable._table
中里面的节点链也需要进行释放,即析构;虽然vector
在默认析构函数中会调用它默认的析构函数,但是vector
中指针所指向的地址空间不会被释放/析构,所以在析构函数中需要额外操作:
~HashTable()
{
for(auto& cur _table)
{
while(cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
cut = nullptr;
}
_table.clear();
_table.shrink_to_fit();
}
这样写由于每一次扩容的时候,都需要重新创建旧表中的所有结点,并然后将旧表中的结点全部释放,这样不如我们直接使用旧表中的结点,将旧表中的节点当作newnode
头插入新表中,就如我们上方实现的那样
#pragma once
#include "HashTable.h"
namespace LZH
{
template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
public:
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
public:
//在C++中,如果 HashTable是一个模板类而不是一个对象,那么它的内部类型应该使用双冒号 ::来访问,而不是点号 .
typedef typename OpenHash::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::iterator iterator;
typedef typename OpenHash::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::const_iterator const_iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
const_iterator begin() const
{
return _ht.begin();
}
const_iterator end() const
{
return _ht.end();
}
pair<iterator, bool> insert(const pair<K, V>& kv)
{
return _ht.Insert(kv);
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V()));
return ret.first->second;
}
iterator find(const K& key)
{
return _ht.Find(key);
}
bool erase(const K& key)
{
return _ht.Erase(key);
}
private:
OpenHash::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
};
//测试
void test_unordered_map1()
{
unordered_map<int, int> m;
m.insert(make_pair(1, 1));
m.insert(make_pair(3, 3));
m.insert(make_pair(2, 2));
unordered_map<int, int>::iterator it = m.begin();
while (it != m.end())
{
//it->first = 1;
//it->second = 1;
cout << it->first << ":" << it->second << endl;
++it;
}
cout << endl;
}
void test_unordered_map2()
{
string arr[] = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉", "梨" };
unordered_map<string, int> countMap;
for (auto& e : arr)
{
countMap[e]++;
}
for (auto& kv : countMap)
{
cout << kv.first << ":" << kv.second << endl;
}
}
}
#pragma once
#include "HashTable.h"
namespace LZH
{
template<class K, class Hash = HashFunc<K>>
class unordered_set
{
public:
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
//在C++中,如果 HashTable是一个模板类而不是一个对象,那么它的内部类型应该使用双冒号 ::来访问,而不是点号
typedef typename OpenHash::HashTable<K, K, SetKeyOfT, Hash>::const_iterator iterator;
typedef typename OpenHash::HashTable<K, K, SetKeyOfT, Hash>::const_iterator const_iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
const_iterator begin() const
{
return _ht.begin();
}
const_iterator end() const
{
return _ht.end();
}
pair<iterator, bool> insert(const K& key)
{
return _ht.Insert(key);
}
iterator find(const K& key)
{
return _ht.Find(key);
}
bool erase(const K& key)
{
return _ht.Erase(key);
}
private:
OpenHash::HashTable<K, K, SetKeyOfT, Hash> _ht;
};
// 测试
void print(const unordered_set<int>& s)
{
unordered_set<int>::const_iterator it = s.begin();
while (it != s.end())
{
//*it = 1;
cout << *it << " ";
++it;
}
cout << endl;
}
void test_unordered_set1()
{
int a[] = { 3, 33, 2, 13, 5, 12, 1002 };
unordered_set<int> s;
for (auto e : a)
{
s.insert(e);
}
s.insert(54);
s.insert(107);
unordered_set<int>::iterator it = s.begin();
while (it != s.end())
{
//*it = 1;
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
print(s);
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。