这篇博客的目的是让尚未学会hash表的朋友们对hash表有一个直观的理解,并且能根据本文定义出属于自己的第一个hash表,但算不上研究文,没有深究概念和成功案例。
什么是hash表?
hash表也叫做散列表,是一种通过键值快速访问数据的结构,hash表有两种常见的定义形式:数组、数组和链表的结合。
理解hash表的关键:
1.散列法
将字符组成的字符和字符串转换为固定长度的数值和索引值的方法,通过更短的hash值进行搜索比用原值搜索更快,通常用于数据库建立索引或者加解密。
2.装填因子
设m和n分别表示表长和表中填入的节点数,将α=n/m定义为散列表的装填因子,装填因子越大,越容易冲突。
3.散列函数
压缩待处理的键值,降低空间开销。
4.冲突
两个不同的键值具有同一个散列函数值,因而映射到散列表的同一位置,称为冲突或碰撞,冲突的两个键值称为同义词。
冲突与散列函数有关,也和表的装填因子有关,hash表在即将填满时冲突几率提高,性能下降严重,但整体是一种极其高效的算法。
hash表基本上无法避免冲突。
5.处理冲突
如果由键值得到的散列函数值(以后用hash地址称呼)已经存有记录,则继续寻找下一个空的hash地址。
常见的处理冲突方法有开放寻址法、再散列法、拉链法、建立公共溢出区。
以拉链法定义hash表为例:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<Windows.h>
using namespace std;
//用于避免冲突的链表,同义键值插入链表
//散列法中装填因子可以大于1,即待插入表元素总数可以大于表长度,但通常建议装填因子<=1,可以最大限度通过键值直接映射hash表
struct Node
{
int data;
struct Node* next;
};
//hash表主体,上面的链表是用来辅助解决冲突的
struct Hash_Table
{
Node* Value[100];
};
//创建hash表
Hash_Table* CreateHashTable()
{
Hash_Table* ptHash = (Hash_Table*)malloc(sizeof(Hash_Table));
memset(ptHash, 0, sizeof(Hash_Table));
return ptHash;
}
//在Hash表中寻找数据
//hash法为除留余数法
Node* FindHashData(Hash_Table* pHashTbl, int data)
{
Node* pNode;
if (NULL == pHashTbl)
return NULL;
if (NULL == (pNode = pHashTbl->Value[data % 100])) //该hash地址尚未插入数据,data%100就是此处应用的hash法
return NULL;
//遍历该hash地址指向的单链表的数据,如果键值等于hash表中存储的键值数据,匹配到就返回节点。
while (pNode)
{
if (data = pNode->data)
return pNode;
pNode = pNode->next;
}
return NULL;
}
//在Hash表中插入数据
BOOL InsertDataIntoHashTable(Hash_Table* pHashTbl, int data)
{
Node* pNode;
if (NULL == pHashTbl)
return NULL;
if (NULL == pHashTbl->Value[data % 100]) //该节点尚未插入数据
{
pNode = (Node*)malloc(sizeof(Node));
pNode->data = data;
pNode->next = NULL;
pHashTbl->Value[data % 100] = pNode;
return TRUE;
}
//如果该键值已经插入hash表则插入失败,hash表存在同义键值,但不保存重复键值的数据
if (NULL != FindHashData(pHashTbl, data))
return FALSE;
pNode = pHashTbl->Value[data % 100];
while (NULL != pNode)
pNode = pNode->next;
//插入hash地址指向链表的末尾
pNode->next = (Node*)malloc(sizeof(Node));
pNode->next->data = data;
pNode->next->next = NULL;
return TRUE;
}
//从hash表中删除数据
BOOL DeleteDataFromHashTable(Hash_Table* pHashTbl, int data)
{
Node* pNode,*pHead;
if (NULL == pHashTbl)
return FALSE;
if (NULL == pHashTbl->Value[data % 100])
return FALSE;
if (NULL == (pNode = FindHashData(pHashTbl, data)))
return FALSE;
//如果查找到的hash节点是hash地址链表的首元素,重定向指针并删除。
if (pNode == pHashTbl->Value[data % 100])
{
pHashTbl->Value[data % 100]->next = pNode->next;
free(pNode);
return TRUE;
}
//如果查找到的hash节点不是hash地址链表的首元素,定位到pNode的上一个节点后重定向指针并删除。
pHead = pHashTbl->Value[data % 100];
while (pHead->next != pNode)
pHead = pHead->next;
pHead->next = pNode->next;
free(pHead);
return TRUE;
}
上面的例子是读过一位前辈的例子后模仿的,前辈的例子已经非常精炼,很难有修改的地方,就加了一些注释方便大家理解,下面是学习开放定址法后自己写的一个例子,请大家指教。
//依据hash处理冲突的开放寻址法
//开放寻址法有三种探查技术:这里用的线性探测再散列方法
typedef enum _USE_STATUS {
STATUS_EMPTY = 0,
STATUS_NORMAL_USE = 1,
STATUS_DELETED = 2
}USE_STATUS;
struct HASH_DATA
{
int keyvalue;
USE_STATUS use;
};
struct HASH_TABLE
{
HASH_DATA data[100];
};
//定义哈希函数,又称散列函数
int hash_func(int key)
{
return key%10;
};
//定义处理冲突的增量序列,线性探测再散列方法增量
//线性探测的缺点:1.处理溢出需另编程序。2.删除工作困难,删除元素的时候需要将单元打上删除标记,不能直接设置元素为空,否则影响后续探测。
//3.处理不确定的关键字域时很容易产生堆聚现象,堆聚具有一旦堆聚就更加容易堆聚的特点。
int hash_di(int val)
{
return val;
};
HASH_TABLE* CreateHashTable()
{
HASH_TABLE* hash = new HASH_TABLE;
memset(hash, 0, sizeof(HASH_TABLE));
return hash;
};
//若是当前探查的单元为空,表示查找失败
//若探查到T[d-1]仍然没有查找到,表示查找失败
HASH_DATA* FindHashTable(HASH_TABLE* head, int key)
{
for(int i = 0; i < 100; i++)
{
if(head->data[(hash_func(key)+hash_di(i))%100].keyvalue == key && STATUS_NORMAL_USE == head->data[(hash_func(key)+hash_di(i))%100].use)
{
return &(head->data[hash_func(key)+i]);
}
if(STATUS_EMPTY == head->data[(hash_func(key)+hash_di(i))%100].use)
return NULL;
}
return NULL;
}
//若是当前探查的单元中含有key,则插入失败
//找到空元素或者已删除的元素就插入其中
BOOL InsertHashNode(HASH_TABLE* head, int key)
{
for(int i = 0; i < 100; i++)
{
if(head->data[(hash_func(key)+hash_di(i))%100].use == STATUS_DELETED || head->data[(hash_func(key)+hash_di(i))%100].use == STATUS_EMPTY)
{
head->data[(hash_func(key)+hash_di(i))%100].keyvalue = key;
head->data[(hash_func(key)+hash_di(i))%100].use = STATUS_NORMAL_USE;
return TRUE;
}
}
return FALSE;
}
//若是探查T[d-1]的时候仍然未找到包含key的单元,则删除失败
//线性探查法找到待删除的元素时只能标记为已删除
//当找到为空的元素仍然没有找到对应的key,则删除失败
BOOL DeleteHashNode(HASH_TABLE* head, int key)
{
for(int i = 0; i < 100; i++)
{
if(head->data[(hash_func(key)+hash_di(i))%100].keyvalue == key && STATUS_NORMAL_USE == head->data[(hash_func(key)+hash_di(i))%100].use)
{
head->data[(hash_func(key)+hash_di(i))%100].use = STATUS_DELETED;
return TRUE;
}
else if(STATUS_EMPTY == head->data[(hash_func(key)+hash_di(i))%100].use)
{
return FALSE;
}
}
return FALSE;
}
- Redis数据结构:字典(hash表)
使用场景: # set person name "tom" # set person name "jerry" 1. 字典结构: 哈希表数据结构 typedef ...
- HDU5183 hash 表
做题的时候忘了 数据结构老师说的hash表了, 用二分找,还好过了, hash 表 对这题 更快一些 #include <iostream> #include <algorithm& ...
- php 数据结构 hash表
hash表 定义 hash表定义了一种将字符组成的字符串转换为固定长度(一般是更短长度)的数值或索引值的方法,称为散列法,也叫哈希法.由于通过更短的哈希值比用原始值进行数据库搜索更快,这种方法一般用来 ...
- java数据结构之hash表
转自:http://www.cnblogs.com/dolphin0520/archive/2012/09/28/2700000.html Hash表也称散列表,也有直接译作哈希表,Hash表是一种特 ...
- 【数据结构】Hash表
[数据结构]Hash表 Hash表也叫散列表,是一种线性数据结构.在一般情况下,可以用o(1)的时间复杂度进行数据的增删改查.在Java开发语言中,HashMap的底层就是一个散列表. 1. 什么是H ...
- Redis原理再学习04:数据结构-哈希表hash表(dict字典)
哈希函数简介 哈希函数(hash function),又叫散列函数,哈希算法.散列函数把数据"压缩"成摘要,有的也叫"指纹",它使数据量变小且数据格式大小也固定 ...
- 【数据结构】Hash表简介及leetcode两数之和python实现
文章目录 Hash表简介 基本思想 建立步骤 问题 Hash表实现 Hash函数构造 冲突处理方法 leetcode两数之和python实现 题目描述 基于Hash思想的实现 Hash表简介 基本思想 ...
- 6.数组和Hash表
当显示多条结果时,存储在变量中非常智能,变量类型会自动转换为一个数组. 在下面的例子中,使用GetType()可以看到$a变量已经不是我们常见的string或int类型,而是Object类型,使用-i ...
- 数组和Hash表
数组和Hash表 当显示多条结果时,存储在变量中非常智能,变量类型会自动转换为一个数组. 在下面的例子中,使用GetType()可以看到$a变量已经不是我们常见的string或int类型,而是Obje ...
随机推荐
- 一步一步制作yaffs/yaffs2根文件系统(一)---储备好基础知识再打
开发环境:Ubuntu 12.04 开发板:mini2440 256M NandFlash 64M SDRAM 交叉编译器:arm-linux-gcc 4.4.3点此可下载 BusyBox版本: ...
- ISE综合后得到的RTL图如何与硬件对应起来,怎么知道每个element的功能
2013-06-23 21:34:03 要知道“我写的这段代码会综合成什么样的电路呢”,就要搞清楚RTL图中每个模块的功能,从而将代码与硬件对应,判断综合后的电路是否与预期的一致.如何做到? 之前查了 ...
- MySQL优化器 limit影响的case
测试的用例中,因为limit的大小不同,而产生了完全不同的执行计划: 1. 测试case: create table t1 ( f1 ) not null, f2 ) not null, f3 ) n ...
- 让Windows Server 2008 + IIS 7+ ASP.NET 支持10万个同时请求
具体设置如下: 1. 调整IIS 7应用程序池队列长度 由原来的默认1000改为65535. IIS Manager > ApplicationPools > Advanced Setti ...
- jquery serialize()方法的扩展
Jquery提供的序列化表单方法serialize方法确实方便,但是我在使用的时候发现了一个弊端:当我使用type:“post”进行ajax请求的时候, 这个时候参数data:$("#myf ...
- 使用powerdesigner 画图的详细说明
一.概念数据模型概述 数据模型是现实世界中数据特征的抽象.数据模型应该满足三个方面的要求: 1)能够比较真实地模拟现实世界 2)容易为人所理解 3)便于计算机实现 概念数据模型也称信息模型,它以实体- ...
- Android学习系列(1)--为App签名(为apk签名)
写博客是一种快乐,前提是你有所写,与人分享,是另一种快乐,前提是你有舞台展示,博客园就是这样的舞台.这篇文章是android开发人员的必备知识,是我特别为大家整理和总结的,不求完美,但是有用. 1.签 ...
- FOR 循环 索引从n 开始
RF 中FOR 循环默认是从0开始,如果想从任意n开始如下所示: 方法一: 结果,如你所愿输出1-6: 方法二,利用FOR遍历list来实现: 结果: 这里注意是输出1-9而不是1-10
- mssql server 2005还原数据库bak文件与“备份集中的数据库备份与现有的xx数据库不同”解决方法
mssql server 2005还原数据库bak文件,网站使用虚拟主机建站会经常遇到,一般情况下,主机商有在线的管理程序,但有时候没有的话,就需要本地还原备份sql数据库了.这种情况mssql se ...
- CRC(Cyclic Redundancy Check)循环冗余校验码与海明码的计算题
(17)采用CRC进行差错校验,生成多项式为G(X)=X4+X+1,信息码字为10111,则计算出的CRC校验码是 (17) .A.0000 B.0100 C.0010 D.1100试题 ...