一、散列原理
理想的散列结构不过是一个包含关键字的具有固定大小的数组,散列表的长度是数据结构的一部分。散列中的关键字不需要支持比较大小,只要能够支持判等操作就行了。每个关键字按照一个散列函数被映射到0到TableSize-1范围内的桶单元。当不同的关键字被映射到相同的单元时,称作冲突(conflict)。散列的插入,查找,删除都是常数时间复杂度。
常见的散列方法有除余法hash(key)=key%M,字符串为key时也常对字符求和作为散列值。
二、闭路定址
散列表的值为一个指向链表节点的指针,当对应的桶单元被占用时,将对应的值插入到对应的链表中(比如插入到链表的
头结点),这种方式节省空间,删除操作简单,但是需要动态的申请节点内存,影响速度,并且空间分布不连续,系统缓存
失效,I/O次数增多。
//结构定义
struct ListNode;
typedef struct ListNode* Position;
struct HashTbl;
typedef struct HashTbl *HashTable;
HashTable InitializeTable(int TableSize);
void DestroyTable(HashTable H);
Position Find(ElementType Key,HashTable H);
void Insert(ElementType Key,HashTable H);
ElementType Retrieve(Position P);
struct ListNode
{
ElementType Element;
Position Next;
}
typedef Position List;
struct HashTbl
{
int TableSize;
List* TheLists;//二级指针,表示存放节点地址的数组;
}
<textarea readonly="readonly" name="code" class="c++">
//InitializeTable()
HashTable InitializeTable(int TableSize)
{
HashTable H;
if(TableSize<MinTableSize)
{
Error("Table size too small");
return NULL;
}
HashTable=malloc(sizeof(struc HashTbl));
if(HashTable==NULL)
{
FatalError("Out of space!");
}
H->TabelSize=nextPrim(Tablesize);
H->TheList=malloc(sizeof(ListNode*)*TabelSize);
if(H->TheList==NULL)
{
FatalError("Out of space!");
}
int i=0;
for(i;i<TableSize;++i)
{
H->TheList[i]=malloc(sizeof(struct ListNode));
if(H->TheList[i]==NULL)
{
FatalError("Out of space!");
}
else
{
H->TheList[i]->Next=NULL;
}
}
}
//Find()
Position Find(ElementType Key,HashTable H)
{
Positon start=H->TheLists[Hash(Key),H->TableSize)];
while(start!=NULL && start->Element!=Key)
{
start=start->Next;
}
return start;
}
//Insert()
void Insert(ElementType Key,HashTable H)
{
Position p=Find(Key,H);
if(p==NULL)
{
Position NewCell=malloc(sizeof(struct ListNode));
if(NewCell==NULL)
{
FatalError("Out of space!");
}
else
{
Position L=H->TheList[Hash(Key,H->TableSize)];
NewCell->Next=L->Next;
NewCell.Element=Key;
L->Next=NewCell;
}
}
}
三、开放定址
(1)线性试探
线性试探指的是,当发生散列冲突时,从当前位置出发,逐一的往前试探,直到找到一个空的位置。
其优点是空间在初始时就分配好,无需动态分配,提高了效率,并且由于数据临近,具有良好的局部性,充分利用了系统的IO性能,但是会导致更多的冲突。插入时若新词条尚不存在,则存入直接查找终止处的空桶,若存在,则往后逐一试探,直至找到空桶。删除时由于存在查找链,直接清除命中的桶会导致随后的查找链被切断,因此可做惰性删除。
(2)平方试探
以平方数为距离,确定下一个试探桶单元。
只有当表长为素数,切装填因子不超过0.5时,才一定能找出一个空桶。
(3)双平方试探
从冲突位置开始,依次向后试探。
四、桶排序
桶排序适用于元素分布范围固定,元素分布随机的情况,例如下面是输入最小值,最大值,和散列长度进行桶排序的代码,每个通采用链表组织,桶内部用STL里面list的sort()函数。
vector<int> BucketSort(vector<int>iVec,int min_elem,int max_elem,int bucketSize)
{
vector<int>sortedVec;
list<int>* bucketTbl = new list<int>[bucketSize];
list<int>empty_lis;
for (int i = 0; i < bucketSize;++i)
{
bucketTbl[i] = empty_lis;
}
int perBucket = (max_elem - min_elem )/ bucketSize + 1;
for (int j = 0; j < iVec.size();++j)
{
bucketTbl[(iVec[j] - min_elem) / perBucket].push_back(iVec[j]);
}
for (int k = 0; k < bucketSize;++k)
{
bucketTbl[k].sort();
copy(bucketTbl[k].begin(), bucketTbl[k].end(), back_inserter(sortedVec));
}
delete[]bucketTbl;
return sortedVec;
}
int main()
{
vector<int>input_vec = { 11, 23, 32, 77, 43, 34,83, 55, 99, 47,67, 20,5, 101, 120, 130 };
copy(input_vec.begin(), input_vec.end(), ostream_iterator<int>(std::cout, " "));
vector<int>sorted_vec = BucketSort(input_vec, 11,130,11);
cout << endl;
copy(sorted_vec.begin(), sorted_vec.end(), ostream_iterator<int>(std::cout, " "));
system("pause");
return 0;
}
桶排序适用于当输入范围有限,并且输入值随机分布的情况,是最耗费空间的排序方法,但是能获得线性的复杂度。