散列查找的C实现
概念
散列查找,类似与查英文字典的过程。如果我们要查找“zoo”(key)对应的释义(value),我们不会从第一页开始逐页查找(顺序查找),而是直接根据大致的推算(Hash函数),找到最后几页。
所以散列查找的基本思想是根据key通过hash函数计算出value的位置,然后得到value。
按理说每一个key都对应不同的地址是最理想的,然而这种hash函数很难找到(或者说没有实用意义,比如线性函数)就会发生多个key对应一个地址的现象,这是就需要去处理冲突。
数字关键字的散列函数的构造方法一般有:
1)直接定址法
h(key) = a * key + b
计算简单,且不会产生冲突,但要求地址集合与关键词集合大小相同,所以并不实用。
数组使用的就是直接定址法。
2)除留余数法
h(key) = key mod p
一般选取key为素数,这样均匀分布的可能性比较大。
3)数字分析法
就是根据具体情况来处理。比如使用身份证号码作为关键字,那么应选取其中比较随机的数值,比如前两位代表省编号的数字就不适合,会导致聚集效应。
字符关键字的散列函数构造
1)ASCII码加和法
h(key) = (∑key[i]) mod TableSize
即将每一位字符对应的ASCII码加起来求和作为特征值。但这种方法太过简单,假设最大长度为8的字符串,(∑key[i])的取值区间为0至1016,而关键词集合包含有63^8种关键词,如此显然会造成严重的冲突。
2)简单改进——前三个字符移位法
h(key) = (key[0] + key[1]*27 + key[2]*27^2) mod TableSize
3)好的散列函数——移位法
只考虑26个字母,那么一个字母至少需要5位去存储,假设字符串最大长度为12,那么取一个64位长度的无符号整数,依次移位将字符串映射到整数上去,便得到了特征值,不会出现重复。
处理冲突的方法
1)开放地址法
1.线性探测法
比如在插入时发生冲突(已被占据),那么逐次向后查找空位。
会产生一次聚集(Primary Clustering)现象
2.平方探测法
2)分离链表法
实现
//头文件
//HashTable.h
#ifndef HASHTABLE_H_
#define HASHTABLE_H_
#include <stdbool.h>
#define KEYLENGTH 20
#define MAXTABLESIZE 100000
//哈希表类型
typedef struct TblNode* HashTable;
//关键字类型为字符串,最大长度由KEYLENGTH宏定义,只能包含大小写字母,数字,及下划线和空格
typedef char KeyType[KEYLENGTH + 1];
//值类型,为void指针
typedef void* ValueType;
//自定义的释放数据函数指针类型
typedef void (*FreeIt)(void* data);
//创建一个容量为N的散列表(N < MAXTABLESIZE)
//表越大性能越好,同时也越占据内存
HashTable CreateTable(int TableSize);
//向表中 插入/修改 键值关系,如果出错则返回false(比如key中有非法字符)
bool InsertEntry(HashTable H, const KeyType key, const ValueType data);
//通过key-value的关系获取值,如果不存在或key中有非法字符则返回NULL
ValueType getValue(const HashTable H, const KeyType key);
//通过key来删除关系,失败返回false
bool DelEntry(HashTable H, const KeyType key);
//释放散列表占据的内存
//如果不需要释放数据,则第二个为NULL
void DestroyTable(HashTable H, FreeIt func);
#endif
//HashTable.c
#include "HashTable.h"
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
#include <malloc.h>
#include <math.h>
#include <stdio.h>
#define ERROR -1
//一个存储单元包括键,值,及状态标记
typedef struct HashEntry Cell;
struct HashEntry {
KeyType Key; //Key是一个数组
ValueType Data;
Cell* Next; //单向链表
};
//哈希表,存放存储单元数组
//主干使用数组,分支使用链表
typedef struct TblNode* HashTable;
struct TblNode {
int TableSize;
Cell* Cells;
};
//将单个字符(数字、字母、下划线或空格)映射到0-63上
//返回ERROR表示出错
static int CharMap_(char ch)
{
//数字映射到0-9
if (isdigit(ch)) {
return ch - '0';
} else
//大写字母映射到10-35
if (isupper(ch)) {
return ch - 'A' + 10;
} else
//小写字母映射到36-61
if(islower(ch)) {
return ch - 'a' + 36;
} else
//空格映射到62
if (ch == ' ') {
return 62;
} else
//下划线映射到63
if (ch == '_') {
return 63;
} else {
//超出映射范围
fprintf(stderr, "Error key format\n");
return ERROR;
}
}
//利用移位法计算字符串关键字映射的地址
//每一个字符有26*2+2+10=2^6,64/6=10,
//字符串最多10位,如果超过10位,便只取奇数位数字
//于是有效长度为20位
//如果出错,返回ERROR
static int Hash(const char* key, int TableSize)
{
//关键字长度
int len = strlen(key);
//步长
int step = (len > 10) ? 2 : 1;
//字符串尾指针
const char* const end = key + len;
//散列函数值,初始化为0
uint64_t H = 0;
//每一次左移6位,把字符的6位映射刻上去
while (key < end) {
int val = CharMap_(*key);
if (val == ERROR) {
//错误传递
fprintf(stderr, "Error in Hash()\n");
return ERROR;
}
H = (H << 6) + val;
key += step;
}
//将计算出来的字符串特征值对表长取模即为地址
return (H % TableSize);
}
//返回大于N且小于MAXTABLESIZE的最小素数
static int NextPrime(int N)
{
//从大于N的下一个奇数开始
int i, p = (N % 2) ? (N + 2) : (N + 1);
while (p < MAXTABLESIZE) {
for (i = (int)sqrt(p); i > 2; --i) {
if (p % i == 0) break;
}
//说明for没有中途退出,p是素数
if (i == 2) break;
//否则试探下一个素数
else p += 2;
}
return p;
}
//找到key对应的cell的前一个cell的指针,如果返回NULL说明出了问题
//正常情况下不会返回NULL
static Cell* FindEntryPre(const HashTable H, const KeyType key)
{
//计算得到数组中的位置
int p = Hash(key, H->TableSize);
if (p == ERROR) {
return NULL;
}
Cell* pre = &(H->Cells[p]);
for (; (pre->Next) && strcmp(key, (pre->Next)->Key); pre = pre->Next);
return pre;
}
//创建一个容量为N的散列表(N < MAXTABLESIZE)
HashTable CreateTable(int TableSize)
{
HashTable H = (HashTable)malloc(sizeof(struct TblNode));
//散列表中的数组的实际大小比输入的TableSize大一点
H->TableSize = NextPrime(TableSize);
//为数组分配内存
H->Cells = (Cell*)malloc(sizeof(Cell) * H->TableSize);
//初始化数组
for (int i = 0; i < H->TableSize; ++i) {
H->Cells[i].Data = NULL;
H->Cells[i].Key[0] = '\0';
H->Cells[i].Next = NULL;
}
return H;
}
//插入键值关系,如果出错则返回false(比如key中有非法字符)
bool InsertEntry(HashTable H, const KeyType key, const ValueType data)
{
Cell* pre = FindEntryPre(H, key);
if (pre == NULL) {
fprintf(stderr, "Error in InsertEntry()\n");
return false;
}
//如果是空的,那就新建一个
if (pre->Next == NULL) {
pre->Next = (Cell*)malloc(sizeof(Cell));
strcpy((pre->Next)->Key, key);
pre->Next->Next = NULL;
}
//修改对应的value
pre->Next->Data = data;
return true;
}
//key-value获取值,如果不存在或key中有非法字符则返回NULL
ValueType getValue(const HashTable H, const KeyType key)
{
Cell* pre = FindEntryPre(H, key);
if (pre == NULL) {
fprintf(stderr, "Error in InsertEntry()\n");
return NULL;
}
//不存在,则返回NULL
if ((pre->Next) == NULL) {
return NULL;
} else {
return pre->Next->Data;
}
}
//通过key来删除关系
bool DelEntry(HashTable H, const KeyType key)
{
Cell* pre = FindEntryPre(H, key);
if (pre == NULL) {
fprintf(stderr, "Error in DelEntry()\n");
return false;
} else
if (pre->Next == NULL) {
printf("Can not find %s!\n", key);
} else {
//删除此链表节点
Cell* mid = pre->Next;
pre->Next = mid->Next;
free(mid);
}
return true;
}
//从给定位置开始释放单项链表(递归)
static void freeList_(Cell* begin, FreeIt func)
{
if (begin) {
freeList_(begin->Next, func);
if (func) func(begin->Data);
free(begin);
}
}
//释放散列表占据的内存
//如果不需要释放数据,则第二个为NULL
void DestroyTable(HashTable H, FreeIt func)
{
//首先释放分支上的链表
for (int i = 0; i < H->TableSize; ++i) {
//跳过链表头,递归释放
freeList_(H->Cells[i].Next, func);
}
//然后释放主干的数组
free(H->Cells);
//最后释放表
free(H);
}
散列查找的C实现的更多相关文章
- [置顶] Hash查找,散列查找
//Hash.h #ifndef HASH_H #define HASH_H #define HASH_ARR_SIZE 100 #define FILL -1 #include <stdlib ...
- 【知识强化】第六章 查找 6.4 散列(Hash)表
本节课我们来学习一种新的查找方式叫做散列查找.什么是散列查找呢?在学习散列查找之前,一定要介绍一个基本概念就是散列表.那么学习散列表之前我们先来回忆一下之前所学习过的所有查找方式,那么无论是顺序查找还 ...
- 散列之HashTable学习
1,什么是散列? 举个例子,在日常生活中,你将日常用品都放在固定的位置,当你下次需要该东西时,直接去该地方取它.这个过程就相当于散列查找. 若将它们随意杂乱无章地存放,当需要某件东西时,只能一个地方一 ...
- 数据结构---散列表查找(哈希表)概述和简单实现(Java)
散列表查找定义 散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,是的每个关键字key对应一个存储位置f(key).查找时,根据这个确定的对应关系找到给定值的key的对应f(key) ...
- 数据结构(四十二)散列表查找(Hash Table)
一.散列表查找的基础知识 1.散列表查找的定义 散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key).查找时,根据这个确定的对应关系找到 ...
- 算法与数据结构(十二) 散列(哈希)表的创建与查找(Swift版)
散列表又称为哈希表(Hash Table), 是为了方便查找而生的数据结构.关于散列的表的解释,我想引用维基百科上的解释,如下所示: 散列表(Hash table,也叫哈希表),是根据键(Key)而直 ...
- DS哈希查找--线性探测再散列
题目描述 定义哈希函数为H(key) = key%11.输入表长(大于.等于11),输入关键字集合,用线性探测再散列构建哈希表,并查找给定关键字. --程序要求-- 若使用C++只能include一个 ...
- 【数据结构与算法Python版学习笔记】查找与排序——散列、散列函数、区块链
散列 Hasing 前言 如果数据项之间是按照大小排好序的话,就可以利用二分查找来降低算法复杂度. 现在我们进一步来构造一个新的数据结构, 能使得查找算法的复杂度降到O(1), 这种概念称为" ...
- javascript数据结构与算法--散列
一:javascript数据结构与算法--散列 一:什么是哈希表? 哈希表也叫散列表,是根据关键码值(key,value)而直接进行访问的数据结构,它是通过键码值映射到表中一个位置来访问记录的,散列 ...
随机推荐
- shell 重定向0,1,2
.1和2分别表示标准输入.标准输出和标准错误信息输出,可以用来指定需要重定向的标准输入或输出,比如 >a.txt 表示将错误信息输出到文件a.txt中. #将1,2输出转发给/dev/null设 ...
- Git基础命令学习
Git是项目代码管理软件 主要管理逻辑如下: 所有代码保存在远程,本地获取远程代码保存在本地仓库,并于本地工作目录修改代码 修改完成后,提交到本地暂存区,添加必要注释,再尝试提交到远程仓库 若发生冲突 ...
- 理解了这些异常现象才敢说真正懂了TCP协议
很多人总觉得学习TCP/IP协议没什么用,觉得日常编程开发只需要知道socket接口怎么用就可以了.如果大家定位过线上问题就会知道,实际上并非如此.如果应用在局域网内,且设备一切正常的情况下可能确实如 ...
- JAVA基础知识|HTTP协议-两个特性
一.无连接 无连接:服务器与浏览器之间的一次连接只处理一个http请求,请求处理结束后,连接断开.下一次请求再重新建立连接. 然而随着互联网的发展,一台服务器同一时间处理的请求越来越多,如果依然采用原 ...
- (六)爬虫之使用selenium
selenium是使用javascript编写,主要用来进行web应用程序测试,在python爬虫中可以用来进行动态网页爬取,解决爬虫中的javascript渲染(执行js语句).总结记录下,以备后面 ...
- eclipse远程连接hadoop单机模式出现的问题
按照http://tydldd.iteye.com/blog/2007938配置单机模式 主要是 (1)配置hadoop-env.sh,指定jdk的安装路径 添加jdk路径 # The java im ...
- SQL-W3School-高级:SQL 撤销索引、表以及数据库
ylbtech-SQL-W3School-高级:SQL 撤销索引.表以及数据库 1.返回顶部 1. 通过使用 DROP 语句,可以轻松地删除索引.表和数据库. SQL DROP INDEX 语句 我们 ...
- linux简单命令7--管道符和通配符
”&&“和管道符“|”不一样. ---------------------------------------------------------通配符---------------- ...
- 使用rsync备份数据
(1).实验环境与目标 源主机:youxi1 192.168.5.101 目标主机:youxi2 192.168.5.102 目标:将源主机youxi1的数据备份到youxi2上. rsync是C/S ...
- Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.5:test
解决方法: 打包跳过测试有两种方法 一是命令行 mvn clean package -Dmaven.test.skip=true 二是写入pom文件 <plugin> <groupI ...