哈希表详解及应对哈希冲突(闭、开散列)

哈希搜索结构

        顺序搜索以及二叉搜索树中,元素存储位置和元素各关键码之间没有对应的关系,因此在查找一个元素的时候,必须要经过关键码的多次比较。所以这样的搜索效率取决于搜索过程中元素的比较次数。

        而我们理想的搜索方法就是不用经过任何比较,一次直接从表中得到要搜索的元素。如果能够构造一种结构,让所要查找的关键码与它的存储位置有一定的关系,那么可以根据这种关系找到其位置从而更快的找到关键码。

        在这种结构中插入时,根据待插入的元素从而经过某些函数计算出它的插入位置,进行插入;查找时通过对查找元素的计算出其位置,再根据位置在结构中的对应出进行比较,如果相等则查找成功。 
        这种方式的结构就是哈希搜索结构,其中使用的计算位置函数就是哈希函数。

哈希函数

                     Hash(key) = key%m(m为内存单元的个数)

代码形式:

int HashFun(int key)
{
    return key % m;//关键码为key,其存储对应位置为key模m
}

哈希冲突

       当不同关键字通过相同哈希函数计算出相同的哈希地址时,这种现象称为哈希冲突或哈希碰撞,把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

       例如:13%10 = 3,23%10 = 3,两个不同的关键字通过计算得到相同的哈希地址,而同一内存单元内只能放入一个关键字,此时即哈希冲突。

        引起哈希冲突的一个原因可能是:哈希函数设计不够合理。

哈希函数

         常见的哈希函数:

         直接定址法、除留余数法、平方取中法、折叠法、随机数法、数学分析法等。

         注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突。

负载因子

        为了增加哈希表的搜索效率,尽量不让哈希表插满,而是规定一个负载因子,控制哈希表内插入元素的个数,从而提高效率。 
         负载因子的定义为:α = 填入表中元素 / 可填入总元素 
         α 越大,表明填入表内元素越多,产生哈希冲突的可能就越大。相反则越小。

处理哈希冲突

         解决哈希冲突时要思考哈希函数设计是否合理,考虑重新设计哈希函数。(哈希表中不能插入相同元素)

         解决哈希冲突的两种常见方法: 

          ①闭散列法(开放地址法):当哈希表发生冲突时,如果哈希表未被装满,则可以把key存放到哈希表中下一个空位处。

          ②开散列法(链地址法):首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。使用链地址法比开地址法节省存储空间。   (原因:开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间)

闭散列

         寻找下一个空余位置时有两种方法:

         ①线性探测:从发生冲突的位置开始,依次继续向后探测,直到有空位置。       

             优点:简单,在O(N)中可以找到空位置。

             缺点:所有冲突连在一起,容易产生数据堆积,导致搜索效率降低。

         ②二次探测:发生哈希冲突时,二次探测寻找下一个空位置的公式为:H(i)=(H(0)+i^2)^2%m 

             优点:解决了线性探测中数据堆积的问题。

             缺点:若空位置少,找空位置可能需要大量时间,空间利用率低。

闭散列哈希表的基本操作代码如下:

//hashTable.h

#include<stdlib.h>
#include<string.h>
#include<Windows.h>
#define MAXSIZE 10
typedef int DataType;

typedef enum
{
	EMPTY,
	EXIST,
	DELETED
}State;
typedef struct HTElem
{
	DataType _data;
	State _state;
}HTElem;
typedef struct HashTable
{
	HTElem _ht[MAXSIZE];
	int _size;
}HashTable;


//hashTable.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "hashTable.h"

int HashFunc(DataType data)
{
	return data % MAXSIZE;
}
void PrintHashTable(HashTable* pHT)
{
	int i = 0;
	for (; i < MAXSIZE; i++)
	{
		if (pHT->_ht[i]._state == EXIST)
			printf("%d ", pHT->_ht[i]._data);
	}
}
void InitHashTable(HashTable* pHT)
{
	assert(pHT);
	int i = 0;
	for (; i < MAXSIZE; i++)
	{
		pHT->_ht[i]._state = EMPTY;
	}
	pHT->_size = 0;
}
int InsertHashTable(HashTable* pHT, DataType data)
{
	int HashAddr;
	assert(pHT);
	HashAddr = HashFunc(data);   //1.计算哈希地址
	while(EMPTY != pHT->_ht[HashAddr]._state) //2.找存储位置
	{
		if ((EXIST == pHT->_ht[HashAddr]._state) &&( data == pHT->_ht[HashAddr]._data)) //已经插入此数
			return 0;
		HashAddr++;
		HashAddr %= MAXSIZE;
	}
	if (pHT->_size == MAXSIZE)
	{
		printf("哈希表已满\n");
		return;
	}
	pHT->_ht[HashAddr]._data = data;
	pHT->_ht[HashAddr]._state = EXIST;
	pHT->_size++;
	return 1;
}
int FindHashTable(HashTable* pHT, DataType data)
{
	int HashAddr;
	assert(pHT);
	HashAddr = HashFunc(data);
	while (EMPTY != pHT->_ht[HashAddr]._state)
	{
		if ((EXIST == pHT->_ht[HashAddr]._state) && (data == pHT->_ht[HashAddr]._data))
			return HashAddr;
		HashAddr++;
		if (HashAddr == MAXSIZE)
			HashAddr = 0;
	}
	return -1;

}
int DeleteHashTable(HashTable* pHT, DataType data)
{
	int HashAddr = FindHashTable(pHT, data);
	if (-1 != HashAddr)
	{
		pHT->_ht[HashAddr]._state = DELETED;
		pHT->_size--;
		return 1;
	}
	return 0;
}
int SizeHashTable(HashTable* pHT)
{
	assert(pHT);
	return pHT->_size;
}
int EmptyHashTable(HashTable* pHT)
{
	assert(pHT);
	return 0 == pHT->_size;
}
void DestroyHashTable(HashTable* pHT)
{
	asserrt(pHT);
	free(pHT->_ht);
	pHT->_size = 0;
}


//test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "hashTable.h"
void test()
{
	HashTable ht;
	InitHashTable(&ht);
	InsertHashTable(&ht, 1);
	InsertHashTable(&ht, 2);
	InsertHashTable(&ht, 5);
	InsertHashTable(&ht, 4);
	InsertHashTable(&ht, 3);
	InsertHashTable(&ht, 6);
	InsertHashTable(&ht, 8);
	InsertHashTable(&ht, 7);
	InsertHashTable(&ht, 9);
	
	int i = FindHashTable(&ht, 9);
	if (i != -1)
		printf("找到了,元素所在哈希表的位置为:%d\n", i);
	else
		printf("找不到\n");

	int j = DeleteHashTable(&ht, 3);
	if (j == 1)
		printf("删除成功!\n");
	else
		printf("删除失败\n");
	PrintHashTable(&ht);
  DestroyHashTable(&ht);
}
int main()
{
	test();
	system("pause");
	return 0;
}

开散列        

       缺陷:一些链表较长

       解决方式:重新散列----->扩容

开散列哈希表的基本操作代码如下(包含使用素数表进行扩容处理):

//HashBucket.h

#pragma once
#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<Windows.h>
typedef struct HashBucketNode
{
	struct HashBucketNode* _pNext;
	DataType _data;
}HashBucketNode;
typedef struct HashBucket
{
	HashBucketNode** _table;
	int _capacity;
	int size;
}HashBucket;


void PrintHashBucket(HashBucket* pHB);//打印
void InitHashBucket(HashBucket* pHB, int capacity);//初始化
int FindHashBucket(HashBucket* pHB, DataType data);//查找
int InsertHashBucket(HashBucket* pHB, DataType data);//插入
int DeleteHashBucket(HashBucket* pHB, DataType data);//删除
void CheckCapacity(HashBucket* pHB); //是否扩容
void DestroyHashBucket(HashBucket* pHB);//销毁
int EmptyHashBucket(HashBucket* pHB);
int SizeHashBucket(HashBucket* pHB);

//HashBucket.c

HashBucketNode* BuyHashBucketNode(DataType data)
{
	HashBucketNode* pNewNode = (HashBucketNode*)malloc(sizeof(HashBucketNode));
	if (NULL == pNewNode)
	{
		assert(0);
		return 0;
	}
	pNewNode->_data = data;
	pNewNode->_pNext = NULL;
	return pNewNode;
}
size_t GetNextPrime(size_t capacity)//获取素数N,进行扩容时用到的
{
	int _PrimeSize = 28;
	unsigned int _PrimeList[_PrimeSize] =
	{//素数表
		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 (size_t i = 0; i < _PrimeSize; ++i)
	{
		if (_PrimeList[i] > capacity)
		{
			return _PrimeList[i];
		}

	}
	return _PrimeList[_PrimeSize - 1];
}
void CheckCapacity(HashBucket* pHB)
{
	assert(pHB);
	int i = 0;
	HashBucketNode* cur = NULL;
	int HashAddr = 0;
	if (pHB->size == pHB->_capacity)
	{
		int NewCapacity = GetNextPrime(pHB->_capacity);
		HashBucketNode** pNewTable = (HashBucketNode**)malloc(NewCapacity * sizeof(HashBucketNode*));  //开辟新空间
		if (NULL == pNewTable)
		{
			assert(0);
			return;
		}  
		memset(pNewTable, 0, sizeof(HashBucketNode*)*NewCapacity);
        int OldCapacity = pHB->capacity;
        pHB->_capacity = NewCapacity;
		for (; i < OldCapacity; i++)               //拷贝元素
		{
			cur = pHB->_table[i];
			while (cur)
			{
				HashAddr = HashBucketFunc(cur->_data, pHB);   //计算新的哈希地址(将i号桶的所有节点重新哈希)
				pHB->_table[i] = cur->_pNext;    //将cur从原链表中删除
				cur->_pNext = pNewTable[HashAddr];//将cur插入新链表
				pNewTable[HashAddr] = cur;
				cur = pHB->_table[i];   //取原链表下一个节点
			}
		}
		free(pHB->_table);
		pHB->_table = pNewTable;
	}

}
void PrintHashBucket(HashBucket* pHB)
{
	assert(pHB);
	int i = 0;
	for (; i < pHB->_capacity; i++)
	{
		printf("%d号桶:", i);
		HashBucketNode* cur = pHB->_table[i];
		while (cur)
		{
			printf("%d->", cur->_data);
			cur = cur->_pNext;
		}
		if (NULL == cur)
			printf("NULL\n");
	}

}
int HashBucketFunc(DataType data, HashBucket* pHB)
{
	return data % pHB->_capacity;
}

void InitHashBucket(HashBucket* pHB, int capacity)
{
	assert(pHB);
	capacity = 10;  //求出最接近哈希表长度_capacity的质数作为%的值;
	pHB->_table = (HashBucketNode**)malloc(capacity * sizeof(HashBucket*));
	if (NULL == pHB->_table)
	{
		assert(0);
		return;
	}
	int i = 0;
	for(;i<capacity;i++)
	{
		pHB->_table[i] = NULL;
	}
	pHB->size = 0;
	pHB->_capacity = capacity;
}
int InsertHashBucket(HashBucket* pHB, DataType data)
{
	int HashAddr = 0;
	assert(pHB);
	HashBucketNode* cur = NULL;

	CheckCapacity(pHB);                   //查看容量是否已满
	HashAddr = HashBucketFunc(data, pHB); //计算哈希桶号
	cur = pHB->_table[HashAddr];
	while (cur)                          //查找是否已经插入相同元素
	{
		if (data == cur->_data)
			return 0;
		cur = cur->_pNext;
	}
	cur = BuyHashBucketNode(data);
	cur->_pNext = pHB->_table[HashAddr];  //头插
	pHB->_table[HashAddr] = cur;
	pHB->size++;
	return 1;
}
int FindHashBucket(HashBucket* pHB, DataType data)
{
	int HashAddr = 0;
	HashBucketNode* cur = NULL;
	assert(pHB);
	HashAddr = HashBucketFunc(data, pHB);
	cur = pHB->_table[HashAddr];
	while (cur)
	{
		if (cur->_data == data)
			return 1;
		cur = cur->_pNext;
	}
	return 0;
}
int DeleteHashBucket(HashBucket* pHB,DataType data)
{
	int HashAddr = 0;
	HashBucketNode* cur = NULL;
	HashBucketNode* parent = NULL;
	assert(pHB);
	HashAddr = HashBucketFunc(data, pHB);
	cur = pHB->_table[HashAddr];
	while (cur)
	{
		if (data == cur->_data)     //找到待删除节点
		{
			if (cur == pHB->_table[HashAddr])      //待删除节点的位置在桶的第一个节点
				pHB->_table[HashAddr] = cur->_pNext;
			else
				parent->_pNext = cur->_pNext;
			free(cur);
			cur = NULL;
			pHB->size--;
			return 1;
		}
		parent = cur;
		cur = cur->_pNext;
	}
	return 0;
}
void DestroyHashBucket(HashBucket* pHB)
{
	assert(pHB);
	int i = 0;
	HashBucketNode* del = NULL;
	for (; i < pHB->_capacity; i++)
	{
		del = pHB->_table[i];
		while (del)
		{
			pHB->_table[i] = del->_pNext;
			free(del);
			del = pHB->_table[i];
		}
	}
	free(pHB->_table);
	pHB->_table = NULL;
	pHB->_capacity = 0;
	pHB->size = 0;
}
int SizeHashBucket(HashBucket* pHB)
{
	return pHB->size;
}
int EmptyHashBucket(HashBucket* pHB)
{
	assert(pHB);
	return 0 == pHB->size;
}


//test.c

void test()
{
	HashBucket hb;
	InitHashBucket(&hb,10);
	InsertHashBucket(&hb, 1);
	InsertHashBucket(&hb, 2);
	InsertHashBucket(&hb, 3);
	InsertHashBucket(&hb, 4);
	InsertHashBucket(&hb, 5);
	InsertHashBucket(&hb, 6);
	InsertHashBucket(&hb, 7);
	InsertHashBucket(&hb, 8);
	InsertHashBucket(&hb, 9);
	InsertHashBucket(&hb, 10);

	InsertHashBucket(&hb, 11);
	InsertHashBucket(&hb, 12);
	InsertHashBucket(&hb, 13);
	InsertHashBucket(&hb, 14);
	InsertHashBucket(&hb, 15);

	PrintHashBucket(&hb); 
	DestroyHashBucket(&hb);
	int i = FindHashBucket(&hb, 5);
	if (i == 1)
		printf("找到了\n");
	else 
		printf("找不到\n");
	DeleteHashBucket(&hb, 4);
	PrintHashBucket(&hb);
}
int main()
{
	test();
	system("pause");
	return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值