【C语言】散列表(哈希表)

哈希表

  • 在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应的存储位置f(key)。查找时,根据这个确定的对应关系找到给定值key的映射f(key),若查找集合中存在这个记录,则必定在f(key)的位置上。这种对应关系f称为散列函数(哈希函数)。采用散列技术将记录存储在一块连续的存储空间中,这种连续存储空间称为散列表(哈希表)。
  • 由于key的范围是接近无限的,而哈希表存储空间是有限的,因此难以避免的会碰到两个不同的关键字,经哈希函数后却获得相同的f(key),这种现象称为冲突,将这两个关键字叫做同义词。

构建方法

基本原则:

  • 应满足计算简单,哈希函数的计算时间不应该超过其他查找技术与关键字比较的时间。
  • 尽量让散列地址均匀地分布在存储空间中,这样可以保证存储空间的有效利用,并减少为处理冲突而耗费的时间。

直接定址法

取关键字的某个线性函数值为散列地址,即f(key)=a*key+b
此方法简单、均匀,且不会产生冲突,但需要事先知道关键字的分布情况,适合查找表较小且连续的情况。

数字分析法

抽取关键字中的部分来计算散列存储位置。
可对抽取出的数字进行反转(123变321)、右环位移(123变312)、左环位移、甚至前两位数与后两位数相加(1234变12+34=46)等。

平方取中法

对关键字(1234)进行平方(1522756),再抽取中间的3位,即227。

折叠法

将关键字从左到右分割成位数相等的几部分(最有一位数不够时可以短些),然后将这几部分叠加求和,并按散列表表长取后几位作为散列地址。
关键字为9876543210,散列表表长为3。将关键字分为四组987|654|321|0,四部分叠加987+654+321+0=1962,再取后三位为962。

除留余数法

f(key)=key mod p (p<=m)
若散列表表长为m,通常p为小于或等于表长的最小质数或不包含小于20质因子的合数。

伪随机数法

f(key)=random(key)
当关键字长度不一致时,此方法比较合适。

处理散列冲突的方法

开放定址法

开放定址法就是一旦发生了冲突,就是寻找下一个空的散列地址,只要表足够大,空的散列地址总能找到,并记录。

  • 线性探测法:f(key)=(f(key) + di) mod m(di = 1,2,3,…,m-1)
    在解决冲突时,会碰到本来不是同义词却需要争夺同一地址的情况,这种现象叫做堆积。
  • 二次探测法:f(key)=(f(key) + di) mod m(di = 12, -12, 22, -22, …, q2, -q2) (q<=m/2)
    二次探测法就是跳跃式探测方法,效率较高,但是会出现有空间探测不到的情况,因而存储失败。而线性探测法只要有空间就一定能探测成功。
  • 随机探测法:f(key)=(f(key) + di) mod m(di为一个伪随机序列)

再散列函数法

事先准备多个散列函数,每当发生散列地址冲突时,就换一个散列函数计算。

链地址法

将所有的关键字为同义词的记录存储在一个单链表中,将这种表称为同义词子表。在散列表中只存储所有同义词子表的头指针。
链地址法提供了绝不会出现找不到地址的保障,但在查找时需要遍历单链表,会有性能损耗。

公共溢出区法

为所有的冲突关键字建立一个公共溢出区来存放。
在查找时,对给定值通过散列函数计算出散列地址后,先与基本表的相应位置进行比对,如果相等,则查找成功;如果不等,则到溢出表中进行顺序查找。

散列表的性能分析

如果没有冲突,查找的时间复杂度为o(1)。
装填因子α=填入表中的记录个数 / 散列表长度。
散列表的平均查找长度取决于装填因子,而不是查找集合中的记录个数。

代码实现

/*
 * @Author: Xyh4ng
 * @Date: 2022-11-28 20:15:29
 * @LastEditors: Xyh4ng
 * @LastEditTime: 2022-11-28 21:25:23
 * @Description:
 * Copyright (c) 2022 by Xyh4ng 503177404@qq.com, All Rights Reserved.
 */
#include <stdio.h>
#include <stdlib.h>

#define HASHSIZE 12
#define NULLKEY -32768

typedef struct HashTable
{
    int *data;
    int count;
} HashTable;

void InitHashTable(HashTable *H)
{
    H->count = HASHSIZE;
    H->data = (int *)malloc(sizeof(HashTable) * HASHSIZE);
    for (int i = 0; i < H->count; i++)
    {
        H->data[i] = NULLKEY;
    }
}

int HashFunc(int key)
{
    return key % HASHSIZE;
}

// 采用开放定址法构建哈希表,线性探索解决哈希冲突
void InsertHashTable(HashTable *H, int key)
{
    int addr = HashFunc(key);
    while (H->data[addr] != NULLKEY)
    {
        addr = (addr + 1) % HASHSIZE;
    }
    H->data[addr] = key;
}

int SearchHashTable(HashTable H, int key, int *addr)
{
    *addr = HashFunc(key);
    while (H.data[*addr] != key)
    {
        *addr = (*addr + 1) % HASHSIZE;
        if (H.data[*addr] == NULLKEY || *addr == HashFunc(key))
        {
            return 0;
        }
    }
    return 1;
}

int main()
{
    int a[HASHSIZE] = {12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34};
    HashTable H;
    InitHashTable(&H);
    for (int i = 0; i < HASHSIZE; i++)
    {
        InsertHashTable(&H, a[i]);
    }
    for (int i = 0; i < HASHSIZE; i++)
    {
        int addr;
        SearchHashTable(H, a[i], &addr);
        printf("查找 %d 的地址为:%d \n", a[i], addr);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值