散列(Hash)
散列是以常数平均时间执行插入,删除,查找的技术;需要元素之间比较,排序的信息,如查找最大,最小值等等操作不被hash所支持;
散列表关键的是设计哈希函数,运算简单并且在单元之间均匀的分配关键字,并减少碰撞;
1 //分离连接散列表的类型声明 2 #ifndef _HashSep_H 3 4 struct ListNode; 5 typedef struct ListNode *Position; 6 struct HashTbl; 7 typedef struct HashTbl *HashTable; 8 9 HashTable InitializeTable(int TableSize); 10 void DestroyTable(HashTable H); 11 Position Find (ElementType Key,HashTable H); 12 void Insert(ElementType Key,HashTable H); 13 ElementType Retrieve(Position P); 14 15 #endif /*_HashTable_H声明*/ 16 17 struct ListNode 18 { 19 ElementType Element; 20 Position Next; 21 }; 22 23 typedef Position List; 24 25 struct HashTbl 26 { 27 int TableSize; 28 List *TheLists;//TheList 实际上是一个指向指向ListNode结构的指针的指针; 29 }; 30 31 //初始化 32 HashTable InitializeTable(int TableSize) 33 { 34 HashTable H; 35 int i; 36 if(TableSize<MinTableSize) 37 { 38 Erro("Table Size too small!"); 39 return NULL; 40 } 41 //分配hash表 42 H=malloc(sizeof(struct HashTbl)); 43 if(H==NULL) 44 { 45 FataErro("Out of Space!"); 46 } 47 H->TableSize=NextPrime(TableSize);//设置表的大小为以素数; 48 //分配链表数组 49 H->TheLists=malloc(sizeof(List)*H->TableSize); 50 if(H->TheList==NULL) 51 FataErro("out of space"); 52 //分配头结点 53 for(i=0;i<H->TableSize;i++) 54 { 55 H->TheLists[i]=malloc(sizeof(Struct ListNode)); 56 if(NULL==H->TheList[i]) 57 FataErro("Out of space"); 58 else 59 H->TheList[i]->Next=NULL; 60 } 61 return H; 62 } 63 // 分散链接表的Find操作 64 Position Find (ElementType Key,HashTable H) 65 { 66 Position P; 67 List L; 68 L=H->TheLists[hash(Key,H->TableSize)]; 69 P=L->Next; 70 while(NULL!=P&&P->Element!=Key) 71 p=p->next; 72 return p; 73 } 74 //分散链接表的Insert操作 75 void Insert(ElementType Key,HashTable H) 76 { 77 List L; 78 Position pos,NewCell; 79 pos=Find(KEy ,H); 80 if(Pos==NULL)//key在hash表中未找到 81 { 82 NewCell=malloc(sizeof(struct ListNode)); 83 if(NULL==NewCell) 84 FataErro("out of space"); 85 else 86 { 87 L=H-TheLists[hash(Key,H->TableSize)]; 88 NewCell->Next=L->Next; 89 NewCell->Element=Key; 90 L->Next=NewCell; 91 } 92 } 93 }
分离链接散列算法的缺点是需要指针,由于给新单元分配地址需要时间,因此这就导致算法速度的减慢,同时算法实际上还要求对另一种数据结构的实现;
开放定址法
常用的冲突解决的方法:线性探测法,平方探测法,双散列(两个散列函数)
定理:如果使用平方探测,且表的大小是素数,那么当表至少有一半是空的时候,总能够插入一个新的元素。
对于使用平方探测的开放定址散列法,如果表的元素填的太满,那么操作的运行时间将开始消耗过长,且Insert操作可能失败。这可能发生在有太多的移动和插入场合,此时解决的方法是建立另外一个大约两倍大的表,扫描整个原始散列表,计算每个元素的新散列表值并将其插入到新表中;此时产生了再散列。
小结:
散列表可以用来以常数时间实现Insert和Find操作。当使用散列表时,注意诸如装填因子细节特别重要;
分散链接散列法:虽然装填银子不很大时,性能并不明显降低,但装填因子还是应该接近于1;
对于开放定址法:除非完全不可避免,否则装填因子应该不超过0.5.如果使用线性探测,那么性能随着装填因子接近于1将急速下降;
再散列运算可以通过使表增长(收缩)来实现,这样将会保持合理的装填因子;
使用散列表可以不可能找出最小元素。除非准确知道一个字符串,否则散列表也不可能有效查找它。二叉查找树可以迅速找到在一定范围内的所有项,散列表是做不到的。。
如果不需要有序的信息以及对输入的排序,那么就应该使用散列表;
散列表的典型用途:1)、编译器使用散列表跟踪源代码中声明的变量。这种数据结构叫做符号表,2)在为游戏编制的程序中。当程序搜索不同的行时,它跟踪通过计算基于位置的散列函数而看到一些位置。如果同意的位置再出现。程序通常通过简单移动变换来避免昂贵的重复计算。游戏程序的这种一般特点叫做交换表。