浅谈散列
通过一个函数,将要储存的值与其储存的地址联系起来,方便进一步的访问。出于这个原因,设计的散列函数(Hash)就要有比较成熟的考虑。但是无论散列函数优秀与否,我们都应该重视冲突的解决。
Hi(X) = (Hash(X) + F(i)) mod TableSize
一般公式,其中X是要储存的元素,i是已发生的冲突次数
解决冲突
分离链接法
使用链表操作,在发生冲突的地方插入新的节点。
struct ListNode;
typedef struct ListNode *Position;
struct HashTbl;
typedef struct HashTbl *HashTable;
struct ListNode
{
int num;
Position Next;
};
typedef Position List;
struct HashTbl
{
int TableSize;
List *TheLists;
};
建立散列(使用了表头链接散列)
HashTable Initialize(int TableSize)
{
HashTable H;
int i;
H = malloc(sizeof(struct HashTbl));
if(H == NULL)
{
printf("Out of Space\n");
exit(EXIT_FAILURE);
}
H->TableSize = NextPrime(TableSize);//找出大于TableSize的下一个素数
H->Thelists = malloc(sizeof(List) * H->TableSize);
if (H->thelists == NULL)
{
printf("Out of space!");
exit(EXIT_FAILURE);
}
for (int i = 0; i < H->tablesize; i++)
{
H->thelists[i] = malloc(sizeof(struct ListNode));
if (H->thelists[i] == NULL)
{
printf("Out of space!");
exit(EXIT_FAILURE);
}
else
{
H->thelists[i]->next = NULL;
}
}
return H;
}
查找操作
//找到会返回地址
//找不到会返回NULL
Position Find(int Key, HashTable H)
{
Position P;
List L;
L = H->TheLists[Hash(Key, H->TableSize)];//Hash函数自己设计
P = L->Next;
while(P != NULL && P->num != Key)
{
P = P->Next;
}
return P;
}
插入操作
先查找,再插入。插在已有元素的前面,就像排队一样。但是具体使用的时候这个函数需要重写,因为Find函数内部也计算了一次散列函数。把的操作写在这个函数的内部就行了。
void Insert(int Key, HashTable H)
{
Position Pos, NewCell;
List L;
Pos = Find(Key, H);
if(Pos == NULL)
{
NewCell = malloc(sizeof(struct ListNode));
if(NewCell == NULL)
{
printf("Out of Space\n");
exit(EXIT_FAILURE);
}
else
{
L = H->TheLists[Hash(Key, H->HashSize)];
NewCell->Next = L->Next;
NewCell->num = Key;
L->Next = NewCell;
}
}
}
开放定址法
一开始申请的散列空间略大于要储存的数据的量。
冲突函数可以用线性探测法,也可以用平方探测法。
typedef unsigned int Index;
typedef Index Position;
struct HashTbl;
typedef struct HashTbl *HashTable;
enum kindOfEntry {Legitimate, Empty, Deleted};
struct HashEntry
{
int num;
enum KindOfEntry Info;
};
typedef struct HashEntry Cell;
struct HashTbl
{
int TableSize;
Cell *TheCells;
};
建立散列的操作仅与分散链接法略有不同
HashTable InitializeTable(int TableSize)
{
HashTable H;
int i;
H = malloc(sizeof(struct HashTbl));
if(H == NUll)
{
printf("Out of Space\n");
exit(EXIT_FAILURE);
}
H->TableSize = NextPrime(TableSize);
H->TheCells = malloc(sizeof(struct HashEntry) * H->TableSize);
if (H->TheCells)
{
printf("Out of Space\n");
exit(EXIT_FAILURE);
}
for (i = 0; i < H->TableSize; i++)
{
H->TheCells[i].Info = Empty;
}
return H;
}
典型的线性探测法F(i) = i,平方探测法F(i) = i^2。
平方探测法查找操作
Position Find(int Key, HashTable H)
{
Position CurrentPos;
int ColistionNum;
ColistionNum = 0;
CurrentPos = Hash(Key. H->TableSize);
while(H->TheCells[CurrentPos].Info != Empty && H->TheCells[CurrentPos].num != Key)
{
//F(i) = i^2;F(i - 1) = i^2 - 2 * i + 1;
//F(i) = F(i - 1) + 2 * i - 1;
CurrentPos += 2 * ++ColistionNum - 1;
if(CurrentPos >= H->HashTable)
{
CurrentPos -= H->TableSize;
}
}
return CurrentPos;
}
平方探测法插入操作
用数据储存点的Info标志改点数据的意义
void Insert(int Key, HashTable H)
{
Position Pos;
Pos = Find(Key, HashTable H);
if (H->TheCells[Pos].Info != Legitimate)
{
H->TheCells[Pos].Info = Legitimate;
H->TheCells[Pos].num = Key;
}
}
删除操作简单来说就是先查找,然后根据查找的结果修改数据的状态。
再散列
通过某种标志判定开放选址散列已使用的空间情况,重构散列。对于一开始无法却确定数据大小的情况有用。
HashTable Rehash(HashTable H)
{
int i,OldSize;
Cell *OdlCells;
OdlCells = H->TheCells;
OldSize = H->TableSize;
H = InitializeTable(2 * OldSize);
for (i = 0; i < OldSize; i++)
{
if (OdlCells[i].Info == Legitimate)
{
Insert(OdlCells[i].num, H);
}
}
free(OdlCells);
return H;
}