C++哈希表的使用


1.C++ STL unordered_map用法

在C++11中,unordered_map作为一种关联容器,替代了hash_map,unordered_map的底层实现是hash表,所以被称为无序关联容器。使用时需要指明头文件 #include<map>。

不管是map还是unordered_map都是一种 key-map(value) 映射的容器,提供非常高的查找效率。

2.成员

2.1 类型定义

  • unordered_map::const_iterator    受控序列的常量迭代器。

Mymap c1; 
c1.insert(Mymap::value_type('a', 1)); 
c1.insert(Mymap::value_type('b', 2)); 
c1.insert(Mymap::value_type('c', 3)); 

for (Mymap::const_iterator it = c1.begin(); it != c1.end(); ++it) 
        std::cout << " [" << it->first << ", " << it->second << "]";
output:

[a,1],[b,2],[c,3]
  • unordered_map :: const_local_iterator    受控序列的常量存储桶迭代器。用法和const_iterator类似。

  • unordered_map :: const_pointer    指向元素的常量指针。

Mymap c1; 
c1.insert(Mymap::value_type('a', 1)); 
c1.insert(Mymap::value_type('b', 2)); 
c1.insert(Mymap::value_type('c', 3)); 

for (Mymap::iterator it = c1.begin(); it != c1.end(); ++it) 
{ 
      Mymap::const_pointer p = &*it; 
      std::cout << " [" << p->first << ", " << p->second << "]"; 
}
output:
[a,1],[b,2],[c,3]
  • unordered_map :: const_reference    该类型描述了一个对象,该对象可用作对受控序列元素的恒定引用。

Mymap c1; 
c1.insert(Mymap::value_type('a', 1)); 
c1.insert(Mymap::value_type('b', 2)); 
c1.insert(Mymap::value_type('c', 3)); 

for (Mymap::iterator it = c1.begin(); it != c1.end(); ++it) 
{ 
      Mymap::const_reference ref = *it; 
      std::cout << " [" << ref.first << ", " << ref.second << "]"; 
}
output:
[a,1],[b,2],[c,3]
  • unordered_map::hasher    哈希函数。

Mymap c1; 
Mymap::hasher hfn = c1.hash_function(); 
std::cout << "hfn('a') == " << hfn('a') << std::endl; 
std::cout << "hfn('b') == " << hfn('b') << std::endl; 
output:
hfn('a') == 3826002220
hfn('b') == 3876335077
  • unordered_map :: iterator    受控序列的迭代器类型。用法和const_iterator类似。

  • unordered_map :: key_equal    比较函数。

Mymap c1; 
 
Mymap::key_equal cmpfn = c1.key_eq(); 
std::cout << "cmpfn('a', 'a') == " << std::boolalpha << cmpfn('a', 'a') << std::endl; 
std::cout << "cmpfn('a', 'b') == " << std::boolalpha << cmpfn('a', 'b') << std::endl; 
output:
true, false

2.2 成员函数

  •  begin    指定受控序列或存储桶的开始。
Mymap c1; 
c1.insert(Mymap::value_type('a', 1)); 
c1.insert(Mymap::value_type('b', 2)); 
c1.insert(Mymap::value_type('c', 3)); 

for (Mymap::const_iterator it = c1.begin(); it != c1.end(); ++it) 
        std::cout << " [" << it->first << ", " << it->second << "]"; 
output:
[a,1],[b,2],[c,3]
  • bucket    获取键值的存储桶(存储区)编号。
Mymap c1; 
c1.insert(Mymap::value_type('a', 1)); 
c1.insert(Mymap::value_type('b', 2)); 
c1.insert(Mymap::value_type('c', 3)); 

Mymap::size_type bs = c1.bucket('a'); 
std::cout << "bucket('a') == " << bs << std::endl; 
  • clear    删除所有元素。
Mymap c1; 
c1.insert(Mymap::value_type('a', 1)); 
c1.insert(Mymap::value_type('b', 2)); 
c1.insert(Mymap::value_type('c', 3)); 

c1.clear(); 
std::cout << "size == " << c1.size() << std::endl; 
std::cout << "empty() == " << std::boolalpha << c1.empty() << std::endl; 
output:
size == 0 empty()== true
  • count    查找与指定键匹配的元素数。
Mymap c1; 
c1.insert(Mymap::value_type('a', 1)); 
c1.insert(Mymap::value_type('b', 2)); 
c1.insert(Mymap::value_type('c', 3)); 

std::cout << "count('A') == " << c1.count('A') << std::endl; 
std::cout << "count('b') == " << c1.count('b') << std::endl; 
output:
count('A')== 0 count('b')== 1
  • empty    判断是否为空。
  • erase    删除指定位置的元素。两种形式:

    iterator erase(iterator where); // 要移除的位置通过where来指定
    iterator erase(iterator first, iterator last); // 移除[first, last)范围的元素
    
    Mymap c1; 
    c1.insert(Mymap::value_type('a', 1)); 
    c1.insert(Mymap::value_type('b', 2)); 
    c1.insert(Mymap::value_type('c', 3)); 
    
    Mymap::iterator it2 = c1.erase(c1.begin()); 
    std::cout << "*erase(begin()) == [" << it2->first << ", " << it2->second << "]"; 
    
    c1.insert(Mymap::value_type('d', 4)); 
    c1.insert(Mymap::value_type('e', 5)); 
    
    it2 = c1.end(); 
    it2 = c1.erase(c1.begin(), --it2); 
    std::cout << "*erase(begin(), end()-1) == [" << it2->first << ", " << it2->second << "]" << std::endl;
    [b,2]
    [e,5]
  • find    查找与指定键匹配的元素。
Mymap c1; 
c1.insert(Mymap::value_type('a', 1)); 
c1.insert(Mymap::value_type('b', 2)); 
c1.insert(Mymap::value_type('c', 3)); 

std::cout << "find('A') == " << std::boolalpha << (c1.find('A') != c1.end()) << std::endl; 

std::cout << "find('b') == " << std::boolalpha << (it != c1.end()) << ": [" << it->first << ", " << it->second << "]" << std::endl; 
find('A')==false
find('b')== true
  • end    指定受控序列的结尾。注意:它所指的位置是迭代器最后一个元素位置的下一个位置。
Mymap c1;
c1.insert(Mymap::value_type('a', 1));
c1.insert(Mymap::value_type('b', 2));
c1.insert(Mymap::value_type('c', 3));

Mymap::iterator it2 = c1.end();
--it2;
std::cout << " [" << it2->first << ", " << it2->second << "]";
--it2;
std::cout << " [" << it2->first << ", " << it2->second << "]";
[c,3]
[b,2]
  • hash_function    获取存储的哈希函数对象。
  • insert    添加元素。
Mymap c1;  
c1.insert(Mymap::value_type('a', 1)); 
c1.insert(Mymap::value_type('b', 2)); 
c1.insert(Mymap::value_type('c', 3));
  • key_eq    判断key是否相等。
  • swap    交换两个容器的元素。
Mymap c1;
c1.insert(Mymap::value_type('a', 1));
c1.insert(Mymap::value_type('b', 2));
c1.insert(Mymap::value_type('c', 3));

for (Mymap::const_iterator it = c1.begin();it != c1.end(); ++it)
    std::cout << " [" << it->first << ", " << it->second << "]";
    std::cout << std::endl;

Mymap c2;
c2.insert(Mymap::value_type('d', 4));
c2.insert(Mymap::value_type('e', 5));
c2.insert(Mymap::value_type('f', 6));
c1.swap(c2);

for (Mymap::const_iterator it = c1.begin();it != c1.end(); ++it)
    std::cout << " [" << it->first << ", " << it->second << "]";
    std::cout << std::endl;

swap(c1, c2);

for (Mymap::const_iterator it = c1.begin();it != c1.end(); ++it)
    std::cout << " [" << it->first << ", " << it->second << "]";
    std::cout << std::endl;
[a,1],[b,2],[c,3]
[d,4],[e,5],[f,6]
[a,1],[b,2],[c,3]

 3. 哈希表的查找

理想的情况是希望不经过任何比较,一次存取便能得到所查记录,那就必须在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使每个关键字和结构中一个惟一的存储位置相对应。因而在查找时,只要根据这个对应关系f找到给定值K的像f(K)。若结构中存在关键字和K相等的记录,则必定在f(K)的存储位置上,由此,不需要进行比较便可直接取得所查记录。在此,我们称这个对应关系f为哈希( Hash)函数,按这个思想建立的表为哈希表。

哈希函数的构造方法

哈希函数是从关键字集合到地址集合的映像。通常,关键字集合比较大,它的元素包括所有可能的关键字,而地址集合的元素仅为哈希表中的地址值。哈希函数其实是一个压缩映像,那么这种情况就不可避免的产生冲突,那么在建造哈希表时不仅要设定一个好的哈希函数,还要设定一种处理冲突的方法。

  • 直接定址法

取关键字或关键字的某个线性函数值为哈希地址。即H(key)=key 或 H(key)=a*key+b   (a,b为常数)。

举例:统计解放以后出生人口,其中年份作为关键字,哈希函数取关键字自身加一个常数H(key)=key+(-1948).查找1970年出生的人数,则直接查(1970-1948)=22项即可。

地址 01          02         03   ...           22          23            24  ...    

年份 1949     1950      1951   ...     1970

人数                     .............            15000

  • 除留余数法

取关键字被某个不大于哈希表表长m的数p除后所得余数为哈希地址(p为素数)

H(key)=key  MOD  p,p<=m   (最简单,最常用)p的选取很重要

一般情况,p可以选取为质数或者不包含小于20的质因数的合数(合数指自然数中除了能被1和本身整除外,还能被其他数(0除外)整除的数)。

  • 数字分析法

  • 平方取中法

  • 折叠法

  • 随机数法

常用冲突处理方式

  • 开放定址法

这个方法的基本思想是:当发生地址冲突时,按照某种方法继续探测哈希表中的其他存储单元,直到找到空位置为止。这个过程可用下式描述: 
H i ( key ) = ( H ( key )+ d i ) mod m ( i = 1,2,…… , k ( k ≤ m – 1)) 
其中: H ( key ) 为关键字 key 的直接哈希地址, m 为哈希表的长度, di 为每次再探测时的地址增量。 
采用这种方法时,首先计算出元素的直接哈希地址 H ( key ) ,如果该存储单元已被其他元素占用,则继续查看地址为 H ( key ) + di 的存储单元,如此重复直至找到某个存储单元为空时,将关键字为 key 的数据元素存放到该单元。 
增量 d 可以有不同的取法,并根据其取法有不同的称呼: 

( 1 ) d i = 1 , 2 , 3 , …… 线性探测再散列; 
( 2 ) d i = 1^2 ,- 1^2 , 2^2 ,- 2^2 , k^2, -k^2…… 二次探测再散列; 
( 3 ) d i = 伪随机序列 伪随机再散列;

  • 再散列函数法:(再哈希法)遇到冲突就重新采用一个散列函数计算新的存储位置,可以使关键字不产生聚集

  • 链地址法(拉链)将所有关键字的同义词记录在一个单链表中,在散列表中只存储所有同义词表的头指针

  • 建立一个公共溢出区法: 为所有冲突的关键字开辟一个公共的溢出区(表)来存放

4. 代码

哈希表采用数组存储,哈希函数构造和处理冲突的方法是除留余数法+开放定址法

/****
* Hash Table
*
****/


//#include "Global.h"
#include <iostream>
using namespace std;

// HashTable Data Structure Definition
// array hashtable
#define tablesize 10
typedef int HashTable[tablesize];
//hash function initialization way
void Initial_HashTable(HashTable &ht)
{
    for (int i = 0; i < tablesize; i++)
        ht[i] = 0;
}
//search hashtable function
int Search_HashTable(HashTable &ht,int key)
{
    int address = key%tablesize;
    int compare = 0;
    while (compare < tablesize&&ht[address] != key&&ht[address] != 0)
    {
        compare++;
        address = (address+1)%tablesize;
    }
    if (compare == 10 || ht[address] == 0)
        cout << "can not find elem" << endl;
    return address;
}
//insert hashtable function
void Insert_HashTable(HashTable &ht,int key) 
{
    int res = Search_HashTable(ht,key);
    if (ht[res] == 0) ht[res] = key;
}
//test function
int main()
{
    int data[8] = { 25,36,39,47,20,58,16,35 };
    HashTable ht;

    //initialization.
    Initial_HashTable(ht);

    //insert datas.
    for (int i = 0; i < 8; i++)
    {
        Insert_HashTable(ht, data[i]);
    }
    cout << endl;

    //search.
    cout << "25 : " << Search_HashTable(ht, 25) << endl;
    cout << "35 : " << Search_HashTable(ht, 35) << endl;
    cout << "145 : " << Search_HashTable(ht, 145) << endl;
    system("pause");
    return 0;
}

 

 5. 参考

https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2008/bb982522(v=vs.90)

https://www.cnblogs.com/mydomain/p/11205623.html

https://www.cnblogs.com/ajianbeyourself/p/4358672.html

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM