散列(哈希表)

本文介绍了散列技术的基本原理,包括散列表的概念、散列函数的设计方法及其应用场景。详细阐述了六种常用的散列函数实现方式,并探讨了如何解决散列过程中产生的冲突问题。

      散列是一种用于以常数平均时间执行插入、删除和查找的技术。理想的散列表数据结构是一个包含有关键字的具有固定大小的数组。典型情况下,一个关键字就是一个带有相关值的字符串。

      通常把表的大小记作TableSize。每个关键字被映射到从0到TableSize-1这个范围中的某个数,并且被放到适当的单元中。这个映射就叫做散列函数。


散列函数

      如果输入的关键字是整数,则一般方法是直接返回“Key mod TableSize”的结果,除非Key具有某些不理想的性质。例如,若表的大小是10而关键字都以0为个位,则此时上述标准的散列函数就是一个不好的选择。为了避免上面那种情况,通常保证表的大小是素数。

下面是常用方法:

1.直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a·key + b,其中a和b为常数(这种散列函数叫做自身函数)。若其中H(key)中已经有值了,就往下一个找,直到H(key)中没有值了,就放进去。
2.数字分析法:分析一组数据,比如一组员工的出生年月日,这时我们发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。
3.平方取中法:当无法确定关键字中哪几位分布较均匀时,可以先求出关键字的平方值,然后按需要取平方值的中间几位作为哈希地址。这是因为:平方后中间几位和关键字中每一位都相关,故不同关键字会以较高的概率产生不同的哈希地址。
4.折叠法:将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。数位叠加可以有移位叠加和间界叠加两种方法。移位叠加是将分割后的每一部分的最低位对齐,然后相加;间界叠加是从一端向另一端沿分割界来回折叠,然后对齐相加。
5.随机数法:选择一随机函数,取关键字的随机值作为散列地址,通常用于关键字长度不同的场合。
6.除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p,p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。


冲突解决

如果当一个元素被插入时另一个元素已经存在(散列值相同),那么就产生一个冲突,这个冲突需要消除。

解决冲突的方法有:分离连接法开放定址法(平方探测)、线性探测、二次散列


一、分离链接法--拉链法
分离链接法是将散列到同一个值的所有元素保留到一个表中。
#include<iostream>
using namespace std;

typedef struct listNode {
        const char *value;
        struct listNode* next;
}ListNode;

typedef struct hashTable {
        int tableSize;
        ListNode **theLists;
}HashTable;

unsigned int Hash(const char* key,int tableSize);
HashTable* InitHashTable(int tableSize);
ListNode* Find(const char *key,HashTable *H);
void Insert(const char *key,HashTable *H);

unsigned int
Hash(const char *key,int tableSize)
{
         unsigned int hashVal = 0;
         while (*key != '\0')
                hashVal = (hashVal << 5) + *key++;
         return hashVal % tableSize;
}

HashTable* 
InitHashTable(int tableSize)
{
         int i;
         HashTable *H;
         H = (HashTable*)malloc(sizeof(struct hashTable));
         if (H == NULL)
                 printf("out of space for H!");

         H->theLists = (ListNode**)malloc(sizeof(struct listNode)*tableSize);
         if (H->theLists == NULL)
                 printf("out of space for H->theLists");

         for (i = 0; i < H->tableSize; i++) {
                 H->theLists[i] = (ListNode*)malloc(sizeof(struct listNode));
         if (H->theLists[i] == NULL)
                 printf("out of space");
         else
                 H->theLists[i]->next = NULL;
         }
         return H;
}

ListNode* 
Find(const char *key, HashTable *H)
{
         ListNode *p;
         ListNode *L;
         L = H->theLists[Hash(key,H->tableSize)];
         p = L->next;
         while (p != NULL && p->value != key)
                p = p->next;
         return p;
}

void 
Insert(const char *key, HashTable *H)
{
        ListNode* p, *temp;
        ListNode *L;
        p = Find(key,H);
        if (p == NULL)
        {
            temp = (ListNode*)malloc(sizeof(ListNode));
            if (temp == NULL)
                printf("out of space");
            else {
                L=H->theLists[Hash(key,H->tableSize)];
                temp->next = L->next;
                temp->value = key;
                L->next = temp;
            }
         }
}

二、开放定址--平方探测法


分离链接法的缺点是需要指针,由于需要给新单元分配地址需要时间,导致算法速度有些慢。开放定址散列法是一种不用链表解决冲突的方法。

#include<iostream>

using namespace std;

#define MINSIZE 100

static int MinTableSize = MINSIZE;
enum EntryKind {Legitimate,Empty,Deleted};

struct HashEntry
{
          char* value;
          enum EntryKind info;
};


typedef struct HashTb
{
         int TableSize;
         struct HashEntry *theCells;
}HashTable;

int NextPrime(int TableSize);
void handlError(char *msg);
unsigned int Hash(char *key,int TableSize);
HashTable* InitializeTable(int TableSize);
unsigned int Find(char*,HashTable *H);
void Insert(char*,HashTable *H);
HashTable* Rehash(HashTable *H);

unsigned int
Hash(char *key, int TableSize)
{
            unsigned int hashVal=0;
            while (*key != '\0')
            hashVal = (hashVal << 5) + *key++;
            return hashVal % TableSize;
}

HashTable*
InitializeTable(int TableSize)
{
        HashTable *H;
        int i;
        if (TableSize < MinTableSize)
        {
                handlError("Table size is too small");
        }

        H = (HashTable*)malloc(sizeof(HashTable));
        if (H == NULL)
                handlError("malloc for H is error");

         H->TableSize = NextPrime(TableSize);

         H->theCells = (struct HashEntry*)malloc(sizeof(struct HashEntry)*H->TableSize);
         if (H->theCells == NULL)
                   handlError("malloc for H->theCells is error");

         for (i = 0; i < H->TableSize; i++)
                   H->theCells[i].info = Empty;
         return H;
}


unsigned int 
Find(char* key, HashTable *H)
{
        unsigned int Pos;
        int collisionNum;
        collisionNum = 0;
        Pos = Hash(key,H->TableSize);
        while (H->theCells[Pos].info != Empty && H->theCells[Pos].value != key)
        {
                Pos = Pos + 2 * ++collisionNum - 1;
                if (Pos >= H->TableSize)
                         Pos -= H->TableSize;
         }
        return Pos;
}


void 
Insert(char* key, HashTable *H)
{
         unsigned int Pos;

         Pos = Find(key,H);
         if (H->theCells[Pos].info != Legitimate)
         {
                   H->theCells[Pos].info = Legitimate;
                   H->theCells[Pos].value = key;
         }
}

HashTable* 
Rehash(HashTable *H)   //再散列
{
        int i, oldSize;
        HashEntry *oldCells;
        oldCells = H->theCells;
        oldSize = H->TableSize;

        H = InitializeTable(2*oldSize);
        for (i = 0; i < oldSize; i++)
             if (oldCells[i].info == Legitimate)
                          Insert(oldCells[i].value,H);
        free(oldCells);
        return H;
}

void 
handlError(char *msg)
{
        fputs(msg,stderr);
        fputc('\n',stderr);
        exit(1);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值