哈希概念
常规搜索:
数据杂乱无章——->顺序查找—–>时间复杂度0(n)。
数据有序—–>二分查找——>时间复杂度0(log(n))。
建立二叉搜索树—–>时间复杂度0(n)(单支树)。
理想的搜索方法是:可以不经过任何比较,一次直接从表中得到要搜索的元素,如果构造一种存储结构,通过某种函数使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么查找时通过该函数可以很快的找到该元素。
当该结构中:
- 插入元素时:根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放。
- 搜索元素时:对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若相等,则搜索成功。
该方法即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表或者散列表。
用该方法进行搜索不必进行多次关键码的比较,因此搜索速度比较快。但是哈希函数中一般会选一个最接近m(空间大小)的质数作为除数。
问题:按照上述哈希方法,向集合中插入2,会出现什么问题??
对于两个数据元素的关键字M,N(M!=N),但有Hash(M)=Hash(N),即不同的关键字算出了相同的哈希地址,该现象称为哈希冲突或哈希碰撞,把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。
那么如何处理哈希冲突呢????
发生哈希冲突的原因:
1. 哈希函数设置不合理
考虑重新设计哈希函数
哈希函数设计原则:
①.哈希函数的定义域必须包含需要存储的全部关键码,如果散列表有m个地址,其值域必须在0~m-1之间。
②.哈希函数计算出来的地址能均匀的分布在整个空间中。
③.哈希函数应该比较简单。
具体解决方法:
- 闭散列:开放定值法,当发生哈希冲突时,如果哈希表未被装满,说明哈希表必然还有空位置,那么就把关键字存放到表中“下一个”空位置去。
- 开散列:链地址法(开链法)首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一个集合,每一个子集称为一个桶,各个桶中的元素通过一个单链表链接起来。
本篇我们主要讲解闭散列:
闭散列:
当发生哈希冲突时,如果哈希表未被装满,说明哈希表必然还有空位置,那么就把关键字存放到表中“下一个”空位置去。
那如何寻找下一个空余位置?
1. 线性探测
从发生冲突的位置开始,依次继续向后探测,直到有空位置。
插入时:使用哈希函数找到待插入元素在哈希表中的位置,如果该位置没有元素则直接插入,如果该位置有元素但不是待插入元素则发生哈希冲突,使用线性探测找到下一个空位置,在找空位置的路上如果遇到元素与待插入元素相同则不插入(即哈希表中不允许有相同的元素),没有遇到等找到空位置就插入。
说明: 由于哈希表里不能有相同的元素,所以删除元素时就不能随便删除元素,若直接删除极易导致哈希表中元素重复。
优点: 简单
缺陷: 一旦发生哈希冲突,所有的冲突连在一起,容易产生数据堆积。
那如何缓解呢??
负载因子:
散列表的负载因子定义为:α=填入表中的元素个数 / 散列表的长度。α是散列表装满程度的标志因子,由于表长是定值,α与填入表中的元素个数成正比,所以,α越大,填入表中的元素就越多,产生冲突的可能性就越大;反之,α越小,标明填入表中的元素就越少,产生冲突的可能性就越小。一般应该严格控制在0.7~0.8之间。超过0.8,查表时的不命中率按照指数曲线上升。
- 二次探测
发生哈希冲突时,二次探测寻找下一个空位置的公式为:H(i)=(H(0)+i^2)^2%m
优点:不存在数据堆积。
缺点: 当元素较多时需要比较很多次。
代码实现:
.h文件
#include<assert.h>
#include<malloc.h>
#include<stdlib.h>
#include<math.h>
#include<assert.h>
#include<stdio.h>
//哈希表的状态
typedef enum state
{
EMPTY, //空
EXIST, //存在
DELETE,//删除
}state;
typedef int DataType;
typedef struct Elem
{
DataType _data;
enum state _state;
}Elem;
typedef struct Size
{
int size; //哈希表中有效元素个数
int del; //哈希表被删除元素的个数
}Size;
//哈希表
typedef struct HashTable
{
Elem *data;
int capacity;
Size size;
}HashTable;
//初始化哈希表
void InitHashTable(HashTable *hashtable);
//插入哈希表
void InsertHashTable(HashTable *hashtable, DataType data);
//删除哈希表
int DeleteHashTable(HashTable *hashtable, DataType data);
//查找
int FindHashTable(HashTable *hashtable, DataType data);
//判空
int EmptyHashTable(HashTable *hashtable);
//哈希表元素个数
int SizeHashTable(HashTable *hashtable);
<