2014.06.22 12:36

简介:

  哈希是一种数学思想,将不定长数据通过函数转换为定长数据。不定长数据通常意味着碎片化,动态分配内存等等影响存储和性能的因素。当这个定长数据是一个无符号整数时,可以用来表示数组的下标。因此就可以通过这样的哈希算法来把自定义类型的数据存入一个数组中。这样就有了哈希表的基本思想:把自定义类型(定长或者不定长)的数据存入一个数组中,具体应该插入的位置,则由哈希函数计算得出。

描述:

  举个例子,有一个string类型的数组长度为5。那么我们将一些字符串插入到这个数组的过程中。采用如下哈希函数:

    size_t hasher(const string &s)

    {

      size_t result = 0;

      for (int i = 0; i < s.length(); ++i) {

        result = (result * 10 + s[i]) % 5;

      }

      return result;

    }

  以上的hasher函数是一个从string类型到size_t类型的映射。通过观察代码不难发现,函数的返回值始终介于[0, 5),也就可以作为数组下标来使用。

  如果我们姑且认为string是一个整体的话,那么只需要花费“O(1)”的时间就能计算出哈希值,并以这个哈希值(对数组长度取模)来作为插入的位置。

  

  显然,这个哈希函数并不能保证两个不同的字符串是否会得出同一个整数值。那么当两个字符串有相同的哈希值时,后来者就只能另找位置插入,这种情况称为“冲突”。

  另找位置的办法大致分两种:

    1. 开放寻址

      如果要插入的位置已经被占据了,那么我们使用试探函数进行至少1次试探。一个简单的线性试探函数可以是下面这样:

        size_t linearProbing(const int i)

        {

          return i;

        }

        

        size_t quadraticProbing(const int i)

        {

          return i * i;

        }

      在执行第i次试探时,我们将原始的哈希值hash_val加上试探值probing(i),得到hash_value' = hash_value + probing(i),如果这个哈希值能够对应到一个空闲位置,则插入成功。否则继续下一次试探。关于quadraticProbing,可能存在一个隐患,你看出来了吗?

    2. 拉链法

      所谓拉链,其实就是把数组的每个位置用一条链表来表示,这样所有具有相同哈希值的元素都会穿在一条链表上。这样一来,各种操作的性能都会相应受到链表本身的影响而降低。这种方法的插入操作总是分为两部分:先确定插入到数组的哪个下标,然后插入到对应的那条链表中。

  

  对于开放寻址的哈希表,每个数据元素占据一个数组位置。这样使用一段时间之后,哈希表可能会变得很“满”,导致冲突的发生频率升高,降低哈希表的效率。此时我们用“负载系数”来衡量的程度。定义负载系数load_factor = used_slots / total_slots。当负载系数超过某个阈值时(比如0.5),我们就把数组空间扩大,然后把其中所有元素重新插入一次,这个过程称为rehash。如果你熟悉vector动态扩大的过程,想象这个应该很容易。

  关于unordered_set和unordered_map,是boost中很实用的工具类,可以认为就是哈希表。现在它俩已经是C++11中的STL工具类了。虽然和map与set的用法极其类似,但两者的数据结构不同,因此原理和复杂度都不同。通过unordered,你也应该明白哈希表不保证插入元素的顺序,而map和set所基于的平衡树则保证元素插入后保持有序。

  哈希表的关键是键值key。因此从unordered_set<key>到unordered_map<key, value>所需要的改动其实非常小,仅仅是对于value域的一些操作而已。对于哈希表的性质和结构则完全没有影响。

实现:

我实现的一个HashSet例子,使用开放寻址:

 // My implementation for hash set.
#include <iostream>
#include <string>
#include <vector>
using namespace std; template <class KeyType>
struct HashFunctor {
size_t operator () (const KeyType &key) {
const char *ptr = (const char *)&key;
size_t size = sizeof(key);
size_t result; result = ;
for (size_t i = ; i < size; ++i) {
result = (result << ) ^ *(ptr + i);
} return result;
}
}; template<>
struct HashFunctor<string> {
size_t operator() (const string &key) {
size_t size = key.length();
size_t result; result = ;
for (size_t i = ; i < size; ++i) {
result = (result << ) ^ key[i];
} return result;
}
}; template <class KeyType>
class HashSet {
public:
HashSet() {
m_size = ;
m_capacity = MIN_BUCKET_NUM;
m_data.resize(m_capacity);
m_occupied.resize(m_capacity); for (size_t i = ; i < m_capacity; ++i) {
m_occupied[i] = false;
}
} void insert(const KeyType& key) {
size_t h = _findKey(key); if (m_occupied[h]) {
// value already inserted
return;
} m_data[h] = key;
m_occupied[h] = true;
++m_size; if (load_factor() >= 0.5) {
_rehash(m_capacity * + );
}
} void remove(const KeyType& key) {
size_t h = _findKey(key); if (!m_occupied[h]) {
// value not found
return;
} m_occupied[h] = false;
--m_size; if (m_capacity > MIN_BUCKET_NUM && load_factor() <= 0.05) {
_rehash(m_capacity / );
}
} void update(const KeyType& old_key, const KeyType& new_key) {
remove(old_key);
insert(new_key);
} bool find(const KeyType& key) {
size_t h = _findKey(key); return m_occupied[h];
} size_t size() {
return m_size;
} void clear() {
m_size = ;
for (size_t i = ; i < m_capacity; ++i) {
m_occupied[i] = false;
}
} double load_factor() {
return (double)m_size / (double)m_capacity;
} ~HashSet() {
m_data.clear();
m_occupied.clear();
}
private:
static const size_t MIN_BUCKET_NUM = ;
size_t m_size;
size_t m_capacity;
vector<KeyType> m_data;
vector<bool> m_occupied;
HashFunctor<KeyType> m_hasher; size_t _findKey(const KeyType& key) {
size_t hash_value = m_hasher(key);
size_t h;
size_t i; i = ;
while (i < m_capacity) {
// only works for linear probing
// if applied to quadratic probing, the number of buckets must be carefully chosen.
h = (hash_value + _probeFunction(i)) % m_capacity;
if (!m_occupied[h] || key == m_data[h]) {
return h;
} else {
++i;
}
} return m_capacity;
} size_t _probeFunction(int i) {
return i;
} void _rehash(size_t new_capacity) {
vector<KeyType> old_data;
vector<bool> old_occupied; old_data = m_data;
old_occupied = m_occupied; m_data.resize(new_capacity);
m_occupied.resize(new_capacity); size_t i;
size_t old_capacity; m_size = ;
old_capacity = m_capacity;
m_capacity = new_capacity;
for (i = ; i < m_capacity; ++i) {
m_occupied[i] = false;
} for (i = ; i < old_capacity; ++i) {
if (old_occupied[i]) {
insert(old_data[i]);
}
} old_data.clear();
old_occupied.clear();
}
}; int main()
{
typedef long long KeyType;
HashSet<KeyType> hash;
string cmd;
KeyType data; while (cin >> cmd) {
if (cmd == "insert") {
cin >> data;
hash.insert(data);
} else if (cmd == "remove") {
cin >> data;
hash.remove(data);
} else if (cmd == "find") {
cin >> data;
cout << (hash.find(data) ? "true" : "false") << endl;
} else if (cmd == "clear") {
hash.clear();
} else if (cmd == "size") {
cout << hash.size() << endl;
} else if (cmd == "lambda") {
cout << hash.load_factor() << endl;
} else if (cmd == "end") {
break;
}
}
hash.clear(); return ;
}

我实现的一个HashMap,使用拉链法。当时偷了个懒没实现自定义类型,我错了:

 // My implementation for hash map.
#include <iostream>
#include <string>
#include <vector>
using namespace std; class HashMap {
public:
HashMap() {
_buckets.resize(_bucket_num);
int i; for (i = ; i < _bucket_num; ++i) {
_buckets[i] = nullptr;
}
}; bool contains(int key) {
key = (key > ) ? key : -key;
key = key % _bucket_num;
LinkedList *ptr = _buckets[key]; while (ptr != nullptr) {
if (ptr->key == key) {
return true;
}
} return false;
}; int& operator [] (int key) {
key = (key > ) ? key : -key;
key = key % _bucket_num;
LinkedList *ptr = _buckets[key]; if (ptr == nullptr) {
_buckets[key] = new LinkedList(key);
return _buckets[key]->val;
} LinkedList *ptr2 = ptr->next;
if (ptr->key == key) {
return ptr->val;
} while (ptr2 != nullptr) {
if (ptr2->key == key) {
return ptr2->val;
} else {
ptr = ptr->next;
ptr2 = ptr2->next;
}
}
ptr->next = new LinkedList(key);
ptr = ptr->next;
return ptr->val;
} void erase(int key) {
key = (key > ) ? key : -key;
key = key % _bucket_num;
LinkedList *ptr = _buckets[key]; if (ptr == nullptr) {
return;
} else if (ptr->next == nullptr) {
if (ptr->key == key) {
delete _buckets[key];
_buckets[key] = nullptr;
}
return;
} if (ptr->key == key) {
_buckets[key] = ptr->next;
delete ptr;
return;
} LinkedList *ptr2;
ptr2 = ptr->next; while (ptr2 != nullptr) {
if (ptr2->key == key) {
ptr->next = ptr2->next;
delete ptr2;
return;
} else {
ptr = ptr->next;
ptr2 = ptr2->next;
}
}
} ~HashMap() {
int i;
LinkedList *ptr; for (i = ; i < _bucket_num; ++i) {
ptr = _buckets[i];
while (ptr != nullptr) {
ptr = ptr->next;
delete _buckets[i];
_buckets[i] = ptr;
}
}
_buckets.clear();
}
private:
struct LinkedList {
int key;
int val;
LinkedList *next;
LinkedList(int _key = , int _val = ): key(_key), val(_val), next(nullptr) {};
}; static const int _bucket_num = ;
vector<LinkedList *> _buckets;
}; int main()
{
HashMap hm;
string cmd;
int op1, op2; while (cin >> cmd) {
if (cmd == "set") {
cin >> op1 >> op2;
hm[op1] = op2;
} else if (cmd == "get") {
cin >> op1;
cout << hm[op1] << endl;
} else if (cmd == "find") {
cin >> op1;
cout << (hm.contains(op1) ? "true" : "false") << endl;
}
} return ;
}

《数据结构与算法分析:C语言描述》复习——第七章“哈希”——哈希表的更多相关文章

  1. 数据结构与算法分析——C语言描述 第三章的单链表

    数据结构与算法分析--C语言描述 第三章的单链表 很基础的东西.走一遍流程.有人说学编程最简单最笨的方法就是把书上的代码敲一遍.这个我是头文件是照抄的..c源文件自己实现. list.h typede ...

  2. 最小正子序列(序列之和最小,同时满足和值要最小)(数据结构与算法分析——C语言描述第二章习题2.12第二问)

    #include "stdio.h" #include "stdlib.h" #define random(x) (rand()%x) void creat_a ...

  3. C语言学习书籍推荐《数据结构与算法分析:C语言描述(原书第2版)》下载

    维斯 (作者), 冯舜玺 (译者) <数据结构与算法分析:C语言描述(原书第2版)>内容简介:书中详细介绍了当前流行的论题和新的变化,讨论了算法设计技巧,并在研究算法的性能.效率以及对运行 ...

  4. 《数据结构与算法分析——C语言描述》ADT实现(NO.00) : 链表(Linked-List)

    开始学习数据结构,使用的教材是机械工业出版社的<数据结构与算法分析——C语言描述>,计划将书中的ADT用C语言实现一遍,记录于此.下面是第一个最简单的结构——链表. 链表(Linked-L ...

  5. 《数据结构与算法分析-Java语言描述》 分享下载

    书籍信息 书名:<数据结构与算法分析-Java语言描述> 原作名:Data Structures and Algorithm Analysis in Java 作者: 韦斯 (Mark A ...

  6. 《数据结构与算法分析:C语言描述_原书第二版》CH3表、栈和队列_reading notes

    表.栈和队列是最简单和最基本的三种数据结构.基本上,每一个有意义的程序都将明晰地至少使用一种这样的数据结构,比如栈在程序中总是要间接地用到,不管你在程序中是否做了声明. 本章学习重点: 理解抽象数据类 ...

  7. 读书笔记:《数据结构与算法分析Java语言描述》

    目录 第 3 章 表.栈和队列 3.2 表 ADT 3.2.1 表的简单数组实现 3.2.2 简单链表 3.3 Java Collections API 中的表 3.3.1 Collection 接口 ...

  8. 【数据结构与算法分析——C语言描述】第二章总结 算法分析

    算法 算法(algorithm)是为求解一个问题需要遵循的.被清楚地指定的简单指令的集合. 数学基础 四个定义: 1.大O表示法: 如果存在正常数 c 和 n0 使得当 N ≥ n0时,T(N) ≤ ...

  9. 【数据结构与算法分析——C语言描述】第一章总结 引论

    这一章主要复习了一些数学知识,像指数.对数.模运算.级数公式:还有2种证明方法,归纳假设法和反证法.所幸以前学过,重新拾捡起来也比较轻松. 简要地复习了递归,提出了编写递归例程的四条基本法则: 基准情 ...

  10. 《数据结构与算法分析——C语言描述》ADT实现(NO.05) : 散列(Hash)

    散列(Hash)是一种以常数复杂度实现查找功能的数据结构.它将一个关键词Key,通过某种映射(哈希函数)转化成索引值直接定位到相应位置. 实现散列有两个关键,一是哈希函数的选择,二是冲突的处理. 对于 ...

随机推荐

  1. 用yum rpm 快速安装zabbix agent

    用yum 快速安装zabbix agent. wget http://repo.zabbix.com/zabbix/3.4/rhel/7/x86_64/zabbix-agent-3.4.2-1.el7 ...

  2. java 网络流 TCP/UDP

    一.ServerSocket java.lang.Object |-java.net.ServerSocket 有子类SSLServerSocket. 此类实现服务器套接字.服务器套接字等待请求通过网 ...

  3. git 分之合并和冲突解决

    Git 分支管理和冲突解决 创建分支 git branch 没有参数,显示本地版本库中所有的本地分支名称. 当前检出分支的前面会有星号. git branch newname 在当前检出分支上新建分支 ...

  4. linux命令之grep命令

    grep(global search regular expression(RE) and print out the line,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具,它能使用正 ...

  5. 第3章 如何用DAP仿真器下载程序—零死角玩转STM32-F429系列

    第3章     如何用DAP仿真器下载程序 集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/firege ...

  6. React后端管理系统-商品详情detail组件

    import React from 'react'; import MUtil from 'util/mm.jsx' import Product from 'service/product-serv ...

  7. Linux相关知识

    1.设置代理 sudo vi /etc/apt/apt.conf Acquire::http::Proxy "http://proxy_address:8080/"; 2.生成 s ...

  8. motto - Express 4.x Request对象获得参数方法

    本文搜索关键字:motto express node js nodejs javascript request body request.body 1. req.param() 该方法获得参数最为方便 ...

  9. Spring Boot Shiro权限管理--自定义 FormAuthenticationFilter验证码整合

    思路shiro使用FormAuthenticationFilter进行表单认证,验证校验的功能应该加在FormAuthenticationFilter中,在认证之前进行验证码校验. 需要写FormAu ...

  10. 给网站添加icon图标

    只需制成ico结尾的图片即可