哈希函数之开散列----优化

把发生哈希冲突的元素放在同一个桶下面,成为哈希桶
每个链表下挂一个元素空间利用率最高
优化:
1、只能运用于整型:改变哈希函数中的内容:加函数指针或者将存放的数据类型转换为整型
2、增容:因为链表个数大于总容量,导致哈希表的性能下降
HashBucket.h
# pragma once
# include<stdio.h>
# include<assert.h>
# include<stdlib.h>
# include<malloc.h>
# include"Common.h"
typedef int DataType;
typedef struct HashNode
{
	struct HashNode *_pNext;
	DataType _data;//哈希表中存放的数据
}HashNode;
//给定函数指针,选择转换为整型的方法为默认方法还是先转换为整型
typedef size_t(*pElemToInt)(DataType data);//需用typedef给定类型,给定类型才能定义遍量
typedef struct HashBucket//哈希桶
{
	HashNode **_table;//二级指针,表示哈希表格,指向哈希桶的空间(桶中存放指向链表的指针)
	int _capacity;//容量
	int _size;//哈希表格中有效元素的个数
	pElemToInt _pElemToInt;//包含转换方法到哈希桶中
}HashBucket;
//初始化
void HashBucketInit(HashBucket *ht, int capacity, pElemToInt pElemToInt);
//元素不可以重复的插入
void HashBucketInsertUnique(HashBucket *ht, DataType data);
//元素可以重复的插入
void HashBucketInsertEqual(HashBucket *ht, DataType data);
//删除不可以重复的元素
void HashBucketDeleteUnique(HashBucket *ht, DataType data);
//删除可以重复的元素
void HashBucketDeleteEqual(HashBucket *ht, DataType data);
//查找
HashNode* HashBucketFind(HashBucket *ht, DataType data);
//哈系桶中有多少元素
int HashBucketSize(HashBucket *ht);
//哈希桶是不是空的
int HashBucketEmpty(HashBucket *ht);
//销毁哈希桶
void HashBucketDestory(HashBucket *ht);
//增容
int _CheckCapacity(HashBucket *ht);
void PrintHashBucket(HashBucket *ht);

HashBucket.c

# pragma once
typedef unsigned int size_t;//# include<stddef.h>
size_t GetNextPrime(size_t capacity);

//把元素转换为整型数据,默认方法(整型数据不需要转换,直接返回)
size_t ElemToIntDef(int data);
字符串转换为整型
size_t ElemToInt(const char * str);

# include"HashBucket.h"
# include"Common.h"
//哈希函数
int HashFunc(HashBucket *ht, DataType data)
{
	//首先保证data为整型数据,若不是整型,先转换为整型

	return ht->_pElemToInt(data)%ht->_capacity;
}
void HashBucketInit(HashBucket *ht, int capacity, pElemToInt pElemToInt)
{
	int i = 0;
	assert(ht);
	//获取下一个元素,防止下一个元素不为素数
	capacity = GetNextPrime(capacity);
	//开辟哈希桶的空间
	ht->_table = (HashNode**)malloc(sizeof(HashNode*)*capacity);
	if (NULL == ht->_table)//如果开辟空间失败,返回空
	{
		assert(0);
		return;
	}
	for (; i < capacity; ++i)//初始化空间的每一个位置为空
		ht->_table[i] = NULL;
	ht->_capacity = capacity;
	ht->_size = 0;
	ht->_pElemToInt = pElemToInt;
}
void HashBucketInsertUnique(HashBucket *ht, DataType data)
{
	HashNode *pNewNode = NULL;
	int bucketNo = -1;
	HashNode *pCur = NULL;
	if (0 == _CheckCapacity(ht))
		return;//增容失败,则不能插入
	//计算元素的桶号
	bucketNo = HashFunc(ht, data);
	//检测元素是否在当前桶中(元素须唯一),遍历一遍
    pCur = ht->_table[bucketNo];//从bucketNo位置开始遍历
	while (pCur)
	{
		if (data == pCur->_data)//若果存在则返回
			return;
		pCur = pCur->_pNext;//不存在,则继续往下走
	}
	//创建新节点,在哈希桶下的链表中插入元素
	pNewNode = BuyHashNode(data);
	//头插法
	pNewNode->_pNext = ht->_table[bucketNo];
	ht->_table[bucketNo] = pNewNode;
	ht->_size++;
}
HashNode *BuyHashNode(DataType data)
{
	HashNode *pNewNode = (HashNode*)malloc(sizeof(HashNode));
	if (NULL == pNewNode)//新空间申请失败
	{
		assert(0);
		return NULL;
	}
	//节点创建成功
	pNewNode->_data = data;
	pNewNode->_pNext = NULL;
	return pNewNode;
}
void HashBucketDeleteUnique(HashBucket *ht, DataType data)
{
	 
 	//计算元素的桶号
	int bucketNo = HashFunc(ht, data);
	//检测元素是否在当前桶中
	HashNode *pPre = NULL;
	HashNode *pCur = ht->_table[bucketNo];//找到了桶的位置
	while (pCur)
	{
		if (data == pCur->_data)//等于此位置
		{
			//删除链表中的第一个节点
			if (pCur == ht->_table[bucketNo])//cur是链表中的第一个节点,
			{
				ht->_table[bucketNo] = pCur->_pNext;//哈希桶里直接放cur的下一个节点
			}
			//不是链表中的第一个节点
			else
			{
				pPre->_pNext = pCur->_pNext;//前一个节点指向当前节点的下一个节点
			}
			free(pCur);
			ht->_size--;
			return;
		}
		pPre = pCur;//如果不是pPre记录下pCur
		pCur = pCur->_pNext;//cur继续向后走
	}
}
void HashBucketInsertEqual(HashBucket *ht, DataType data)
{
	int bucketNo = -1;
	HashNode *pNewNode = NULL;
	if (0 == _CheckCapacity(ht))
		return;//增容失败,则不能插入
	bucketNo = HashFunc(ht, data);//计算桶号,通过哈希函数计算
	//可以重复,不用检测链表中是否已经存在此元素,直接向链表中插入此元素即可
	pNewNode = BuyHashNode(data);//给定新节点
	//头插
	pNewNode->_pNext = ht->_table[bucketNo];//新节点连接原节点,原节点为桶位置
	ht->_table[bucketNo] = pNewNode;//此时桶的位置为新节点的位置
	ht->_size++;
}
void HashBucketDeleteEqual(HashBucket *ht, DataType data)
{
	//在当前哈系桶中查找桶位置
	int bucketNo = HashFunc(ht, data);
	HashNode *pPre = NULL;
	HashNode *pCur = ht->_table[bucketNo];
	while (pCur)
	{
		if (data == pCur->_data)
		{
			if (pCur == ht->_table[bucketNo])//当前要删除元素元素是桶位置
			{
				ht->_table[bucketNo] = pCur->_pNext;
				free(pCur);
				pCur = ht->_table[bucketNo];//继续朝后走,后面有可能还有要删除位置
			}
			else//是要删除的节点,且在链表处,不在桶位置
			{
				pPre->_pNext = pCur->_pNext;
				free(pCur);
				pCur = pPre->_pNext;
			}
		}
		else//不相等
		{
			pPre = pCur;
			pCur = pCur->_pNext;
		}
	}
}
int HashBucketSize(HashBucket *ht)
{
	return ht->_size;
}
int HashBucketEmpty(HashBucket *ht)
{
	return 0 == ht->_size;
}
void HashBucketDestory(HashBucket *ht)
{
	int i = 0;
	for (; i < ht->_capacity; ++i)//i表示要删除的桶
	{
		HashNode *pCur = ht->_table[i];
		while (pCur)//只要桶里面有节点
		{
			//删除链表中第一个节点的位置
			ht->_table[i] = pCur->_pNext;
			free(pCur);
			pCur = ht->_table[i];//把当前节点放到链表中第一个节点的位置
		}
	}
	free(ht->_table);
	ht->_capacity = 0;
	ht->_size = 0;
}
int _CheckCapacity(HashBucket *ht)
{
	int i = 0;
 	assert(ht);
	if (ht->_capacity == ht->_size)
	{
		//法一:给一个新的哈希表,将旧哈希表中的内容一个个的插入,然后交换两个哈希表中的内容,后释放新哈希表
		//法二:直接搬入插入方法中的内容将旧哈希表中的内容拿出来,一个个插入,不创建新的节点
		//选用法二
		//选插入方式为插入重复,因为InsertEqual包含了InsertUnique,InsertUnique相当于InsertUnique要插入的元素只有一个,没有相同的
		//给定新空间的大小
		size_t newCapacity = GetNextPrime(ht->_capacity);
		HashNode **pNewTable = (HashNode*)malloc(GetNextPrime(ht->_capacity)*sizeof(HashNode*));
		//判断新空间的大小给的是否成功
		if (NULL == pNewTable)
		{
			assert(0);
			return 0;
		}
		//初始化新空间
		for (i = 0; i < newCapacity; ++i)
			pNewTable[i] = NULL;
		//搬移元素
		//for控制要搬移的链表
		for (i=0; i < ht->_capacity; ++i)
		{
			//pCur表示处理i位置处的链表
			HashNode *pCur = ht->_table[i];
			int bucketNo = -1; 
			while (pCur)//若当前节点存在,则插入新链表
			{
				//取旧哈希桶i号桶的第一个节点
				ht->_table[i] = pCur->_pNext;//头删
				//计算当前节点在新空间的桶号
				bucketNo = ht->_pElemToInt(pCur->_data)%newCapacity;//计算在那个哈希桶中
				//头插法将该节点插入新空间
				pCur->_pNext = pNewTable[bucketNo];//连接新桶中的内容
				pNewTable[bucketNo] = pCur;//将给定的第一个节点放入新空间
				//取旧哈希桶i号桶的第next个节点
				pCur = ht->_table[i];//cur取原链表中的下一个节点
			}
		}
		free(ht->_table);//释放旧空间
		ht->_table = pNewTable;//将空间设置为新空间的内容
		ht->_capacity = newCapacity;
 	}
	return 1;
}
void PrintHashBucket(HashBucket *ht)
{
	int i = 0;
	for (; i < ht->_capacity; ++i)//i表示要删除的桶
	{
		HashNode *pCur = ht->_table[i];
		printf("[%d]:", i);
		while (pCur)//只要桶里面有节点
		{ 
			printf("%d->", pCur->_data);
			pCur = pCur->_pNext;
		}
		printf("NULL\n");
	}
}

common.h

# pragma once
typedef unsigned int size_t;//# include<stddef.h>
size_t GetNextPrime(size_t capacity);

//把元素转换为整型数据,默认方法(整型数据不需要转换,直接返回)
size_t ElemToIntDef(int data);
字符串转换为整型
size_t ElemToInt(const char * str);

common.c

//闭散列用的少,因为浪费空间
# define _CRT_SECURE_NO_WARNINGS 1
# include"Common.h"
#define _PrimeSize 28//enum{_PrimeSize =28};

// 使用素数表对齐做哈希表的容量,降低哈希冲突
const unsigned long _PrimeList[_PrimeSize]=
{//可将long换为long long获取更大的素数
	53ul, 97ul, 193ul, 389ul, 769ul,
	1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
	49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
	1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
	50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457u,
	1610612711ul, 3221225473ul, 4294967291ul
};
//获取比容量大的第一个素数
size_t GetNextPrime(size_t capacity)
{
	int i = 0;
	for (; i < _PrimeSize; ++i)
	{
		if (_PrimeList[i]>capacity)//容量小于素数,直接返回该素数
			return _PrimeList[i];
	}
	return _PrimeList[_PrimeSize - 1];//容量太大了,返回最后一个素数
}
size_t ElemToIntDef(int data)
{
	return data;
}
unsigned int StrToInt(const char * str)
{
	unsigned int seed = 131; // 31 131 1313 13131 131313
	unsigned int hash = 0;
	while (*str)
	{
		hash = hash * seed + (*str++);
	}
return (hash & 0x7FFFFFFF);}

test.c
void test()
{
	HashBucket ht;
	HashBucketInit(&ht, 10, ElemToIntDef);
	HashBucketInsertUnique(&ht, 1);
	HashBucketInsertUnique(&ht, 2);
	HashBucketInsertUnique(&ht, 3);
	HashBucketInsertUnique(&ht, 4);
	HashBucketInsertUnique(&ht, 5);
	HashBucketInsertUnique(&ht, 6);
	PrintHashBucket(&ht);
	HashBucketInsertUnique(&ht, 7);
}
int main()
{
	test();
	system("pause");
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

xuruhua

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

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

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

打赏作者

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

抵扣说明:

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

余额充值