08_Radis-HashTable

本文详细介绍了简单哈希表的定义及其存在的冲突问题,通过线性探测和链式哈希(包括哈希函数改进、头插法插入、删除操作和扩容策略)来解决冲突,并对比了与Map的区别。讨论了链式哈希在避免数组重复赋值的应用实例。
摘要由CSDN通过智能技术生成

无论C++还是Java,都会提供哈希表,但是不同的语言实现不一样;
哈希表是无序的:插入数据并不进行排序(HashMap)

Map:是有序的,Map的底层是按照红黑数实现的,当用户把关键码插入进去,就会自动进行排序。因此Map的效率就没有HashMap高

一:简单哈希

(一):简单哈希表的定义:

typedef int KeyType;

struct ElemType
{
	KeyType key;//关键码
	void* ptr;//存放关键码所指向的数据的地址
};

typedef struct
{
	ElemType data[m];
	int cursize;//当前元素的个数
}HashTable;
HashTable ht;

哈希表的初始化:
在这里插入图片描述

在这里插入图片描述
等价于:
在这里插入图片描述

(二):简单哈希表存在的问题

问题:

  • 哈希冲突
  • 数据不连续
    在这里插入图片描述

(三):解决问题的方法

线性探测

int Inc(int i)
{
	return i;//线性探测
	//return i * i;//平方探测
}

int Hash(KeyType kx)
{
	return kx % m;
}

int Hash_Inc(KeyType kx, int i)
{
	return (Hash(kx) + Inc(i)) % m;
}

在这里插入图片描述

(四):哈希查询

void* FindValue(HashTable* pt, KeyType kx)
{
	assert(pt != nullptr);
	for (int i = 0;i < m;++i)
	{
		int pos = Hash_Inc(kx, i);
		if (pt->data[pos].key == kx)
		{
			return pt->data[pos].ptr;
		}
		if (pt->data[pos].key == NIL)
		{
			return nullptr;
		}
	}
	return nullptr;
}

在这里插入图片描述

二:链式哈希解决哈希冲突

(一):链式哈希的定义

在这里插入图片描述

//链式哈希
#define m 13 //顺序表的大小
typedef int KeyType;

typedef struct 
{
	KeyType key;//关键码
	void* ptr;//存放关键码所指向的数据的地址
}ElemType;

typedef struct
{
	ElemType data;
	struct HashNode* next;
}HashNode;

typedef struct
{
	HashNode* table[m];
	int cursize;
}HashTable;

(二):哈希函数

typedef int KeyType;
int Hash(KeyType kx)
{
	return kx % m;
}

此次关键码kx是整型。若关键码为字符串呢?哈希函数该如何改进?

typedef char KeyType[20];

方法一:将字符串ASCLL码值相加取模;
但是对于“abc” “cba" 关键码一定不同,但是ASCLL相加却相同。所以不能简单的将ASCLL码相加,需要加工

(三):链式插入(头插)

在这里插入图片描述

//头插 时间复杂度O(1)
void Insert_Item(HashTable* pt, ElemType item)//没有去重,关键码相同依然会被插入
{
	assert(pt != nullptr);
	int index = Hash(item.key);
	HashNode* s = (HashNode*)malloc(sizeof(HashNode));
	if (nullptr == s)
	{
		exit(EXIT_FAILURE);
	}
	s->data = item;
	s->next = pt->table[index];
	pt->table[index] = s;
	pt->cursize += 1;
}

HashNode* FindValue(HashTable* pt, KeyType kx)
{
	int index = Hash(kx);
	HashNode* p = pt->table[index];
	while (p != nullptr && p->data.key != kx)
	{
		p = p->next;
	}
	return p;
}

void Insert_Item_New(HashTable* pt, ElemType item)//去重插入
{
	assert(pt != nullptr);
	int index = Hash(item.key);
	HashNode* p = FindValue(pt,item.key);
	if (p != nullptr) return ;
	HashNode* s = (HashNode*)malloc(sizeof(HashNode));
	if (nullptr == s)
	{		
		exit(EXIT_FAILURE);
	}
	s->data = item;
	s->next = pt->table[index];
	pt->table[index] = s;
	pt->cursize += 1;
}

(四):链式删除节点

bool Remove(HashTable* pt, KeyType kx)//删除节点
{
	assert(pt != nullptr);
	int index = Hash(kx);
	HashNode* pr = nullptr;//p的前驱节点
	HashNode* p = pt->table[index];
	while (p != nullptr)
	{
		if (p->data.key == kx)
		{
			if (pr != nullptr)
			{
				pr->next = p->next;
			}
			else//pr为空删除第一个节点
			{
				pt->table[index] = p->next;
			}
			free(p);
			pt->cursize -= 1;
			return true;
		}
		pr = p;
		p = p->next;
	}
	return false;
}

(五):链式清除

void ClearHash(HashTable* pt)//释放节点,哈希表置空
{
	assert(pt != nullptr);
	for (int i = 0;i < m;++i)
	{
		while (pt->table[i] != nullptr)
		{
			HashNode* q = pt->table[i];
			pt->table[i] = q->next;
			free(q);
		}
	}
	pt->cursize = 0;
}

三:链式哈希解决数组重复赋值问题

问题1:
定义大小为100的整形数组,使用随机函数给数组元素赋值,
赋值范围1-100,并且不能重复

查表法:
在这里插入图片描述
问题2:
定义大小为100的整形数组,使用随机函数给数组元素赋值,
赋值范围1-10000,并且不能重复

若使用查表法,开辟空间较大,浪费严重效率底,所以使用链式哈希

#include<stdio.h>
#include<stdlib.h>
#include"HashTable.h"

void Print_Ar(const int* br, int n)
{
	if (br == nullptr || n < 1) return;
	for (int i = 0;i < n;++i)
	{
		printf("%10d", br[i]);
		if ((i + 1) % 10 == 0)
		{
			printf("\n");
		}
	}
	printf("\n");
}

int main()
{
	int ar[100];
	HashTable ht;
	Init_Hash(&ht);
	int i = 0;
	while (i < 100)
	{
		int tmp = rand() % 100000 + 1;//1.....100
		ElemType item = { tmp,nullptr };
		if (Insert_Item_New(&ht, item))
		{
			ar[i] = tmp;
			++i;
		}

	}
	
	Print_Ar(ar, 100);
	

	return 0;
}

四:哈希表的扩充(区别字典的步进式增容)

随着数据量的不断增多,链接的节点越来越多,链越来越长。最终的特点是哈希表退化:哈希表大小m=13,但是每一个链都有10000多个数据节点,查找数据时哈希到某个下标的时间复杂度虽然为O(1),但是节点数据查询时间复杂度由O(1)变成O(n)

慢的根源是链太长,因此就给出了哈希表扩容方案

结构定义:

//链式哈希
#define m 13 //顺序表的大小
#define INC 2 //哈希表增量为原来的2倍
typedef int KeyType;
//typedef char KeyType[20];

typedef struct
{
	KeyType key;//关键码
	void* ptr;//存放关键码所指向的数据的地址
}ElemType;

typedef struct HashNode
{
	ElemType data;
	HashNode* next;
}HashNode;

typedef struct
{
	HashNode** table;
	int capacity;//哈希表容量
	int total;//节点总个数
}HashTable;

初始化:

void Init_Hash(HashTable* pt)
{
	assert(pt != nullptr);
	pt->table = (HashNode**)malloc(sizeof(HashNode*) * m);
	if (pt->table == nullptr) exit(EXIT_FAILURE);
	pt->capacity = m;
	for (int i = 0;i < pt->capacity;++i)
	{
		pt->table[i] = nullptr;
	}
	pt->total = 0;
}

一步扩容

bool Inc(HashTable* pt)
{
	int incsize = pt->capacity * INC;
	int oldsize = pt->capacity;
	HashNode** newdata = (HashNode**)malloc(sizeof(HashNode*) * incsize);
	if (newdata == nullptr)return false;
	for (int i = 0; i < incsize; ++i)
	{
		newdata[i] = nullptr;//newdata执行一块空间,空间里每个元素都是指针类型
	}
	pt->capacity = incsize;
	for (int i = 0; i < oldsize; ++i)//旧表的内容一个个扫描并且哈希到新表中
	{
		while (pt->table[i] != nullptr)//后面链接有节点,需要取出来重新计算关键码放到新表中
		{
			HashNode* s = pt->table[i];
			pt->table[i] = s->next;
			int newindex = Hash(pt, s->data.key);
			s->next = newdata[newindex];
			newdata[newindex] = s;
		}
	}
	free(pt->table);
	pt->table = newdata;
	return true;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值