哈希地址
-
哈希地址是一个逻辑地址,不是实际存在的地址,通过哈希函数得到哈希地址
哈希函数
-
哈希函数由自己决定,选什么函数都可以,假设用数组存储哈希的数据,在数组中用下标去描述位置,通过数据去构造哈希地址,地址就是数组下标
-
图中哈希结构的最大容量为 7
-
一般通过哈希构造函数把数据和下标建立联系
-
取余法得到哈希地址:数据的哈希地址 == 数据 % p(最大容量),通过 % 7得到哈希地址
-
通过哈希构造函数,得到哈希地址 88 % 7 == 4 ,余4把它放在数组下标是4的位置 (把当前元素放到以当前哈希地址为下标的容量的位置)
-
直接定址法:数据是多少,构建出来的哈希地址就是多少,元素是 88,构建出来的哈希地址就是 88,数据就是地址,地址就是数据,图中的元素最小需要的数组长度为 89,浪费空间
-
平方取中法:把原来的数据做平方,取中间一部分
-
折叠法:把字符串转为ASCII码,是一个很大的数字,把数字折叠
哈希冲突
-
通过1个哈希函数所得到的地址可能存在重复(相同的元素)
处理哈希冲突
-
开放地址法:如果要把具有哈希冲突的元素 4 存到哈希结构中,要去数组后面找空的位置,假设数组下标为6的位置有空缺,就把 4 放在这个位置,开放地址法就是把其他空的位置去存储具有哈希冲突的元素
-
邻接表法:图的方式存储采用的就是邻接表法,不开放后面的元素,以当前位置(存在冲突的位置)去创建一个链表出来,把具有冲突的元素放在当前链表中--->以当前元素当做链表的表头去存储元素
构建数据类型
-
如果要处理的是字符串类型的数据,构造一个键出来充当哈希结构的哈希地址的求解--->关联
-
存字符串,没有整型数据,构建一个整型数据处理充当哈希结构的哈希地址的求解
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct pair
{
int key; //键
char element[20]; //数据类型--->字符串类型
}DATA,*LPDATA;
哈希表的创建--->采用取余法
-
得到的哈希地址是用取余法来做的--->数据%p
-
通过取余法所求的哈希地址,所产生的地址个数的范围: [0,p-1]
-
divisior的作用:限定哈希地址的数目 & 存储哈希元素
-
如果用结构体数组充当哈希地址,不好判断空的情况
typedef struct hashTable
{
LPDATA* table; //数据用二级指针表示 便于初始化,以及判断当前hash地址是否存在冲突
int divisor; //H(key)=key%p -->就是限定hash地址的数目 数据要%p需要传入p
int curSize; //当前元素个数
}HASH,*LPHASH;
哈希结构的结构体描述--->用结构体指针表示哈希结构
LPHASH createHashTable(int p)
{
LPHASH hash = (LPHASH)malloc(sizeof(HASH)); //动态内存申请
assert(hash);
//给数据做初始化
hash->curSize = 0;
hash->divisor = p;
hash->table = (LPDATA*)malloc(sizeof(LPDATA)* hash->divisor); //容量由取余数决定
assert(hash->table);
for (int i = 0; i < hash->divisor; i++) //多个一级指针
{
hash->table[i] = NULL; //二级指针申请内存后给每个一级指针做初始化
}
return hash;
}
插入数据
-
注意先申请内存再插入元素
-
用的是二级指针,刚刚只申请了一级指针的内存
//要插入的表 要插入的数据
void insertData(LPHASH hash, DATA data)
{
//做插入前先求hash地址--->调用哈希函数 //不存在哈希冲突
int pos = search(hash, data.key); //找到要存储数据的哈希地址
if (hash->table[pos] == NULL) //当前下标没有数据 直接插入
{
hash->table[pos] = (LPDATA)malloc(sizeof(DATA)); //注意先申请内存再拷贝元素
memcpy(hash->table[pos], &data, sizeof(DATA)); //内存拷贝
hash->curSize++;
}
else //存在哈希冲突
{
if (hash->table[pos]->key == data.key) //键相同回到原来位置
{
strcpy(hash->table[pos]->element, data.element); //用覆盖元素的方式
}
else //键不同回到原来位置说明满了
{
printf("hash表满了,无法插入!\n");
return;
}
}
}
哈希函数
-
可能会存在哈希冲突,需要找合适的位置去存放元素,不一定是直接取余,直接取余得到的地址不一定能用
-
传入键,通过键去找:因为是通过键去生成哈希地址
-
找完一圈后回到原来的位置(通过取余法),说明没有合适的位置
-
如果 curPos + 1中没有元素,把元素放在当前位置即可
//要找的表 传入键
int search(LPHASH hash, int key)
{
int pos = key % hash->divisor; //不存在冲突的hash地址 正常情况
int curPos = pos; //存在冲突 开放地址法做哈希地址的查找
do
{
//key相同,采用覆盖的数据方式 不当做哈希冲突来做
if (hash->table[curPos] == NULL||hash->table[curPos]->key==key)
return curPos; //判断当前位置能不能用 ==NUll说明可以用
curPos=(curPos + 1)%hash->divisor; //不为NULL往后走 如果后面为NULL就插到当前位置
} while (curPos != pos); //当前Pos不等于原来Pos就一直去做查找
return curPos;
}
//!= 原来地址一直做查找
//== 原来地址:找完一圈后回到原来的位置说明它没有合适的位置 直接返回
打印哈希表--->打印数组
void printHash(LPHASH hash)
{
for (int i = 0; i < hash->divisor; i++) //用最大容量长度做打印
{
if (hash->table[i] == NULL)
{
printf("NULL\n");
}
else //不为空打印元素
{
printf("%d:%s\n", hash->table[i]->key, hash->table[i]->element);
}
}
}
测试代码
int main()
{
LPHASH hash = createHashTable(10); //创建哈希表
DATA array[5] = { 1,"雷电",11,"春天",23,"四月",44,"baby",56,"virtual" };
for (int i = 0; i < 5; i++)
{
insertData(hash, array[i]); //把数据插到哈希表中
}
printHash(hash); //打印哈希表
return 0;
}
//测试代码
NULL
1:雷电
11:春天
23:四月
44:baby
NULL
56:virtual
NULL
NULL
NULL