数据结构 --- 闭散列处理哈希冲突(线性探测)

前面我们提到过哈希冲突产生的原因:
是因为不同关键字通过相同的哈希函数计算得出相同的哈希地址。

那发生了哈希冲突,我们该如何处理呢?
有两种方法:1.闭散列,如线性探测;2.开散列,例如:哈希桶

这里我们用闭散列的方式来解决哈希冲突。

闭散列:也叫开放地址法,当发生哈希冲突时,如果哈希表未被填满,说明哈希表中还存在空余的位置,那么就将key存放到“下一个”空位去。

那如何去找下一个空余的位置呢?

线性探测法,拿我们上次的关键码集合{12,285,609,541,753,456,278,47}为例,散列表的大小为10,假设哈希函数为Hash(key) = key % 10:
这里写图片描述

当再往表里插入43时,发现了它和753发生了碰撞,那我们就必须要找出下一个存放43的位置。

线性探测的处理方法是:从发生冲突开始,依次继续向后探测,直到找到空位置为止。

所以,我们从下标为3的位置开始往后找,发现它的下一个位置即下标为4为空,那么就将43插入到此位置。

这里,我们需要引入一个负载因子的概念,对于开放地址法来说,是一个特别重要的因素:

a = 填入表中的元素个数 / 散列表的长度
a是散列表装满程度的标志因子。由于表长是定值,a与表中元素个数成正比。所以,a越大,表中元素越多,发生冲突的可能性就越大。
因此,因严格限制a在0.7-0.8以下,超过0.8,查表时的CPU缓存不命中按指数曲线上升。

接下来,我们来对哈希表进行插入、查找和删除操作。

一个哈希表里应包含一个成员数组和哈希函数,而数组的每个元素又是键值对类型,所以它的每一个元素可以定义为一个结构体,对哈希表的定义如下:

#define HashMaxSize 1000
typedef size_t KeyType;
typedef size_t ValType;

typedef enum
{
    Empty, //空状态
    Deleted, //删除状态
    Valid,  //有效状态
}Stat;

typedef size_t (*HashFunc)(KeyType key);

//这个结构体代表哈希表中的一个元素
//这个元素中同时也包含了键值对
typedef struct HashElem
{
    KeyType key;
    ValType value;
    Stat stat;
}HashElem;

typedef struct HashTable
{
    HashElem data[HashMaxSize];
    size_t size;
    HashFunc func;//函数指针,指向了hash函数
}HashTable;

插入

1.使用哈希函数找到待插入元素在哈希表中的位置
2.如果该位置没有元素(即状态不是有效的)则直接插入新元素;如果该位置有元素且和待插入元素相等,则不用插入;如果该位置有元素但是和待插入元素不相等,发生哈希冲突,线性探测找到下一个空位置,将元素插入。

代码如下:

void HashInsert(HashTable* ht,KeyType key,ValType value)
{
    if(ht == NULL)
    {
        return;//非法输入
    }
    //1.判定hash表是否能继续插入(根据负载因子来判断)
    //这里我们把负载因子定为0.8
    if(ht->size >=0.8 * HashMaxSize)
    {
        //当前的哈希表已经达到负载因子的上限了
        return;//插入失败
    }
    //2.如果能继续插入,根据key来计算offset
    size_t offset = ht->func(key);
    //3.从offet位置开始向后线性探测,找到第一个状态为Empty的元素进行插入
    while(1)
    {
        if(ht->data[offset].stat != Valid)
        {
            //找到了一个合适位置插入元素
            ht->data[offset].stat = Valid;
            ht->data[offset].key = key;
            ht->data[offset].value = value;
           //5.++size;
            ++ht->size;
            return;
        }
        else if(ht->data[offset].key == key && ht->data[offset].stat == Valid)
        {
          //4.如果发现了key相同的元素,插入失败
            return;
        }
        else
        {
            ++offset;
            if(offset >= HashMaxSize)
            {
                offset = 0;
            }
        }

    }
    return;
}

查找

1.根据哈希函数找到要查找元素在哈希表中的位置
2.如果该位置状态为空,说明不存在;如果该位置状态为有效且和待查找元素值一样,说明找到了;如果该位置状态有效但和待查找元素值不一致,继续线性的向后探测,当把哈希表遍历完后还是没找到,说明该元素不存在。

代码:

int HashFind(HashTable* ht,KeyType key,ValType* value)
{
    if(ht == NULL || value == NULL)
    {
        return 0;
    }
    //1.根据key计算出offse
    size_t offset = ht->func(key);
    //2.从offset开始向后查找,每找到一个元素,比较其key值和要查找的key是否相同
    while(1)
    {
        if(ht->data[offset].key == key && ht->data[offset].stat == Valid)
        {
           //3.如果相同,返回其value,查找成功
            *value = ht->data[offset].value;
            return 1;
        }
        else if(ht->data[offset].stat == Empty)
        {
            //4.如果当前是一个空元素,则查找失败
            return 0;
        }
        else
        {
          //4.如果不相同,继续向后进行查找
            offset++;
            if(offset >= HashMaxSize)
            {
                offset = 0;
            }
        }

    }
}

删除

1.根据哈希函数找到要删除元素在哈希表中的位置
2.如果该位置有效且和待删除元素值一样,直接删除;如果该位置状态为空,说明不存在,删除失败;如果该位置有效但和要删除元素值不一致,继续线性的向后探测。

代码:


void HashRemove(HashTable* ht,KeyType key)
{
    if(ht == NULL)
    {
        return;//非法输入
    }
    if(ht->size == 0)
    {
        return;//哈希表为空
    }
    //1.根据key确定offset
    size_t offset = ht->func(key);
    //2.从offset开始,判断当前元素的key是否和要删除元素的Key值相同
    while(1)
    {
         //   a)如果相同,直接删除
        if(ht->data[offset].key == key && ht->data[offset].stat == Valid)
        {
            ht->data[offset].stat = Deleted;
            --ht->size;
            return;

        }
        //   b)如果当前元素状态为空,说明没找到,直接返回
        else if(ht->data[offset].stat == Empty)
        {
            return;//该元素没有在哈希表中
        }
        else
        {
        //   c)如果不同,线性地继续向后查找
            offset++;
            if(offset > HashMaxSize)
            {
                offset = 0;
            }
        }
    }//循环结束
    return;
}

猜你喜欢

转载自blog.csdn.net/y6_xiamo/article/details/80504695