把发生哈希冲突的元素放在同一个桶下面,成为哈希桶
每个链表下挂一个元素空间利用率最高
优化:
1、只能运用于整型:改变哈希函数中的内容:加函数指针或者将存放的数据类型转换为整型
2、增容:因为链表个数大于总容量,导致哈希表的性能下降
每个链表下挂一个元素空间利用率最高
优化:
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;
}