数据结构--搜索结构之哈希二( 开散列 ) ( c语言版 )

开散列: 开散列法又叫链地址法(开链法) , 哈希桶

  • 开散列法:首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。

设元素的关键码为37, 25, 14, 36, 49, 68, 57, 11, 散列表为HT[11],表的大小为11,散列函数为Hash(x) = x % 11

Hash(37)=4  

Hash(25)=3  

Hash(14)=3  

Hash(36)=3  

Hash(49)=5  

Hash(68)=2  

Hash(57)=2  

Hash(11)=0
使用哈希函数计算出每个元素所在的桶号,同一个桶的链表中存放哈希冲突的元素。

如下图所示 : 

  • 通常, 每个桶对应的链表结点都很少,将n个关键码通过某一个散列函数,存放到散列表中的m个桶中,那么每一个桶中链表的平均长度为 n / m。以搜索平均长度为 n / m 的链表代替了搜索长度为 n 的顺序表,搜索效率快的多。
  • 应用链地址法处理溢出,需要增设链接指针,似乎增加了存储开销。事实上:由于开地址法必须保持大量的空闲空间以确保搜索效率,如二次探查法要求装载因子a <= 0.7,而表项所占空间又比指针大的多, 所以使用链地址法反而比开地址法节省存储空间。
     

代码如下 : 

HashTableBucket . h

#pragma once
#include <stdio.h>
#include <assert.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>

typedef int HTBKeyType;
typedef int HTBValueType;

//每一个关键码对应位置的数据结构
typedef struct HashNode
{
	struct HashNode* _next;
	HTBKeyType _key;
	HTBValueType _value;
}HashNode;
//哈希桶的数据结构
typedef struct HashTableBucket
{
	HashNode** _tables;
	size_t _size;
	size_t _len;
}HTB;
//哈希桶初始化
void HTBInit(HTB* htb, size_t len);
//哈希桶销毁
void HTBDestory(HTB* htb);
//哈希桶插入
int HTBInsert(HTB* htb, HTBKeyType key, HTBValueType value);
//哈希桶删除
int HTBRemove(HTB* htb, HTBKeyType key);
//查找
HashNode* HTBFind(HTB* htb, HTBKeyType key);
//哈希桶大小
int HTBSize(HTB* htb);
//判断桶是否为空
int HTBEmpty(HTB* htb);
//测试
void TestHashTableBucket();

HashTableBucket . c

#include"HashTableBucket.h"
//计算容量
int ExpendCapacity(size_t len)
{
	size_t i = 0;
	//使用素数表对齐做哈希表的容量,降低哈希冲突      
	const int _PrimeSize = 28;
	static const unsigned long _PrimeList[28] = {
		53ul, 97ul, 193ul, 389ul, 769ul, 1543ul, 3079ul, 6151ul,
		12289ul, 24593ul, 49157ul, 98317ul, 196613ul, 393241ul,
		786433ul, 1572869ul, 3145739ul, 6291469ul, 12582917ul,
		25165843ul, 50331653ul, 100663319ul, 201326611ul, 402653189ul,
		805306457ul, 1610612741ul, 3221225473ul, 4294967291ul };
	for (; i < 28; i++)
	{
		if (_PrimeList[i] > len)
			return _PrimeList[i];
	}
	return _PrimeList[28 - 1];
}
//计算关键码所在的位置,除留余数法
int HashFunc(HTBKeyType key, size_t len)
{
	return key%len;
}
//检查是否需要扩容
//在哈希桶中扩容的条件为当它的存储的数据大小等于哈希桶的长度时,证明需要扩容
int ChackCapacity(HTB* htb)
{
	if (htb->_size == htb->_len)
		return 1;
	else
		return 0;
}
//哈希桶初始化
void HTBInit(HTB* htb, size_t len)
{
	assert(htb);
	htb->_tables = (HashNode**)malloc(sizeof(HashNode*)*ExpendCapacity(len));
	htb->_len = ExpendCapacity(len);
	htb->_size = 0;
	//将所有数据置空
	memset(htb->_tables, 0, sizeof(HashNode*)*ExpendCapacity(len));
}
//哈希桶销毁
//注意:这里使用的是二级指针,不能直接将htb->tables直接销毁,这就相当于
//将单链表的头结点删除而它的后继节点并没有被删除一样,所以这里应该先
//将哈希桶中每个单链表的所有节点释放,然后再将htb->tables销毁
void HTBDestory(HTB* htb)
{
	size_t i = 0;
	HashNode *cur;
	for (i = 0; i < htb->_len; i++)
	{
		cur = htb->_tables[i];
		//将链表中所有节点释放
		while (cur)
		{
			HashNode *tmp = cur->_next;
			free(cur);
			cur = tmp;
		}
		htb->_tables[i] = NULL;
	}
	htb->_tables = NULL;
	htb->_len = htb->_size = 0;
}
//申请节点
HashNode *BuyHashNoed(HTBKeyType key,HTBValueType value)
{
	HashNode * cur =(HashNode*)malloc(sizeof(HashNode));
	cur->_key = key;
	cur->_value = value;
	cur->_next = NULL;
	return cur;
}
//插入
//这里插入节点时,使用的是头插法,这样可以避免去遍历整个单链表
//注意:这里当检查到需要扩容时,就建一个新的哈希桶,将旧的哈希桶所有的有
//效节点以头插的方法搬迁到新哈希桶中,然后再将新哈希桶的所有信息重新给
//到旧哈希桶中这样就实现了扩容
int HTBInsert(HTB* htb, HTBKeyType key, HTBValueType value)
{
	size_t index;
	HashNode* newNode = NULL;
	assert(htb);
	//检查是否需要扩容
	while (ChackCapacity(htb))
	{
		HTB newhtb;
		HashNode* ptr;
		size_t i = 0;
		HTBInit(&newhtb, htb->_len);
		for (; i < htb->_len; i++)
		{
			ptr = htb->_tables[i];
			//将该关键码所在的单链表所有节点重新插入到新哈希桶中
			while (ptr)
			{
				HashNode* cur = ptr;
				ptr = ptr->_next;
				cur->_next = newhtb._tables[HashFunc(cur->_key, newhtb._len)];
				newhtb._tables[HashFunc(cur->_key, newhtb._len)] = cur;
			}
		}
		free(htb->_tables);
		htb->_tables = newhtb._tables;
		htb->_len = newhtb._len;
	}
	index = HashFunc(key, htb->_len);
	newNode = BuyHashNoed(key, value);
	//采用头插法插入节点信息
	newNode->_next = htb->_tables[index];
	htb->_tables[index] = newNode;
	htb->_size++;
	return 1;
}
//删除
//使用一个prv指针记录要删除节点的前置节点
int HTBRemove(HTB* htb, HTBKeyType key)
{
	size_t index;
	HashNode *cur = NULL, *prv = NULL;
	assert(htb); 
	index = HashFunc(key, htb->_len);
	cur = htb->_tables[index];
	while (cur)
	{
		//删除的节点为头结点
		if (cur->_key == key&&prv == NULL)
		{
			htb->_tables[index] = cur->_next;
			free(cur);
			cur = NULL;
			htb->_size--;
			return 1;
		}
		//删除的节点为非头结点
		if (cur->_key == key)
		{
			prv->_next = cur->_next;
			free(cur);
			cur = NULL;
			return 1;
		}
		prv = cur;
		cur = prv->_next;
	}
	return -1;
}
//查找
HashNode* HTBFind(HTB* htb, HTBKeyType key)
{
	size_t index;
	HashNode* cur = NULL;
	assert(htb);
	index = HashFunc(key, htb->_len);
	cur = htb->_tables[index];
	while (cur)
	{
		if (cur->_key == key)
			return cur;
		cur = cur->_next;
	}
	return NULL;
}
//哈希桶数据的个数
int HTBSize(HTB* htb)
{
	assert(htb);
	return htb->_size;
}
//判断哈希桶是否为空
int HTBEmpty(HTB* htb)
{
	assert(htb);
	return htb->_size > 0 ? 1 : 0;
}
//打印哈希桶
void HashPrint(HTB* htb)
{
	int count = 0;
	HashNode* cur = NULL;
	size_t i;
	assert(htb);
	for (i = 0; i < htb->_len; i++)
	{
		cur = htb->_tables[i];
		count = 0;
		while (cur)
		{
		
			printf("[%d]->", cur->_key);
			cur = cur->_next;
		}
		printf("NULL\n");
	}
	printf("\n");
}
//测试
void TestHashTableBucket()
{
	size_t i;
	HTB htb;
	HTBInit(&htb, 5);
	srand(time(0));
	for (i = 0; i < 50; i++)
		HTBInsert(&htb, rand(), 0);
	HashPrint(&htb);
	HTBDestory(&htb);
}

Test . c

#include "HashTableBucket.h"
int main()
{
	TestHashTableBucket();
	system("pause");
	return 0;
}

调试结果如下 :

若有出错或不懂的地方,​ 欢迎留言, 共同进步 !

猜你喜欢

转载自blog.csdn.net/ds19980228/article/details/82383089
今日推荐