C_哈希结构的简单了解

1.基本了解

哈希

:数据与内存一一映射关系,该结构通过key值映射的位置去寻找存放值的地方。

但是哈希表由于是基于数组的,所以扩容的成本大。

哈希地址

:抽象的参照地址,如数组下标。

eg:int arrHash[5]={0};映射关系为y=x(直接定址法)
指数字1存放在arrHash[1]位置上依次类推
数字1000要存在arrHash[1000]比较浪费内存

映射关系(哈希函数)

:可以简单理解为数学中的函数关系,当然也可以自己写一个函数作为映射
eg:

int getHash(char*str)
{
	if(strcmp(str,"aa")==0)
	{
		return 0;0代表aa的哈希地址
	}
	else if(strcmp(str,"ab")==0)
	{
		return 1;
	}
	.....
}

哈希表

:类似于查字典,通过拼音或者部首来在字典中找到对应的字
它会提供哈希里面用到的哈希函数

哈希冲突

eg:int arrHash[10]={0};映射关系为y=x%10
这样只会存放在0-9的位置上
但这样数据1和数据11都会存放在arrHash[1]导致数据冲突,这种现象称为哈希冲突

哈希冲突常见的处理方案

1.开放地址法:当遇到哈希冲突时,数据放在可以存放数据的位置

在这里插入图片描述
注意:这种情况下,哈希地址个数要大于元素个数,确保所有元素都可以储存

2.数组链表:当发生哈希冲突时,会以冲突位置为表头创造链表

在这里插入图片描述

链式哈希

数据存放在链式结构,而不是数组结构。这种链式结构通常是有序的

在这里插入图片描述

键(关键字)

当数据不满足数据结构需求时,要自己构造数据
eg:当我们储存的数据不是整形,例如字符串时,我们要封装一个key来为构建哈希地址做准备

struct pair
{
	int key;//键(关键字)
	char arr[10];//数据
};

2.例子模拟

这里储存的数据为学生的学号信息与名字
这里将学号作为键,来储存名字

因为每个学号对应一个姓名,所以当遇到相同的学号(键)时要覆盖原来的数据

当发生哈希冲突的时候采用开放地址法,找没有放数据的位置存放

哈希构造函数选用y=x%p,可知p为可以存放数据的多少

思路

学生数据pair为

typedef struct pair
{
	int key;//键值
	char name[10];//数据
}pair;

用数组实现,当数组对应位置为空的时候用NULL表示
所以这个数组里面要存放的是学生数据的地址

typedef struct HashTable
{
	int size;
	int capcity;//可存放数据的大小
	pair** Hash;//指向一个数组,数组存放的是pair*
}HashTable;

在创建hash时就要对其数组内容进行初始化

HashTable* CreatHash(int Cap)
{
	HashTable* NewHash = (HashTable*)malloc(sizeof(HashTable));
	NewHash->capcity = Cap;
	NewHash->size = 0;
	NewHash->Hash = (pair**)malloc(sizeof(pair*) * Cap);
	for (int i = 0; i < Cap; i++)
	{
		NewHash->Hash[i] = NULL;//初始化为空
	}
	return NewHash;
}

再者,插入数据时要找到对应的hash地址,所以先设计FinPos函数

输入hash指针和对应的键,返回对应位置的pos

这里有3种情况
1.对应位置是空的,或者对应位置已满,但还有空位
2.输入数据键相同,要做数据替换
3.表满了无法插入

//找hash地址
int FindPos(HashTable* dev, int keyD)
{
	int pos = keyD % (dev->capcity);
	int FinPos = pos;
	//当发生哈希冲突时找到没有存放数据的位置,当键相同时覆盖
	if (dev->Hash[pos] != NULL && dev->Hash[pos]->key == keyD)//有数据且键相同
	{
		return pos;
	}
	do
	{
		if (dev->Hash[FinPos] == NULL)//如果是空位置
		{
			return FinPos;
		}
		FinPos = (FinPos + 1) % (dev->capcity);//看下一个位置是否为空
	} while (FinPos != pos);//当FinPos==pos时说明循环一圈都没有空位,这时候返回pos到函数外判断
	return FinPos;
}

插入函数:

注意:返回了要插入的位置,这个位置开始是NULL,没有储存数据的空间,这里要先开辟一块pair空间在把数据拷贝过去

当键相同时,只要替换pair结构体中的name数组就行

//插入数据
void Insert(HashTable* dev, pair val)
{
	int pos = FindPos(dev, val.key);
	if (dev->Hash[pos] == NULL)//返回有空位时
	{
		//先申请内存,在用memcpy拷贝
		dev->Hash[pos] = (pair*)malloc(sizeof(pair));
		memcpy(dev->Hash[pos], &val, sizeof(pair));
		dev->size++;
	}
	else//返回没有空位时,可能是key重复,也可能是存满了
	{
		if (dev->Hash[pos]->key == val.key)//key重复,只要交换数组就行,不用memcpy
		{
			strcpy(dev->Hash[pos]->name, val.name);
		}
		else//存满了
		{
			printf("内存不足,无法插入\n");
		}
	}
}

打印hash

//打印hsah
void PrintHash(HashTable* dev)
{
	for (int i = 0; i < dev->capcity; i++)
	{
		if (dev->Hash[i] == NULL)
		{
			printf("NULL \n");
		}
		else
		{
			printf("学号%d 姓名%s\n",dev->Hash[i]->key,dev->Hash[i]->name);
		}
	}
}

3代码实现

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

typedef struct pair
{
	int key;//键值
	char name[10];//数据
}pair;

typedef struct HashTable
{
	int size;
	int capcity;//可存放数据的大小
	pair** Hash;//指向一个数组,数组存放的是pair*
}HashTable;

HashTable* CreatHash(int Cap)
{
	HashTable* NewHash = (HashTable*)malloc(sizeof(HashTable));
	NewHash->capcity = Cap;
	NewHash->size = 0;
	NewHash->Hash = (pair**)malloc(sizeof(pair*) * Cap);
	for (int i = 0; i < Cap; i++)
	{
		NewHash->Hash[i] = NULL;//初始化为空
	}
	return NewHash;
}

//找hash地址
int FindPos(HashTable* dev, int keyD)
{
	int pos = keyD % (dev->capcity);
	int FinPos = pos;
	//当发生哈希冲突时找到没有存放数据的位置,当键相同时覆盖
	if (dev->Hash[pos] != NULL && dev->Hash[pos]->key == keyD)//有数据且键相同
	{
		return pos;
	}
	do
	{
		if (dev->Hash[FinPos] == NULL)//如果是空位置
		{
			return FinPos;
		}
		FinPos = (FinPos + 1) % (dev->capcity);//看下一个位置是否为空
	} while (FinPos != pos);//当FinPos==pos时说明循环一圈都没有空位,这时候返回pos到函数外判断
	return FinPos;
}

//插入数据
void Insert(HashTable* dev, pair val)
{
	int pos = FindPos(dev, val.key);
	if (dev->Hash[pos] == NULL)//返回有空位时
	{
		//先申请内存,在用memcpy拷贝
		dev->Hash[pos] = (pair*)malloc(sizeof(pair));
		memcpy(dev->Hash[pos], &val, sizeof(pair));
		dev->size++;
	}
	else//返回没有空位时,可能是key重复,也可能是存满了
	{
		if (dev->Hash[pos]->key == val.key)//key重复,只要交换数组就行,不用memcpy
		{
			strcpy(dev->Hash[pos]->name, val.name);
		}
		else//存满了
		{
			printf("内存不足,无法插入\n");
		}
	}
}

//打印hsah
void PrintHash(HashTable* dev)
{
	for (int i = 0; i < dev->capcity; i++)
	{
		if (dev->Hash[i] == NULL)
		{
			printf("NULL \n");
		}
		else
		{
			printf("学号%d 姓名%s\n",dev->Hash[i]->key,dev->Hash[i]->name);
		}
	}
}

//测试
int main()
{
	HashTable* Class = CreatHash(5);
	pair Stu[4] = { {1,"小红"},{11,"小刚"},{3,"小明"},{4,"小丽"} };
	for (int i = 0; i < 4; i++)
	{
		Insert(Class, Stu[i]);
	}
	PrintHash(Class);
	printf("\n");
	pair Stu1 = { 21,"小华" };//正常插入
	Insert(Class, Stu1);
	PrintHash(Class);
	printf("\n");
	pair Stu2 = { 1,"丽丽" };//重复覆盖
	Insert(Class, Stu2);
	PrintHash(Class);
	printf("\n");
	pair Stu3 = { 31,"罗斯" };//验证已满
	Insert(Class, Stu3);
	PrintHash(Class);
	return 0;
}

在这里插入图片描述
如果对你有帮助的话还请点赞多多支持一下,一起加油学习吧

评论 10
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BaiRong-NUC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值