最近看PHP数组底层结构,用到了哈希表,所以还是老老实实回去看结构,在这里去总结一下。

1.哈希表的定义

  这里先说一下哈希(hash)表的定义:哈希表是一种根据关键码去寻找值的数据映射结构,该结构通过把关键码映射的位置去寻找存放值的地方,说起来可能感觉有点复杂,我想我举个例子你就会明白了,最典型的的例子就是字典,大家估计小学的时候也用过不少新华字典吧,如果我想要获取“按”字详细信息,我肯定会去根据拼音an去查找 拼音索引(当然也可以是偏旁索引),我们首先去查an在字典的位置,查了一下得到“安”,结果如下。这过程就是键码映射,在公式里面,就是通过key去查找f(key)。其中,按就是关键字(key),f()就是字典索引,也就是哈希函数,查到的页码4就是哈希值。

通过字典查询数据

2.哈希冲突

  但是问题又来了,我们要查的是“按”,而不是“安,但是他们的拼音都是一样的。也就是通过关键字按和关键字安可以映射到一样的字典页码4的位置,这就是哈希冲突(也叫哈希碰撞),在公式上表达就是key1≠key2,但f(key1)=f(key2)。冲突会给查找带来麻烦,你想想,你本来查找的是“按”,但是却找到“安”字,你又得向后翻一两页,在计算机里面也是一样道理的。

  但哈希冲突是无可避免的,为什么这么说呢,因为你如果要完全避开这种情况,你只能每个字典去新开一个页,然后每个字在索引里面都有对应的页码,这就可以避免冲突。但是会导致空间增大(每个字都有一页)。

  既然无法避免,就只能尽量减少冲突带来的损失,而一个好的哈希函数需要有以下特点:

  1.尽量使关键字对应的记录均匀分配在哈希表里面(比如说某厂商卖30栋房子,均匀划分ABC3个区域,如果你划分A区域1个房子,B区域1个房子,C区域28个房子,有人来查找C区域的某个房子最坏的情况就是要找28次)。

  2.关键字极小的变化可以引起哈希值极大的变化。

  比较好的哈希函数是time33算法。PHP的数组就是把这个作为哈希函数。

  核心的算法就是如下:

  1. unsigned long hash(const char* key){
  2. unsigned long hash=;
  3. for(int i=;i<strlen(key);i++){
  4. hash = hash*+str[i];
  5. }
  6. return hash;
  7. }

3.哈希冲突解决办法

   如果遇到冲突,哈希表一般是怎么解决的呢?具体方法有很多,百度也会有一堆,最常用的就是开发定址法和链地址法。

  1.开发定址法

  如果遇到冲突的时候怎么办呢?就找hash表剩下空余的空间,找到空余的空间然后插入。就像你去商店买东西,发现东西卖光了,怎么办呢?找下一家有东西卖的商家买呗。

  由于我没有深入试验过,所以贴上在书上的解释:

  2.链地址法

  上面所说的开发定址法的原理是遇到冲突的时候查找顺着原来哈希地址查找下一个空闲地址然后插入,但是也有一个问题就是如果空间不足,那他无法处理冲突也无法插入数据,因此需要装填因子(插入数据/空间)<=1。

  那有没有一种方法可以解决这种问题呢?链地址法可以,链地址法的原理时如果遇到冲突,他就会在原地址新建一个空间,然后以链表结点的形式插入到该空间。我感觉业界上用的最多的就是链地址法。下面从百度上截取来一张图片,可以很清晰明了反应下面的结构。比如说我有一堆数据{1,12,26,337,353...},而我的哈希算法是H(key)=key mod 16,第一个数据1的哈希值f(1)=1,插入到1结点的后面,第二个数据12的哈希值f(12)=12,插入到12结点,第三个数据26的哈希值f(26)=10,插入到10结点后面,第4个数据337,计算得到哈希值是1,遇到冲突,但是依然只需要找到该1结点的最后链结点插入即可,同理353。

  

哈希表的拉链法实现

  下面解析一下如何用C++实现链地址法。

  第一步。

  肯定是构建哈希表。

  首先定义链结点,以结构体Node展示,其中Node有三个属性,一个是key值,一个value值,还有一个是作为链表的指针。还有作为类的哈希表。

  1. #define HASHSIZE 10
  2. typedef unsigned int uint;
  3. typedef struct Node{
  4. const char* key;
  5. const char* value;
  6. Node *next;
  7. }Node;
  8.  
  9. class HashTable{
  10. private:
  11. Node* node[HASHSIZE];
  12. public:
  13. HashTable();
  14. uint hash(const char* key);
  15. Node* lookup(const char* key);
  16. bool install(const char* key,const char* value);
  17. const char* get(const char* key);
  18. void display();
  19. };

  然后定义哈希表的构造方法

  1. HashTable::HashTable(){
  2. for (int i = ; i < HASHSIZE; ++i)
  3. {
  4. node[i] = NULL;
  5. }
  6. }

  第二步。

  定义哈希表的Hash算法,在这里我使用time33算法。

  1. uint HashTable::hash(const char* key){
  2. uint hash=;
  3. for (; *key; ++key)
  4. {
  5. hash=hash*+*key;
  6. }
  7. return hash%HASHSIZE;
  8. }

  第三步。

  定义一个查找根据key查找结点的方法,首先是用Hash函数计算头地址,然后根据头地址向下一个个去查找结点,如果结点的key和查找的key值相同,则匹配成功。

  1. Node* HashTable::lookup(const char* key){
  2. Node *np;
  3. uint index;
  4. index = hash(key);
  5. for(np=node[index];np;np=np->next){
  6. if(!strcmp(key,np->key))
  7. return np;
  8. }
  9. return NULL;
  10. }

  第四步。

  定义一个插入结点的方法,首先是查看该key值的结点是否存在,如果存在则更改value值就好,如果不存在,则插入新结点。

  1. bool HashTable::install(const char* key,const char* value){
  2. uint index;
  3. Node *np;
  4. if(!(np=lookup(key))){
  5. index = hash(key);
  6. np = (Node*)malloc(sizeof(Node));
  7. if(!np) return false;
  8. np->key=key;
  9. np->next = node[index];
  10. node[index] = np;
  11. }
  12. np->value=value;
  13. return true;
  14. }

  

4.关于哈希表的性能

  由于哈希表高效的特性,查找或者插入的情况在大多数情况下可以达到O(1),时间主要花在计算hash上,当然也有最坏的情况就是hash值全都映射到同一个地址上,这样哈希表就会退化成链表,查找的时间复杂度变成O(n),但是这种情况比较少,只要不要把hash计算的公式外漏出去并且有人故意攻击(用兴趣的人可以搜一下基于哈希冲突的拒绝服务攻击),一般也不会出现这种情况。

哈希冲突攻击导致退化成链表

  最后附上完整代码下载地址。点此下源码

数据结构之哈希(hash)表的更多相关文章

  1. Redis数据结构:字典(hash表)

    使用场景: # set person name "tom" # set person name "jerry" 1. 字典结构: 哈希表数据结构 typedef ...

  2. HDU5183 hash 表

    做题的时候忘了 数据结构老师说的hash表了, 用二分找,还好过了, hash 表 对这题 更快一些 #include <iostream> #include <algorithm& ...

  3. 数据结构,哈希表hash设计实验

    数据结构实验,hash表 采用链地址法处理hash冲突 代码全部自己写,转载请留本文连接, 附上代码 #include<stdlib.h> #include<stdio.h> ...

  4. Redis原理再学习04:数据结构-哈希表hash表(dict字典)

    哈希函数简介 哈希函数(hash function),又叫散列函数,哈希算法.散列函数把数据"压缩"成摘要,有的也叫"指纹",它使数据量变小且数据格式大小也固定 ...

  5. java数据结构之hash表

    转自:http://www.cnblogs.com/dolphin0520/archive/2012/09/28/2700000.html Hash表也称散列表,也有直接译作哈希表,Hash表是一种特 ...

  6. php 数据结构 hash表

    hash表 定义 hash表定义了一种将字符组成的字符串转换为固定长度(一般是更短长度)的数值或索引值的方法,称为散列法,也叫哈希法.由于通过更短的哈希值比用原始值进行数据库搜索更快,这种方法一般用来 ...

  7. Hash 表详解(哈希表)

    散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列 ...

  8. 哈希表(散列表),Hash表漫谈

    1.序 该篇分别讲了散列表的引出.散列函数的设计.处理冲突的方法.并给出一段简单的示例代码. 2.散列表的引出 给定一个关键字集合U={0,1......m-1},总共有不大于m个元素.如果m不是很大 ...

  9. 哈希表(散列表)—Hash表解决地址冲突 C语言实现

    哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.具体的介绍网上有很详 ...

随机推荐

  1. postgresql从timestamp(6)复制到timestamp(0),时间会变

    主要涉及临界点(跨天) 例子(时间:2016-08-05 23:59:59.863) timestamp(6):2016-08-05 23:59:59.863 timestamp(0):2016-08 ...

  2. django+javascrpt+python实现私有云盘

    代码稍后上,先整理下私有云盘的相关功能介绍. 1.登陆界面 2.首页展示,有个人目录.部门目录以及公司目录,针对不用的目录设置不同的权限控制. 3.个人信息展示 4.账号管理.账号信息展示 5.账号添 ...

  3. Java-JSON 解析

    JSON  JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式.它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采 ...

  4. 实验五:任意输入10个int类型数据,排序输出,并找出素数

    源代码: package 数组;import java.util.*;public class vvv { public static void main(String[] args) { Scann ...

  5. JavaScript 获取完整当前域名

    <script> var dq_url = window.location.protocol+"//"+window.location.host;/*获取当前域名*/ ...

  6. AI-2.梯度下降算法

    上节定义了神经网络中几个重要的常见的函数,最后提到的损失函数的目的就是求得一组合适的w.b 先看下损失函数的曲线图,如下 即目的就是求得最低点对应的一组w.b,而本节要讲的梯度下降算法就是会一步一步地 ...

  7. JSON File Parse

    1.write a json file base on website(在网站上写一个json文件) json文件网址:https://raw.githubusercontent.com/DJOSIM ...

  8. webapi 知识点

    在web api 中后台的方法必须 加入 [HttpGet] ,[HttpPost],[HttpPut],[HttpDelete] 来区分,这是一种习惯. ps: get 方式参数都存在http协议头 ...

  9. mongodb怎么创建数据库和配置用户

    mongodb怎么创建数据库和配置用户,远程连接是不够的,还要上去操作和放数据的. 系统:centos 5.x 环境:mongodb 1.安装mongodb 这步就不说了,大家自己去看Centos安装 ...

  10. WinRAR存在严重的安全漏洞影响5亿用户

    WinRAR可能是目前全球用户最多的解压缩软件,近日安全团队发现并公布了WinRAR中存在长达19年的严重安全漏洞,这意味着有可能超过5亿用户面临安全风险. 该漏洞存在于所有WinRAR版本中包含的U ...