散列查找的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)而直接进行访问的数据结构,它是通过键码值映射到表中一个位置来访问记录的,散列 ...
随机推荐
- mysql8.0.17gtid方式实现主从同步
数据库的安装: [root@node1 8.0.17]# rpm -ivh mysql-community-common-8.0.17-1.el7.x86_64.rpm 警告:mysql-commun ...
- ICEM-水雷
原视频下载地址: https://yunpan.cn/cqhsvXAKUQEA4 访问密码 ef39
- springboot实现异步调用
介绍 所谓的异步执行其实就是使用多线程的方式实现异步调用. 异步有什么好处呢? 如果一个业务逻辑执行完成需要多个步骤,也就是调用多个方法去执行, 这个时候异步执行比同步执行相应更快.不过要注意异步请求 ...
- Ubuntu JDK环境变量
环境变量配置 sudo gedit ~/.bashrc export JAVA_HOME=/usr/local/java/jdk1.8.0_201 export JRE_HOME=${JAVA_HOM ...
- Flutter移动电商实战 --(26)列表页_使用Provide控制子类-2
主要实现功能,点击一级分类,二级分类跟着变.这里主要用我们的provide 新建provide provide文件夹下创建:child_category.dart 事件上就是这个实体:BxMallSu ...
- ccf 201803-3 URL映射(python)
使用正则表达式 import re import collections n, m = list(map(int, input().split())) arr = ['']*(m+n) for i i ...
- LC 981. Time Based Key-Value Store
Create a timebased key-value store class TimeMap, that supports two operations. 1. set(string key, s ...
- SQL-W3School-高级:SQL CREATE INDEX 语句
ylbtech-SQL-W3School-高级:SQL CREATE INDEX 语句 1.返回顶部 1. CREATE INDEX 语句用于在表中创建索引. 在不读取整个表的情况下,索引使数据库应用 ...
- Android Popwindow使用总结
Android Popwindow使用总结 转 https://www.jianshu.com/p/3812ff5ef272 1.基本使用方法 View view = getLayoutInflate ...
- C之结构体
#include<stdio.h> #include<stdlib.h> void study(){ printf("好好学习,天天向上 \n"); } / ...