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. 数据结构与算法分析java——线性表1

    说到线性结构的话,我们可以根据其实现方式分为三类: 1)顺序结构的线性表 2)链式结构的线性表 3)栈和队列的线性表   应用程序后在那个的数据大致有四种基本的逻辑结构: 集合:数据元素之间只有&qu ...

  2. Codeforces 758A Holiday Of Equality

    题目链接:http://codeforces.com/problemset/problem/758/A A. Holiday Of Equality time limit per test 1 sec ...

  3. 贪心,POJ(2709)

    题目链接:http://poj.org/problem?id=2709 解题报告: #include <stdio.h> #include <algorithm> #inclu ...

  4. 问题 C: P4 游戏中的Human角色

    题目描述 在一个平面打斗游戏中,任何的角色(Role)都有血量(blood)和位置loc(此处loc是Location类的实例)属性.有了Role类,可以派生出不同的角色,如人.神仙.怪兽等.如下程序 ...

  5. C语言中%p,%u,%lu都有什么用处

    %p表示输出这个指针, %d表示后面的输出类型为有符号的10进制整形, %u表示无符号10进制整型, %lu表示输出无符号长整型整数 (long unsigned)

  6. 下载Xcode历史版本方法

    1.打开链接:https://developer.apple.com/download/more 进入页面 2.在搜索框输入Xcode,回车搜索.如图,找到各种版本Xcode 搜索Xcode 3.双击 ...

  7. 第23章 I2C—读写EEPROM—零死角玩转STM32-F429系列

    第23章     I2C—读写EEPROM 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/f ...

  8. js字符串内容包含单引号‘’和双引号“”怎么办?

    如果javascript中的字符串包含单引号和双引号,可以用转义字符来标识 'I\'m \"OK\"!'; 表示的字符串内容是:I'm "OK"! 转义字符\可 ...

  9. Element表单验证(1)

    Element表单验证(1) 首先要掌握Element官方那几个表单验证的例子,然后才看下面的教程. Element主要使用了async-validator这个库作为表单验证 async-valida ...

  10. 【学时总结】◆学时·V◆ 逆元法

    ◆学时·V◆ 逆元法 □算法概述□ 逆元运算是模运算中的一个技巧,一般用于解决模运算的除法问题.模运算对于加.减.乘是有封闭性的,即 (a±b)%m=a%m±b%m,以及 (a×b)%m=a%m×b% ...