数据结构与算法分析 学习笔记 (4)

散列(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)在为游戏编制的程序中。当程序搜索不同的行时,它跟踪通过计算基于位置的散列函数而看到一些位置。如果同意的位置再出现。程序通常通过简单移动变换来避免昂贵的重复计算。游戏程序的这种一般特点叫做交换表。

Edit By SolarJupiter
原文地址:https://www.cnblogs.com/HuaiNianCiSheng/p/3104124.html