哈希搜索结构
顺序搜索以及二叉搜索树中,元素存储位置和元素各关键码之间没有对应的关系,因此在查找一个元素的时候,必须要经过关键码的多次比较。所以这样的搜索效率取决于搜索过程中元素的比较次数。
而我们理想的搜索方法就是不用经过任何比较,一次直接从表中得到要搜索的元素。如果能够构造一种结构,让所要查找的关键码与它的存储位置有一定的关系,那么可以根据这种关系找到其位置从而更快的找到关键码。
在这种结构中插入时,根据待插入的元素从而经过某些函数计算出它的插入位置,进行插入;查找时通过对查找元素的计算出其位置,再根据位置在结构中的对应出进行比较,如果相等则查找成功。
这种方式的结构就是哈希搜索结构,其中使用的计算位置函数就是哈希函数。
哈希函数
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;
}