1. 散列表查找算法实现
- 首先定义一个散列表的结构以及一些相关的常数。其中 HashTable 就是散列表结构, 结构当中的 elem 为一个动态数组。
#define MAXSIZE 100 /* 存储空间初始分配量 */
#define SUCCESS 1
#define UNSUCCESS 0
#define HASHSIZE 12 /* 定义散列表长为数组的长度 */
#define NULLKEY -32768
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef struct
{
int *elem; /* 数据元素存储基址,动态分配数组 */
int count; /* 当前数据元素个数 */
}HashTable;
int m=0; /* 散列表表长,全局变量 */
- 有了结构,再对散列表进行初始化
/* 初始化散列表 */
Status InitHashTable(HashTable *H)
{
int i;
m=HASHSIZE;
H->count=m;
H->elem=(int *)malloc(m*sizeof(int));
for(i=0;i<m;i++)
H->elem[i]=NULLKEY;
re
- 为了插入时计算地址,我们需要定义散列函数,散列函数可以根据不同情况更改算法。
/* 散列函数 */
int Hash(int key)
{
return key % m; /* 除留余数法 */
}
- 初始化完成后,我们可以对散列表进行插入操作,假设我们插入点关键字集合是 { 12,67,56,16,25,37,22,29,15,47,48,34}
/* 插入关键字进散列表 */
void InsertHash(HashTable *H,int key)
{
int addr = Hash(key); /* 求散列地址 */
while (H->elem[addr] != NULLKEY) /* 如果不为空,则冲突 */
{
addr = (addr+1) % m; /* 开放定址法的线性探测 */
}
H->elem[addr] = key; /* 直到有空位后插入关键字 */
}
- 代码中插入关键字时,首先算出散列地址,如果当前地址不为空关键字,则说明有冲突,此时我们应用开放定址法的线性探测进行重新寻址,此时也可更改为链地址法等其他解决冲突的办法。
- 散列表存在后,我们在需要时就可以通过散列表查找要的记录。
/* 散列表查找关键字 */
Status SearchHash(HashTable H,int key,int *addr)
{
*addr = Hash(key); /* 求散列地址 */
while(H.elem[*addr] != key) /* 如果不为空,则冲突 */
{
*addr = (*addr+1) % m; /* 开放定址法的线性探测 */
if (H.elem[*addr] == NULLKEY || *addr == Hash(key)) /* 如果循环回到原点 */
return UNSUCCESS; /* 则说明关键字不存在 */
}
return SUCCESS;
}
查找的代码和插入的代码非常类似,只需做一个不存在关键字的判断而已。
散列表查找性能分析
如果没有冲突,散列查找是当前介绍的所有查找中效率最高的,因为它时间复杂度为 O(1)。但冲突在实际的应用中是不可避免的。
散列查找的平均长度取决于哪些因素呢?
- 散列是否均匀
散列函数的好坏直接影响着出现冲突的频繁程度,不过,由于不同的散列函数对同一组随机的关键字,产生冲突的可能性是相同的,因此我们可以不考虑它对平均查找长度的影响。 - 处理冲突的方法
处理冲突的方法不同,会使得平均查找长度不同。 - 散列表的装填因子
α = 填入表中的记录数 / 散列表长度。
α 标志着散列表的装满的程度, 当填入表中的记录越多,α就越大,产生冲突的可能性就越大。
不管记录个数 n 有多大,我们总可以选择一个合适的装填因子以便将平均查找长度限定在一个范围之内,此时我们散列查找的时间复杂度就真的是 O(1) 了,为了做到这一点,我们通常是将散列表的空间设置得比查找集合大,此时虽然是浪费了一定的空间,但换来的是查找效率的大大提升。