unordered_map是C++11中加入的,以哈希表为索引方式的STL结构。与map不同,unordered_map寻找索引的值的理论时间复杂度仅为O(1),而依靠红黑树的map是O(logn)。
要理解unordered_map的运作原理,首先来造个轮子,写个自己的哈希表寻址结构。
Hash值是每个数据某个数据(比如是key)通过运算得到的某个数值,将该数据放进整个结构(比如数组)中以这个Hash值为索引的地址中,就类似于把数据倒入桶中。当需要找到某个key值对应的数据时,也同样算出Hash值,然后直接在结构中寻找就可以。由于数组的地址连贯性,Hash值作为索引可以直接计算出数据所在的地址,因此时间复杂度仅为O(1)。
首先是用到的数据的结构Record:
class Record
{
private:
int key;
int value;
public:
int the_key() { return key; }
int the_value() { return value; }
Record() { key = value = -1; }
Record(int k, int v) : key(k), value(v) {}
bool operator==(Record r) { return the_value() == r.the_value(); } //相等
Record operator=(Record r); //赋值构造函数
//...以及更多需要的函数
};
定义好了key和value,写好构造函数并且重载部分需要的操作符(具体略)。然后是利用Hash值寻址的结构hash_table。
enum Error_code { not_present, overflow, deplicate_error, success };
const int hash_size = 100;
class hash_table
{
private:
Record table[hash_size];
int hash(Record r); //获得某个Record的Hash值
bool is_empty(int index) { return table[index].the_key() == -1; } //查询table数组中index位上是否已经有数据
public:
hash_table();
hash_table(const hash_table& ht);
//...以及更多构造函数
Error_code insert(const Record& r); //插入某个Record的函数
Error_code retrive(const int& key, Record& r); //查询表中是否存在某个key值,并且存在r中
Error_code remove(const int& key); //删去该key值对应的Record
Error_code find_key(int value, int& result); //寻找第一个value匹配key
Error_code find_value(int key, int& result); //寻找key对应的value
//...以及更多增删查改的操作
hash_table operator=(const hash_table& ht); //赋值构造函数
//...以及更多重载运算符
};
其中,灵魂部分便是hash(Record r),这个函数对r的key计算得到其Hash值,后面的函数中,增删查改等所有需要从key来寻找到地址的操作,都需要用到这个函数。
//取余操作获得Hash值
int hash_table::hash(Record r)
{
return r.the_key() % hash_size;
}
//每次insert一个Record,便先算出其hash值,然后根据索引存储数据
Error_code hash_table::insert(const Record& r)
{
for (int index = hash(r), k = 0; k < hash_size; (index = (index == hash_size - 1) ? 0 : index + 1), k++)
{
if (is_empty(index))
{
table[index] = r;
return success;
}
}
return overflow;
}
//通过key寻找value也同样先算出Hash值,载根据索引查询数据
Error_code hash_table::find_value(int key, int& result)
{
for (int index = hash(Record(key, 0)); index < hash_size; index++)
{
if (is_empty(index))
{
return not_present;
}
if (table[index].the_key() == key)
{
result = table[index].the_value();
return success;
}
}
}
//...以及更多函数
这里需要注意的一点是,当数据发生冲突,即Hash值相等时,这里采用的办法是从这个地址开始不断向后寻找下一个可以存储数据的地址,然后存下,如果全满则返回overflow;取值的时候也类似。这种处理冲突的办法称为开放寻址法。
像其他STL一样,unordered_map已经内置了很多方法;不过,它还可以传入更多的参数,先看看其定义
template < class Key, // unordered_map::key_type
class T, // unordered_map::mapped_type
class Hash = hash<Key>, // unordered_map::hasher
class Pred = equal_to<Key>, // unordered_map::key_equal
class Alloc = allocator< pair<const Key,T> > // unordered_map::allocator_type
> class unordered_map;
第三到第五个参数都可以省略。第一个参数是Key值,第二个参数是存储的数据,第五个参数是内存分配器,可以不必要修改。这里聊聊第三第四两个参数。
其中,第三个参数需要一个包含取Hash值的函数,取的对象是第一个参数,即为数据的key值。这里依然以取余运算为Hash函数为例。
struct my_hash
{
int operator()(const int& value) const
{
return value % hash_len;
}
};
struct就是默认为public的class。为了简便,直接重载()运算符,这样,每个my_hash的对象都可以相当于一个函数名,用()传入参数,调用取Hash值的函数。
接下来是第四个参数,它需要包含一个判断key值是否相等的函数。
struct my_compare
{
bool operator()(const int& x, const int& y) const
{
return x == y;
}
};
也同样的道理,重载()运算符即可。
有了这样自定义好的两个类,便可以创建unordered_map了。
unordered_map<int, int, my_hash, my_compare> umap;
声明了一个key值为int,数据也是int,取Hash值的方法的类为my_hash,判断key值是否相等的方法的类为my_compare的unordered_map
当然,这个例子中的my_hash和my_compare也是可以省略的,因为int型是可以直接比较的,默认的比较函数可以工作;unordered_map也内置了计算Hash值的方法。
首先,键值对在unordered_map中的每个元素是pair
另外,后一个insert或emplace入的的同key元素将被忽略,不会覆盖原有的value