哈希表(哈希函数和处理哈希冲突)_20230528

news2024/11/25 16:28:46

哈希表(哈希函数和处理哈希冲突)

  1. 前言

关于哈希表的主题的小记原计划5月23日完成,由于本人新冠阳性,身体发烧乏力,周末感觉身体状况稍加恢复,赶紧打开电脑把本文完成,特别秉承“写是为了更好地思考,更好地思考才能取得更大进步”的思想,不敢懈怠,赶紧把学习到的东西记录下来。

首先我们要搞明白为什么要发明哈希表的技术?我们学习和讨论过各种结构(线性表、树、图等),数据记录在结构中的相对位置是随机的,记录和关键字之间不存在确定的关系,因此,在查找记录时,需进行和关键字进行一系列的比较。查找效率依赖于查找过程中所比较的次数。哈希表的发表就是希望不经过任何比较,一次存取便能得到查找记录,可以大大提升查找、插入、删除等基本操作的效率。

要实现哈希表的技术思想,我们必须在储存位置和它的关键字之间建立一个确定的对应关系或对应函数f,使每个关键字和数据结构的为转移储存位置相对应。因此在查找关键字key的时候,我们只需调取函数f(k),计算出数据的储存位置,若结构中存在的关键字和key相等,那么必定在f(key)的位置上。因此不需要进行任何比较便可直接取得查询的记录。在此我们称这个对应关系为哈希函数(Hash function),按这个思想建立的表称为哈希表(Hash table)。

  1. 哈希函数

2.1 哈希冲突

由于哈数是一类影像函数,那么它就比不可避免出现这样一类情况,原始的关键字key_m和key_n并不相等,但是经过哈希函数处理之后f(key_m)=f(key_n),这种情况下就出现所谓的“哈希冲突”。比如我们有5个关键字k1…k5,经过哈希函数h(x)影像之后,h(k2)=h(k5),那么就说k2和k5在h(x)哈希函数影像下出现冲突。

在这里插入图片描述

实际应用当中,只能减少哈希冲突的概率,而没有办法杜绝哈希冲突,因为哈希实际上是对关键字进行了某种程度信息上的压缩,导致的后果就是压缩的信息可能相同,需要再次进行区分。

2.2 哈希函数

构造哈希函数的方法有很多,在介绍各种方法之前,首先需要明确什么是好的哈希函数。若对关键字集合当中的任何一个关键字,经过哈希函数映射到地址中的任意地址的概率是相等的,则称作此类哈希函数是均匀的哈希函数。换句话手,就是经过哈希函数映射后,得到一个随机地址,以便使一组关键字的哈希地址分布在整个区间中,从而减少冲突。

2.3 构造哈希函数的常用方法

a) 直接地址法

取关键字或关键字某个线性函数的值为哈希地址,
H ( K e y ) = a ∗ k e y + b H(Key)=a*key+b H(Key)=akey+b
由于直接定址所得关键字地址和集合相同,对于不同的关键字不会发生哈希冲突,但在实际应用中,使用这种哈希哈数非常少。

b) 数字分析法

假设关键字都是以10为基的数,并且哈希表中的关键字都是事先知道的,则可以取关键字的若干位组成哈希地址。

c) 平方取中法

取关键字平方后的中间几位为哈希地址。这是一种较常用的构造哈希函数的方法。通常在选定哈希哈数的时候不一定能知道关键字的全部情况,取其中哪几位也不一定合适,而一个数字平方后的中间几位和数的每一位都相关,由此随机分布的关键字的哈希地址也是随机的。

d) 折叠法

将关键字分为位数相同的几部分,然后取这几个部分的叠加和作为哈希地址,这个方法称为折叠法(folding)。关键字位数很多,而且关键字每一位上数字分布大致均匀时,可以采用折叠法得到哈希地址。

e)除留余数法

取关键字被某个不大于表长m的数p除后所得余数为哈希地址,也即是说,
H ( k e y ) = k e y   M O D   p    ( p ≤ m ) H(key)=key\ MOD\ p\ \ (p≤m) H(key)=key MOD p  (pm)
这是最简单,最常用的构造哈希函数的方法,不仅对关键字可以直接取模,也可在折叠,平方取中后进行取模处理。

  1. 哈希冲突处理方法

“好”的哈希函数可以减少哈希冲突概率,但不能避免,因此,如何处理哈希冲突是哈希造表不可缺少的一个方面。通常处理哈希冲突有下列几种方法:

3.1 开放定址法
H i = ( H ( k e y ) + d i )   M O D   m     ( i = 1 , 2 , . . . k ) , k < = ( m − 1 ) H_i=(H(key)+d_i)\ MOD\ m\ \ \ (i=1,2,...k), k<=(m-1) Hi=(H(key)+di) MOD m   (i=1,2,...k),k<=(m1)
H(key)为哈希函数,m为哈希表长,di为增量序列,可用下列三种取法:

(1)di=1,2,3,…m-1, 线性探测

(2) di= ±12,±22,…±k2 称为二次探测再散列

(3) di= 伪随机序列

3.2 链地址法

将所有关键字为同义词的记录存储在同一线性表中。假定某哈希函数产生的哈希地址在区间[0,m-1]上,则设立一个指针型向量

Chain ChainHash[m]

其每个分量的初始地址都是空指针,凡是哈希地址为i的记录,都插入到头指针为ChainHash[i]的链表中。为了便于查找、插入和删除操作,需要保证后续链表按关键字有序。

  1. 哈希函数实现

本文采用链地址方法处理冲突,哈希函数采用最简单的除留余数法。

4.1 头文件

/**
 * @file hash_table.h
 * @author your name (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2023-05-23
 * 
 * @copyright Copyright (c) 2023
 * 
 */
#ifndef HASH_TABLE_H
#define HASH_TABLE_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <stdbool.h>
#include "../00_introduction/Status.h"

#define EQ(a,b) ((a)==(b))
#define LT(a,b) ((a)<(b))
#define LQ(a,b) ((a)<=(b))

#define MAX_LEN 20

#define m 13    //采用除留余数法,除数定义为13


typedef int    KeyType;
typedef char*  Record;


typedef struct  ElemType
{
    KeyType key;
    Record  value;
}ElemType;

typedef struct HashNode
{
    KeyType key;
    struct HashNode *next;
} HashNode, *HashPtr;

typedef enum NodeType
{
    head,
    intermediate
}NodeType;

typedef struct Result
{
    NodeType type;
    HashPtr *node_ptr;  //待返回的节点
    HashPtr *node_ptr_2; //待返回节点的前一节点,主要用于删除操作
    int      flag; // 0= search failure, 1 = search success
} Result;


typedef struct HashTable
{
    ElemType   *elem;        //Use the dynamic programming to allocate the space
    int        count;        //Number of element in the current hashtable
    int        size_index;   //Capacity of hash table
} HashTable;

typedef struct SSTable
{
    ElemType *elem;
    int len;
} SSTable;


/**
 * @brief Create a static table
 * 
 * @param fp File to pointer
 * @param st Static table
 */
void create_table(FILE *fp, SSTable *st);

/**
 * @brief Intialize the HashPtr as Null pointer
 *
 * @param hash_chain
 * @param m Number of pointer of hash
 */
void init_hash(HashPtr *hash_chain);



/**
 * @brief Use hash function to map to the address
 * 采用取余的方法
 * @param key Key value
 * @return int Return hash address
 */
int hash_function(KeyType key);




/**
 * @brief search 'key' from the hash table
 * 
 * @param ht Hash table variable
 * @param key Key value
 * @param p   Position of key value
 * @param c   Number of collision in the search
 * @return Result 
 */
Result search_hash(HashPtr *hash, KeyType key);



/**
 * @brief Insert one element into the hashtable
 *
 * @param hash Pointer to hash table
 * @param key Element type
 * @return Status -Return success or unsuccess
 */
Status insert_hash(HashPtr *hash, KeyType key);



/**
 * @brief Insert one element into the hashtable
 *
 * @param hash Pointer to hash table
 * @param key Element type
 * @return Status -Return success or unsuccess
 */
Status delete_hash(HashPtr *hash, KeyType key);


#endif

4.2 函数实现

/**
 * @file hash_table.c
 * @author your name (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2023-05-23
 * 
 * @copyright Copyright (c) 2023
 * 
 */
#ifndef HASH_TABLE_C
#define HASH_TABLE_C
#include "hash_table.h"

void create_table(FILE *fp, SSTable *st)
{
    int n;
    char str[MAX_LEN];
    int i;


    n=0;
    // 当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
    while(fgets(str,MAX_LEN,fp)!=NULL)
    {
        n++;
    }

    fseek(fp,0,SEEK_SET);

    st->len=n;
    st->elem=(ElemType *)malloc(sizeof(ElemType)*(n+1));

    for(i=1;i<=n;i++)
    {
        st->elem[i].value=(Record)malloc(sizeof(char)*MAX_LEN);
        memset(st->elem[i].value,0,sizeof(char)*MAX_LEN);
        fscanf(fp,"%d %s",&(st->elem[i].key),st->elem[i].value);
    }

    return;    
}

void init_hash(HashPtr *hash_chain)
{
    int i;

    for(i=0;i<m;i++)
    {
        *(hash_chain+i)=NULL;
    }

    return;
}

int hash_function(KeyType key)
{
    return (key%m);
}

Result search_hash(HashPtr *hash, KeyType key)
{
    Result res;
    int  k;
    HashPtr *p;
    HashPtr *pre_p;
    bool termination=false;
    bool found =false;

    p = (HashPtr *)malloc(sizeof(HashPtr));
    pre_p = (HashPtr *)malloc(sizeof(HashPtr));

    k=hash_function(key);

    if(hash[k]==NULL || ((hash[k]!=NULL) && key<= hash[k]->key))
    {
        res.flag=0;
        res.type=head;
        res.node_ptr=hash+k;

        if ((hash[k] != NULL) && key == hash[k]->key)
        {
            res.flag=1;
        }

        return res;
    }

    *p=hash[k];
    *pre_p=NULL;

    while ((*p) && !termination)
    {
       if((*p)->key==key)
       {
           termination = true;
           found =true;
       }
       else if (key < (*p)->key)
       {
          termination =true;
       }
       else
       {
            *pre_p=*p;
            *p=(*p)->next;
       }
    }
    
    if(found)
    {
        res.flag=1;
        res.type=intermediate;
        res.node_ptr=p;
        res.node_ptr_2=pre_p;

        return res;
    }
    else
    {
        res.flag = 0;
        res.type = intermediate;
        res.node_ptr = pre_p;
        res.node_ptr_2=p;

        return res;
    }
}

Status insert_hash(HashPtr *hash, KeyType key)
{
    Result res;
    HashPtr new_node;
    HashPtr temp;
    res=search_hash(hash,key);

    if(res.flag==1)
    {
        return ERROR;
    }
    else
    {
        new_node=(HashPtr)malloc(sizeof(HashNode));
        new_node->key=key;
        new_node->next=NULL;

        if(res.type==head)
        {
            new_node->next=*(res.node_ptr);
            *(res.node_ptr)=new_node;
        }
        else
        {
            new_node->next = (*(res.node_ptr))->next;
            (*(res.node_ptr))->next = new_node;
        }
    }

    return OK;
}

Status delete_hash(HashPtr *hash, KeyType key)
{
    Result res;
    HashPtr temp;
    res = search_hash(hash, key);

    if (res.flag == 0)
    {
        return ERROR;
    }
    else
    {
        if (res.type == head)
        {
            (*(res.node_ptr)) = (*(res.node_ptr))->next;
        }
        else The previous next will be the current next
        {
            (*(res.node_ptr_2))->next = (*(res.node_ptr))->next; 
        }
    }
}

#endif

4.3测试函数

/**
 * @file hash_table_main.c
 * @author your name (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2023-05-21
 * 
 * @copyright Copyright (c) 2023
 * 
 */
#ifndef HASH_TABLE_MAIN_C
#define HASH_TABLE_MAIN_C
#include "hash_table.c"

int main(void)
{
    int i;
    FILE *fp;
    SSTable st;
    HashPtr hash[m];
    


    fp=fopen("data.txt","r");
    create_table(fp,&st);
    init_hash(hash);

    for(i=1;i<=st.len;i++)
    {
        insert_hash(hash,st.elem[i].key);
    }

    delete_hash(hash,14);
    
    printf("This is the end of test\n");
    getchar();
    fclose(fp);
    return EXIT_SUCCESS;
}


#endif

  1. 小结

本文对哈希表、哈希函数以及处理哈希冲突的方法进行总结,并且利用C语言对实现了简单的哈希插入、查找和删除操作。

参考资料:

《数据结构》清华大学,严蔚敏

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/578775.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

搜索算法总结

文章目录 搜索算法1. 深度优先搜索&#xff08;Depth-First-Search, DFS&#xff09;2. 广度优先搜索&#xff08;Breadth-first search, BFS&#xff09;3. 启发式搜索策略3.1 爬山法&#xff08;Hill climbing&#xff09;3.2 最佳优先搜索&#xff08;Best-first search&…

【嵌入式环境下linux内核及驱动学习笔记-(13-中断管理)】

目录 1、中断基本概念2、ARM体系中断系统2.1 ARM具有的七种异常模式与中断的关系2.2 ARM多核环境下的中断2.3 exynos4412(contex A9)的中断 3、中断处理程序架构4、 中断接口编程4.1 中断接口函数4.1.1 request_irq4.1.2 free_irq4.1.3 irqreturn_t4.1.4 irq_handler_t 中断处理…

C语言初阶之函数介绍及练习

函数介绍及练习 1.函数是什么&#xff1f;2.C语言中函数的分类&#xff1a;2.1 库函数2.2 自定义函数 3. 函数的参数3.1 实际参数&#xff08;实参&#xff09;&#xff1a;3.2 形式参数&#xff08;形参&#xff09;&#xff1a; 4.函数的调用4.1 传值调用4.2 传址调用 5. 函数…

真相只有一个——谁是凶手

谁是凶手 1.题目描述2. 解题思路3.代码展示 所属专栏&#xff1a;脑筋急转弯❤️ &#x1f680; >博主首页&#xff1a;初阳785❤️ &#x1f680; >代码托管&#xff1a;chuyang785❤️ &#x1f680; >感谢大家的支持&#xff0c;您的点赞和关注是对我最大的支持&am…

漫游计算机系统

1.信息就是位 上下文 那么什么是信息呢&#xff1f; 在计算机系统中&#xff0c;所有的信息——包括磁盘文件、内存中的程序、内存中存放的用户数据以及网络上传送的数据。本质上是一串比特位。 那么又要了解什么是比特了&#xff0c;比特&#xff08;bit)就是二进制&#xff…

基于标准库函数的STM32的freertos的移植(一)——github源码压缩包下载

由于freertos官网将freertos内核与freertos工程分别进行版本管理&#xff0c;因此下载freertos需要将参考工程和内核分别下载。由于采用ST公司提供的标准库函数进行因此还需要下载标准库函数&#xff0c;然后进行移植配置。具体流程如下详细描述&#xff1a; 1.首先在github的…

git Husky

虽然我们已经要求项目使用eslint了&#xff0c;但是不能保证组员提交代码之前都将eslint中的问题解决掉了&#xff1a; 也就是我们希望保证代码仓库中的代码都是符合eslint规范的&#xff1b; 那么我们需要在组员执行 git commit 命令的时候对其进行校验&#xff0c;如果不符合…

centos7安装docker 并创建mysql

Docker 分为 CE 和 EE 两大版本。CE 即社区版&#xff08;免费&#xff0c;支持周期 7 个月&#xff09;&#xff0c;EE 即企业版&#xff0c;强调安全&#xff0c;付费使用&#xff0c;支持周期 24 个月。 Docker CE 分为 stable test 和 nightly 三个更新频道。 官方网站上有…

关于强电与弱的的介绍

强电&#xff1f;弱电&#xff1f;傻傻分不清楚&#xff0c;今天海翎光电的小编为大家系统的介绍一下强电与弱电。 什么是强电&#xff1f; &#xff08;1&#xff09;供配电系统&#xff1a;供配电系统包括负荷分级、供电措施、负荷力矩、电网谐波限值、用电指标、负荷所需要…

MySQL数据库修改root账户密码

博主今天登录数据库遇到了一个问题&#xff0c;通过这篇文章&#xff08;http://t.csdn.cn/58ECT&#xff09;解决了。文中关于修改root账户密码的部分&#xff0c;博主觉得有必要写一篇文章总结下。 第一步&#xff1a;用管理员账户打开CMD 第二步&#xff1a;开启mysql服务 …

dubbo源码阅读: dubbo的xml文件如何解析的?

dubbo源码阅读&#xff1a; dubbo的xml文件如何解析的&#xff1f; DubboNamespaceHandlerspring 的接口 NamespaceHandlerspring 的抽象类 NamespaceHandlerSupport学以致用 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns:xsi"http…

征文 | 吸引铁粉?成为CSDN明星!

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; 征文 | 吸引铁粉&#xff1f;成为CSDN明星&#xff01; 导读 当今数字时代&#xff0c;社交媒体和在线社区成为了人们交流和分享的主要平台之一&#xff0c;CSDN就是其…

前沿重器[34] | Prompt设计——LLMs落地的版本答案

前沿重器 栏目主要给大家分享各种大厂、顶会的论文和分享&#xff0c;从中抽取关键精华的部分和大家分享&#xff0c;和大家一起把握前沿技术。具体介绍&#xff1a;仓颉专项&#xff1a;飞机大炮我都会&#xff0c;利器心法我还有。&#xff08;算起来&#xff0c;专项启动已经…

31 Vue 表单输入绑定的实现

前言 这是最近的碰到的那个 和响应式相关的问题 特定的操作之后响应式对象不“响应“了 引起的一系列的文章 主要记录的是 vue 的相关实现机制 呵呵 理解本文需要 vue 的使用基础, js 的使用基础 测试用例 测试用例如下, 一个简单的 v-model 的使用 问题的调试 这里 …

尝试在UNet的不同位置添加SE模块

目录 &#xff08;1&#xff09;se-unet01&#xff08;在卷积后&#xff0c;下采样前&#xff0c;添加SE模块&#xff09; &#xff08;2&#xff09;se-unet02&#xff08;在卷积后&#xff0c;上采样前&#xff0c;添加SE模块&#xff09; &#xff08;3&#xff09;se-un…

Qt基础之三十六:异常处理

本文将介绍如何在Qt中使用try...catch和调试dump文件来处理异常。 Qt版本5.12.6 一.使用try...catch 一段简单的捕获异常的代码,新建一个控制台工程,pro文件不用修改 #include <QCoreApplication> #include <QDebug>int main(int argc, char *argv[]) {QCoreA…

历届蓝桥杯青少年编程比赛 计算思维题真题解析【已更新5套 持续更新中】

一、计算思维组考试范围 计算思维组面向小学生&#xff08;7-12 岁&#xff0c;约 1-6 年级&#xff09;&#xff0c;通过设计多个角度的考核题目、层次科学的试卷组合、线上限时的考试形式&#xff0c;更加精确地考查学生的计算能力、反应能力、思维与分析能力&#xff0c;使…

注解实现自动装配

要使用注解须知&#xff1a; 1.导入约束 context约束 2.配置注解的支持 官方配置文件 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/…

Flume实践

1 NetCat方式 ]# ./bin/flume-ng agent --conf conf--conf-file ./conf/flume_netcat.conf --name a1 -Dflume.root.loggerINFO,console [rootmaster ~]# yum -y intalll telnet 发数据&#xff1a; ]# telnet master 44444 数据接收&#xff0c;是在终端上接收的&#xff0…

[创业之路-70] :聊天的最高境界因场景不同而不同

一、聊天的最高境界因场景不同而不同。 销售式聊天&#xff1a; 聊天的最高境界不是真相&#xff0c;而是拨动对方的心弦&#xff0c;让对方心理爽&#xff0c;让对方舒心。聊天的结果是你买单。 消费式聊天&#xff1a; 聊天的最高境界不是真相&#xff0c;而是让自己心理爽…