数据结构之哈希表以及常用哈希的算法表达(含全部代码)

简介: 数据结构之哈希表以及常用哈希的算法表达(含全部代码)

目录


为什么要有哈希

哈希表

含义

创建哈希表需要注意的点

算法的选择

哈希冲突的处理

线性探测法

再哈希法

链表法

哈希表的实现(代码部分)

确定结构体(节点)

准备一个哈希算法

创建一个哈希表(即开辟空间)

创建节点

数据加入哈希表的具体实现

获取数据,数据加入哈希表

打印哈希表

查找哈希表(重点,这也能帮你理解哈希表的结构与优势所在)

销毁哈希表

主函数


正文


为什么要有哈希


如果要存储一组数据,我们都能想到使用数组或者链表来存储,但是实际生产生活中,数据都是非常庞大的,动辄几十G,几十T,甚至以P来计算。这样一来,数组和链表的优势以劣势就非常明显了。

插入数据 查找数据
数组 劣势 优势
链表 优势 劣势

既然这两种方法都不能完美解决实际问题,那就一定会有一种方法的诞生来处理它,我们的哈希表就这样诞生啦,哈希表集这两种存储形式的优点于一身,接下来就让我们走进哈希表吧。


哈希表


含义


哈希表也叫散列表,哈希表是一种数据结构,它提供了快速的插入操作和查找操作,无论哈希表总中有多少条数据,在有足够优秀的哈希算法时,插入和查找的时间复杂度都是为O(1)。


散列表 ( Hash table  ),是根据键(Key)而直接访问在记忆体储存位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表 。表中存储的是一个地址,我们通过键值计算出来的哈希值得到这个地址,再取这个地址中的数据即可。


创建哈希表需要注意的点


算法的选择


常见的算法形式有,根据键值进行加法运算,减法运算,乘法运算,位移运算。


值得一说的是位移运算,无论是前移还是后移, 都会使键值转换出来的哈希值大小难以确定,可能有键值很大,但是哈希值很小的情况,这就让使用者无法大致确定键值对应的哈希值。但这并不是缺点,看实际生产要求。


而一般都是以上几种运算的复合运算作为哈希算法。例如;

hashvalue = (hashvalue << 4) + hashvalue + *key++;
//hashvalue为哈希值,key为键值


哈希冲突的处理


哈希冲突即为不同键值通过哈希算法得到了相同的哈希值,这种情况,我们肯定不能将数据直接存储到对应的地址去,这样会将原来的数据覆盖。对此常见的 方法有一下三种,我们最推荐使用链表的方式。


线性探测法


在线性探测哈希表中,数据的插入是线性的查找空白单元,例如我们将数88经过哈希函数后得到的数组下标是16,但是在数组下标为16的地方已经存在元素,那么就找17,17还存在元素就找18,一直往下找,直到找到空白地方存放元素。


再哈希法


双哈希是为了消除原始聚集和二次聚集问题,线性探测每次的探测步长都是固定的。双哈希是除了第一个哈希函数外再增加一个哈希函数用来根据关键字生成探测步长,这样即使第一个哈希函数映射到了数组的同一下标,但是探测步长不一样,这样就能够解决聚集的问题。

和第一个哈希函数不一样

不能输出为0,因为步长为0,每次探测都是指向同一个位置,将进入死循环,经过试验得出stepSize = constant-(key%constant);形式的哈希函数效果非常好,constant是一个质数并且小于数组容量


链表法


通过在哈希表中再寻找一个空位解决冲突的问题,还有一种更加常用的办法是使用「链表法」来解决哈希冲突。「链表法」相对简单很多,「链表法」是每个数组对应一条链表。当某项关键字通过哈希后落到哈希表中的某个位置,把该条数据添加到链表中,其他同样映射到这个位置的数据项也只需要添加到链表中,并不需要在原始数组中寻找空位来存储。

345.png


哈希表的实现(代码部分)


确定结构体(节点)


#define HASHSIZE 256 //你的哈希表的元素个数
typedef int mydatatype;
typedef unsigned char uint8;
struct node
{
  //char * key;//键值,最好使用指针,这里为了便捷,使用数组
  char key[128];
  mydatatype data;//这个东西就是你的数据
  struct node * next;    //解决哈希冲突的链表法
};


准备一个哈希算法


/* 
GetHashValue:获取哈希值
  @key:输入的键值
  返回值:
    输入的键值对应的哈希值
 */
uint8 GetHashValue(const char * key)
{
  uint8 hashvalue = 0;
  while(*key)
  {
    hashvalue = (hashvalue << 4) + hashvalue + *key++;  //首先我们应该准备一个哈希算法
                                                            //你可能会要调试多次
  }
  printf("hashvalue = %d\n",hashvalue);
  return hashvalue; 
}


创建一个哈希表(即开辟空间)


/* 
GetHashTable: 得到一个哈希表
  返回值:
    哈希表所需的空间
 */
struct node ** GetHashTable(void)
{
            //这个空间开出来就是为了保存地址的  
            //因此sizeof(struct node *)
  return (struct node **)calloc(sizeof(struct node *) , HASHSIZE);    //calloc会初始化空间为0
}


创建节点


/* 
GetDataNode: 创建节点的函数
  @key:输入的键值
  @data:键值对应的数据
  返回值:
    哈希表的一个节点
 */
static struct node * GetDataNode(const char * key,mydatatype data)  //const关键字对变量加以限定,使它不能被改变
{
  struct node * pnew = malloc(sizeof(struct node));
    strcpy(pnew ->key,key);   //注意,这里不能直接写pnew->key = key;
    pnew ->data = data;
    pmew ->next = NULL;    
  return pnew;
}


数据加入哈希表的具体实现


/* 
InsertDataToHashTable:将数据加入到哈希表
  @ht:哈希表
  @key:键值
  @date:键值对应的数值
 */
static void InsertDataToHashTable(struct node ** ht,const char * key,mydatatype data)
{
    struct node *pnew = GetDataNode(key,data);
    //计算哈希值
    uint8 hashvalue = GethashValue(key);
    if(!ht[hashvalue])  //如果哈希表的下标为hashvalue的元素为空 
              //则这个地方是没有填入数据的 直接填入就可以了
    {
        ht[hashvalue]) = pnew;
    }
    else
    {
        //如果有hashvlaue与key值d都相同的情况 
    //一般有两种做法  1 直接丢弃不用   2 加上一个num计数
    //一般我们用尾插法插入
        struct node * p = ht[hashvalue];
        while(p)
        {
            if(!strcmp(p ->key,key))
            {
                free(pnew);
                printf("键值一样\n");
                return;
            }
            p = p->next;
        }
        p = pnew;    //p已经是最后一个节点的next
    }
}


获取数据,数据加入哈希表


/* 
GetData:获取数据,加入到哈希表中
  @ht:哈希表
 */
void GetData(struct node ** ht)
{
    if(!ht)
        return;
    mydatatype data;
    char key[128] = {0};
    while(1)
    {
        //先输入键值,再输入data
        if(scanf("%s%d",key,data) != 2)//你匹配上的东西不等于2  说明你的匹配失败
        {
            char ha[1024];
            fgets(ha,1024,stdin);    //将标准输入里面的东西拿掉
            printf("输入有误,请重新输入\n");
`            continue;
        }    
        if(!strncmp(key,"##",2))    //输入“##”结束输入
            break;
        InsertDataToHashTable(ht,key,data);
    }
}


打印哈希表


/* 
PrintHashTable:打印这个哈希表
  @ht:哈希表
 */
void PrintHashTable(struct node ** ht)
{
    int i;
    for(i = 0;i < HASHSIZE;i++)
    {
        if(ht[i])
        {
            struct node *p = hr[i];
            while(p)
            {
                printf("HV = %d\tkey = %s\tdata = %d\n",i,p->key,p->data);
                p = p ->next;
            }
        }
    }   
}


查找哈希表(重点,这也能帮你理解哈希表的结构与优势所在)


/* 
FindData:查找哈希表
  @ht:哈希表
  @key:键值
 */
void FindData(struct node ** ht,const char * key)
{
    uint8 hashvalue = GetHashValue(key);
    if(ht[hashvalue])    //哈希表存在
    {
        struct node * p = ht[hashvalue];
        int i = 1;
        while(p)
        {
            if(strcmp(p ->key,key)
            {
                printf("找到了,键值为%d中的第%d个,value = %d\n",hashvalue,i,p->data);
                return;
            }
            i++;
            p = p ->next;
        }
    }
    printf("查找的元素%s不存在\n",key);
}


销毁哈希表


/* 
DestoryHashTable:销毁这个哈希表
  @ht:哈希表
 */
void DestoryHashTable(struct node ** ht)
{
    int i = 0; 
    for(i = 0;i < HASHSIZE;i++)
    {
        struct node * p = ht[i];
        struct node * pre = NULL;
        while(!ht[i])
        {
            if(p ->next)
            {
                free(p);
                pre ->next = NULL;
                p = ht[i];
            }
            else
            {
                pre = p;
                p = p ->next;
            }
        }
    }
}


主函数


int main()
{
  struct node ** ht = GetHashTable();
  GetData(ht);
  PrintHashTable(ht);
  printf("输入你要查找的键值:");
  char buf[128] = {0};
  scanf("%s",buf);
  FindData(ht,buf);
  DestoryHashTable(ht);
  return 0;
}
相关文章
|
17天前
|
算法 安全
散列值使用相同的哈希算法
当使用相同的哈希算法对相同的数据进行散列时,所产生的散列值(也称为哈希值或摘要)总是相同的。这是因为哈希算法是一种确定性的函数,它对于给定的输入将始终产生相同的输出。 例如,如果你用SHA-256算法对字符串"hello world"进行哈希处理,无论何时何地,只要输入是完全一样的字符串,你都会得到相同的160位(40个十六进制字符)的SHA-256散列值。 但是,需要注意的是,即使是输入数据的微小变化也会导致产生的散列值完全不同。此外,不同的哈希算法(如MD5、SHA-1、SHA-256等)会对相同的数据产生不同的散列值。 哈希算法的一个关键特性是它们的“雪崩效应”,即输入中的一点小小
27 4
|
21天前
|
存储 算法 程序员
C 语言递归算法:以简洁代码驾驭复杂逻辑
C语言递归算法简介:通过简洁的代码实现复杂的逻辑处理,递归函数自我调用解决分层问题,高效而优雅。适用于树形结构遍历、数学计算等领域。
|
21天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
50 1
|
1月前
|
存储 缓存 算法
通过优化算法和代码结构来提升易语言程序的执行效率
通过优化算法和代码结构来提升易语言程序的执行效率
|
1月前
|
算法
分享一些提高二叉树遍历算法效率的代码示例
这只是简单的示例代码,实际应用中可能还需要根据具体需求进行更多的优化和处理。你可以根据自己的需求对代码进行修改和扩展。
|
1月前
|
算法 测试技术 开发者
在Python开发中,性能优化和代码审查至关重要。性能优化通过改进代码结构和算法提高程序运行速度,减少资源消耗
在Python开发中,性能优化和代码审查至关重要。性能优化通过改进代码结构和算法提高程序运行速度,减少资源消耗;代码审查通过检查源代码发现潜在问题,提高代码质量和团队协作效率。本文介绍了一些实用的技巧和工具,帮助开发者提升开发效率。
41 3
|
1月前
|
分布式计算 Java 开发工具
阿里云MaxCompute-XGBoost on Spark 极限梯度提升算法的分布式训练与模型持久化oss的实现与代码浅析
本文介绍了XGBoost在MaxCompute+OSS架构下模型持久化遇到的问题及其解决方案。首先简要介绍了XGBoost的特点和应用场景,随后详细描述了客户在将XGBoost on Spark任务从HDFS迁移到OSS时遇到的异常情况。通过分析异常堆栈和源代码,发现使用的`nativeBooster.saveModel`方法不支持OSS路径,而使用`write.overwrite().save`方法则能成功保存模型。最后提供了完整的Scala代码示例、Maven配置和提交命令,帮助用户顺利迁移模型存储路径。
|
2月前
|
存储 Java 开发者
Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效
【10月更文挑战第19天】在软件开发中,随着项目复杂度的增加,数据结构的组织和管理变得至关重要。Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,帮助开发者告别混乱,提升代码质量。
32 1
|
2月前
|
缓存 分布式计算 监控
优化算法和代码需要注意什么
【10月更文挑战第20天】优化算法和代码需要注意什么
23 0
|
16天前
|
算法
基于WOA算法的SVDD参数寻优matlab仿真
该程序利用鲸鱼优化算法(WOA)对支持向量数据描述(SVDD)模型的参数进行优化,以提高数据分类的准确性。通过MATLAB2022A实现,展示了不同信噪比(SNR)下模型的分类误差。WOA通过模拟鲸鱼捕食行为,动态调整SVDD参数,如惩罚因子C和核函数参数γ,以寻找最优参数组合,增强模型的鲁棒性和泛化能力。
下一篇
DataWorks