/* 8.9 散列表查找(哈希表)概述 散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key) 查找时,根据这个确定的对应关系找到给定值key的映射f(key),若查找集合中存在这个记录,则必定在f(key)的位置上。 这里我们把这种对应关系f称为散列函数,又称为哈希表(Hash)函数。按这个思想,采用散列技术将记录存储在一块连续的 存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。那么关键字对应的记录存储位置我们成为散列地址。 8.9.2 散列表查找步骤(在哪存的,上哪去找) 1.在存储时,通过散列函数计算记录的散列地址,并按此散列地址存储该记录。 2.当查找记录时,我们通过同样的散列函数计算记录的散列地址,按此散列地址访问该记录。 8.10 散列函数的构造方法 设计原则: 1.计算简单 2.散列地址分布均匀 常用的几种构造方法: 1.直接定址法 2.数字分析法 3.平方取中法 4.折叠法 5.除留余数法 6.随机数法 8.11 处理散列冲突的方法 从上边的除留余数法的例子也可以看出,我们设计得再好的散列函数也不可能完全避免冲突,这就像我们再健康也只能尽量预防疾病, 但却无法保证永远不得病一样,既然冲突不能避免,就要考虑如何处理它。 ....具体方法见书 p637 */ /* 8.12 散列表查找实现 */ #define SUCCESS 1 #define UNSUCCESS 0 //定义散列表长为数组的长度 #define HASHSIZE 12 #define NULLKEY -32768 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; return OK; } //散列函数 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 + 1] == NULLKEY || *addr == Hash(key)) { //如果循环回到原点 则说明关键字不存在 return UNSUCCESS; } } return SUCCESS; } /* 8.12.2 散列表查找性能分析 如果没有冲突,散列表(哈希表)查找是我们本章介绍的所有查找中效率最高的,因为它的时间复杂度为O(1)。 可惜,没有冲突的散列表只是一种理想,实际应用中,冲突时不可避免的。 那么三列查找的平均查找长度取决于哪些因素呢? 1.散列表是否均匀 2.处理冲突的方法 3.散列表的装填因子 */