散列

基本概念

  1. 散列是一种用于以常数平均时间执行插入、删除和查找的技术
  2. 理想的散列表数据结构是一个包含有关键字的具备固定大小的数组,表的大小记做TableSize,习惯上使表从0TableSize - 1变化,每一个关键字被映射到0TableSize - 1范围中的某个数并被放在合适的单元中,这个映射就叫散列函数
  3. 当两个关键字散列到同一个值的时候就会产生冲突,由于单元数目有限而关键字无穷多,处理冲突的方法有分离连接法和开放定址法等
  4. 散列的通常作法是对\(Key\)作一些运算,最后获得一个\(HashVal\),而后使\(Key\) \(mod\) \(TableSize\),困难之处在于须要使得\(HashVal\)分布的尽可能均匀,通常考虑将\(TableSize\)尽量为一个素数
  • 好比,考虑将字符串的ASCII值相加(一种简单的散列)
typedef unsigned int Index;

Index Hash(const char *Key, int TableSize)
{
    unsigned int HashVal = 0;
    while (*key != '\0') HashVal += *Key++;
    return HashVal % TableSize;
}
  • 好比,取前三个字符,每一个给予不一样的权重(一种不太好的散列)
Index Hash(const char *Key, int TableSize)
{
    return (Key[0] + 27 * Key[1] + 729 * Key[2]) % TableSize;
}
  • 好比,使用一个更复杂的公式\(\sum^{KeySize - 1}_{i = 0}{Key[KeySize - i -1] * 32^i}\)得到\(HashVal\)
Index Hash(const char *Key, int TableSize)
{
    unsigned int HashVal = 0;
    while (*Key != '\0') HashVal = (HashVal << 5) + *Key++;
    return HashVal % TableSize;
}

解决冲突

分离连接法

它的作法就是将散列到同一个值的全部元素保留到一个如图所示的链表数组中数组

//分离连接散列表类型声明
struct ListNode;
typedef struct ListNode *Position;
struct Hashbl;
typedef struct Hashbl *HashTable;

struct ListNode
{
    ElementType Element;
    Position Next;
};

typedef Position List;
struct Hashbl
{
    int TableSize;
    List* TheLists;
};

//初始化
HashTable InitializeTable(int TableSize)
{
    HashTable H;
    int i;

    if (TableSize < MiniTableSize)
    {
        Error("Table size too small");
        return NULL;
    }

    H = malloc(sizeof(struct HashTbl));
    if (H == NULL) FatalError("out of space");
    H->TableSize = NextPrime(TableSize);
    H->TheLists = malloc(sizeof(List) * H->TableSize);
    if (H->TheLists == NULL) FatalError("out of space");

    for (i = 0; i < H->TableSize; i++)
    {
        H->TheLists[i] = malloc(sizeof(struct ListNode));
        if (H->TheLists[i] == NULL) FatalError("out of space");
        else H->TheLists[i]->Next = NULL;
    }
    return H;
}

//寻找一个值
Position Find(Element Key, HashTable H)
{
    Position P;
    List L;
    L = H->TheLists[Hash(Key, H->TableSize)];
    P = L->Next;
    while (P != NULL && P->Element != Key) P = P->Next;
    return P;
}

//插入一个值
void Insert(ElementType Key, HashTable H)
{
    Position Pos, NewCell;
    List L;
    
    Pos = Find(Key, H);
    if (Pos == NULL)
    {
        NewCell = malloc(sizeof(struct ListNode));
        if (NewCell == NULL) FatalError("out of space");
        else
        {
            L = H->TheLists[Hash(Key, H->TableSize)];
            NewCell->Next = L->Next;
            NewCell->Element = Key;
            L->Next = NewCell;
        }
    }
}

如上面的代码所示,它将新元素插入到表的最前面,不只是由于方便,更是由于新插入的元素有可能最早被访问,这里使用了链表,天然还能够想到用二叉查找树,甚至另外一个散列表来实现,这里使用了表头,在空间不足时能够考虑不使用表头数据结构

定义散列表的装填因子\(\lambda\)为散列表中的元素个数与散列表大小的比值函数

开放定址法

简介

开放定址散列法是另外一种不用链表解决冲突的办法,在开放定址法中,若是有冲突发生,就选择尝试另外的单元,直到找到空的单元为止,单元\(h_0(X)\)\(h_1(X)\)\(h_2(X)\)等相继被试选,其中\(h_i(X) = (Hash(X) + F(i))\) \(mod\) \(TableSize\)\(F(0) = 0\),函数\(F\)为冲突解决方法spa

线性探测法

\(F(i) = i\)的情形,就是逐个探测每一个单元格以查找出一个空单元,这种方法的一个问题就是即便表格相对较空,占据的单元也会造成一些区块,其结果称为一次聚焦code

平方探测法

\(F(i) = i^2\)的情形,它消除了线性探测的一次聚焦问题,关于它有一个定理:若是使用平方探测,且表的发小是素数(两个条件都要知足),那么当表至少有一半是空的时候,总可以插入一个新的值。虽然平方探测消除了一次聚焦,可是散列到同一位置的元素将探测相同的备选单元,称为二次聚焦,模拟结果指出,它通常要引发另外的少于一半的探测blog

双散列

再散列

可扩散列

相关文章
相关标签/搜索