哈希表和时间复杂度

哈希表   

(Hash Table),它通过哈希函数将键值映射到特定的数组索引,从而实现高效的查找、插入和删除操作。其核心思想是将数据直接存储到具有固定大小的数组中,通过哈希函数计算出每个数据的存储位置。

主要特性

  1. 哈希函数:哈希函数用于将输入的键(通常是字符串或数字)转换为数组中的索引。理想的哈希函数应尽量避免不同的键映射到同一索引(称为冲突)。

  2. 冲突处理

    • 开放地址法:在发生冲突时,通过线性探查、二次探查或双重哈希等方式,寻找数组中下一个空闲位置。
    • 链地址法:在每个哈希表的索引处,维护一个链表或其他容器来存储所有具有相同哈希值的元素。
  3. 时间复杂度

    • 查找、插入和删除的平均时间复杂度为 O(1)O(1)O(1),但在最坏情况下(大量冲突)可能退化为 O(n)O(n)O(n)。
  4. 负载因子:负载因子是哈希表中存储的元素数量与哈希表大小的比值。当负载因子过大时,哈希表的性能会下降,通常通过扩展哈希表(重新哈希)来解决。

哈希表的应用

  • 数据库索引
  • 缓存(如 LRU Cache)
  • 唯一性检测(如查找重复项)
  • 字典/映射实现

优缺点

  • 优点

    • 查找、插入和删除的平均时间复杂度为常数时间 O(1)O(1)O(1)。
    • 适合快速查找和存储大量数据。
  • 缺点

    • 当哈希函数设计不当或负载因子过大时,性能会急剧下降。
    • 存在内存浪费,特别是在使用开放地址法时,需要额外的存储空间。

哈希冲突

发生在两个不同的键通过哈希函数映射到相同的数组索引时。为了解决哈希冲突,常见的解决办法主要有以下两种:开放地址法链地址法,每种方法又有不同的变种和策略。

1. 链地址法(Separate Chaining)

链地址法是最常见的哈希冲突解决方案。它通过在哈希表的每个索引处维护一个链表(或其他容器),当多个键被映射到同一索引时,直接将它们放入这个链表中。

  • 优点
    • 简单直观,容易实现。
    • 不受表大小限制,可以处理超过哈希表容量的数据。
  • 缺点
    • 如果冲突过多,链表长度变长,查找效率会退化到 O(n)O(n)O(n)。
  • 改进方法
    • 使用自平衡二叉搜索树跳表代替链表,从而在冲突严重时保持较好的性能。

2. 开放地址法(Open Addressing)

开放地址法通过在哈希表中寻找下一个可用的空闲位置来存储冲突的元素。它不使用外部链表,而是在数组内部解决冲突。

2.1 线性探查(Linear Probing)

当发生冲突时,按照线性顺序(即每次向前移动一格)依次检查下一个位置,直到找到一个空闲的槽位。

  • 公式h(k, i) = (h(k) + i) % m,其中 i 表示冲突次数,m 为哈希表大小。

  • 优点

    • 实现简单。
    • 连续数据的访问具有较高的缓存命中率。
  • 缺点

    • 容易产生主堆积现象(primary clustering):即多个连续的空位被占用后,新的元素很容易探查到这些连续区域,进一步加剧冲突。

 算法时间复杂度


        执行这个算法所花时间的度量
        
        将数据量增长和时间增长用函数表示出来,这个函数就叫做时间复杂度。
        一般用大O表示法:O(n)-----时间复杂度是关于数据n的一个函数
        随着n的增加,时间复杂度增长较慢的算法时间复杂度低
    时间复杂度的计算规则
        1,用常数1 取代运行时间中的所有加法常数
        2,在修改后的运行函数中,只保留最高阶项。
        3,如果最高阶存在且系数不是1,则去除这个项相乘的常数。

哈希表相关操作的函数接口

#ifndef __HASH_H__  // 防止头文件重复包含,定义唯一的头文件保护符。
#define __HASH_H__#include <head.h>  // 包含标准或自定义的头文件,用于提供头文件的依赖。#define HASH_SIZE 27  // 定义哈希表的大小,这里使用 27 个槽位(26 个字母 + 1 个非字母字符槽位)。/*** @brief 定义存储的数据类型,每个哈希节点存储一个用户的姓名和电话。*/
typedef struct per
{char name[64];  // 用户的姓名,最多 64 个字符。char tel[32];   // 用户的电话号码,最多 32 个字符。
} HsDatetype;/*** @brief 定义哈希表节点,每个节点包含用户数据和指向下一个节点的指针(用于解决冲突时的链表)。*/
typedef struct hashnode
{HsDatetype data;        // 该节点存储的用户数据(姓名和电话)。struct hashnode *pnext; // 指向下一个节点的指针,用于处理哈希冲突(链表法)。
} Hsnode_t;/*** @brief 计算哈希值的函数,根据输入字符返回对应的哈希表索引。* @param key 输入的字符(通常是姓名的首字母)。* @return 返回计算得到的哈希表索引。*/
int hashfuction(char key);/*** @brief 向哈希表中插入一个数据。* @param data 要插入的用户数据(包含姓名和电话号码)。* @return 插入成功返回 0,失败返回 -1。*/
int insert_hatable(HsDatetype data);/*** @brief 遍历哈希表,输出所有存储的数据。* @return 成功返回 0。*/
int traverse_table();/*** @brief 查找哈希表中是否存在指定名字的用户数据。* @param name 要查找的名字。* @return 如果找到,返回指向该节点的指针;如果未找到,返回 NULL。*/
Hsnode_t *fine_table(char *name);/*** @brief 删除哈希表中指定名字的用户数据,并将删除的数据存储到指定指针中。* @param name 要删除的名字。* @param data 用于存储删除的数据的指针。* @return 成功删除返回 1,未找到返回 0。*/
int delete_hatable(char *name, HsDatetype *data);/*** @brief 删除哈希表中的所有数据,释放内存。* @return 成功返回 0。*/
int delete_table();#endif  // __HASH_H__ 结束头文件保护符。

 函数详细部分

#include "hash.h"  // 包含哈希表相关的头文件,定义了数据类型和常量(如 HASH_SIZE)。// 定义哈希表为全局变量,每个位置存储指向链表头节点的指针,初始化为NULL。
Hsnode_t *hashtable[HASH_SIZE] = {NULL};/*** @brief 哈希函数,将字符转换为哈希表的索引。* @param key 输入的字符(通常是姓名的首字母)。* @return 返回该字符在哈希表中的索引。*/
int hashfuction(char key)
{// 如果是小写字母,将其转换为从 0 开始的索引 ('a' -> 0, 'b' -> 1, ...)。if(key >= 'a' && key <= 'z'){return key - 'a';}// 如果是大写字母,也转换为从 0 开始的索引 ('A' -> 0, 'B' -> 1, ...)。else if(key >= 'A' && key <= 'Z'){return key - 'A';}// 如果不是字母字符,则返回哈希表的最后一个位置。else{return HASH_SIZE - 1;}
}/*** @brief 向哈希表中插入数据。* @param data 要插入的用户数据(包含姓名和电话信息)。* @return 成功返回 0,失败返回 -1。*/
int insert_hatable(HsDatetype data)
{// 根据名字的第一个字符计算哈希值,得到存储位置。int addr = hashfuction(data.name[0]);// 为新节点分配内存空间,存储数据。Hsnode_t *pnode = (Hsnode_t*)malloc(sizeof(Hsnode_t));if(NULL == pnode)  // 如果内存分配失败,打印错误并返回 -1。{perror("malloc fail\n");return -1;}// 初始化新节点的指针和数据。pnode->pnext = NULL;pnode->data = data;// 如果当前哈希表位置为空,将新节点直接插入此处。if(hashtable[addr] == NULL){hashtable[addr] = pnode;return 0;}// 如果哈希表位置已有节点,则需要按字母顺序插入到链表中。Hsnode_t *p = hashtable[addr];// 如果新节点应该插入到链表头部(字母顺序更小),则将其作为新的头节点。if(strcmp(p->data.name, data.name) >= 0){pnode->pnext = p;hashtable[addr] = pnode;return 0;}// 否则,找到链表中的正确位置,保持字母顺序。while(p->pnext != NULL && strcmp(p->pnext->data.name, data.name) < 0){p = p->pnext;}// 将新节点插入链表中,维护链表顺序。pnode->pnext = p->pnext;p->pnext = pnode;return 0;
}/*** @brief 遍历哈希表,打印所有存储的用户信息。* @return 成功返回 0。*/
int traverse_table()
{printf("\n");// 遍历哈希表的每一个槽位。for(int i = 0; i < HASH_SIZE; ++i){if(hashtable[i] == NULL)  // 如果当前位置为空,跳过。{continue;}Hsnode_t *p = hashtable[i];printf("%c \n", i + 'a');  // 打印当前槽位对应的字母。// 遍历链表,打印每个节点的用户数据(姓名和电话)。while(p != NULL){printf("%s  %s \n", p->data.name, p->data.tel);p = p->pnext;}printf("\n");}return 0;
}/*** @brief 查找哈希表中是否存在指定名字的用户信息。* @param name 要查找的名字。* @return 返回指向找到节点的指针,未找到返回 NULL。*/
Hsnode_t *fine_table(char *name)
{// 根据名字的第一个字符计算哈希地址。int addr = hashfuction(name[0]);// 遍历哈希表中对应的链表,寻找匹配的名字。Hsnode_t *p = hashtable[addr];while(p != NULL){// 如果找到名字匹配的节点,返回该节点。if(!strcmp(p->data.name, name)){return p;}p = p->pnext;}return NULL;  // 未找到返回 NULL。
}/*** @brief 删除哈希表中指定名字的用户数据。* @param name 要删除的名字。* @param data 保存被删除的节点数据(输出参数)。* @return 成功返回 1,未找到返回 0。*/
int delete_hatable(char *name, HsDatetype *data)
{// 根据名字的第一个字符计算哈希地址。int addr = hashfuction(name[0]);Hsnode_t *p = hashtable[addr];if(hashtable[addr] == NULL)  // 如果当前哈希表位置为空,返回 0。{return 0;}// 如果第一个节点就是要删除的节点,直接删除它。if(!strcmp(p->data.name, name)){*data = p->data;  // 保存删除的节点数据。hashtable[addr] = p->pnext;  // 更新哈希表头指针。Hsnode_t *q = p;free(q);  // 释放节点内存。return 0;}// 否则,遍历链表,查找要删除的节点。while(p->pnext != NULL){if(!strcmp(p->pnext->data.name, name)){*data = p->pnext->data;  // 保存删除的节点数据。Hsnode_t *q = p->pnext;p->pnext = p->pnext->pnext;  // 更新链表指针。free(q);  // 释放节点内存。return 1;}p = p->pnext;}return 0;  // 如果未找到节点,返回 0。
}/*** @brief 删除整个哈希表中的所有节点,释放所有内存。* @return 成功返回 0。*/
int delete_table()
{// 遍历哈希表的每一个槽位。for(int i = 0; i < HASH_SIZE; ++i){if(hashtable[i] == NULL)  // 如果当前槽位为空,跳过。{continue;}Hsnode_t *p = hashtable[i];// 释放该槽位下链表中的所有节点。while(p != NULL){hashtable[i] = p->pnext;  // 更新链表指针。Hsnode_t *q = p;p = p->pnext;free(q);  // 释放节点内存。}}return 0;
}

函数验证 

#include "hash.h"  // 包含哈希表相关的头文件,提供数据结构和函数声明。/*** @brief 主函数,程序入口。* @param argc 命令行参数的数量。* @param argv 命令行参数的列表。* @return 程序的退出状态码。*/
int main(int argc, char *argv[])
{// 初始化多个用户数据,包括姓名和电话号码,使用结构体数组存储。HsDatetype pers[] = {{"zhansan", "110"}, {"lisi", "120"},{"wangwu", "119"}, {"longjunlin", "114"},{"maqi", "10086"}, {"waa", "156"}};// 将每个用户数据插入哈希表。insert_hatable(pers[0]);insert_hatable(pers[1]);insert_hatable(pers[2]);insert_hatable(pers[3]);insert_hatable(pers[4]);insert_hatable(pers[5]);// 遍历并打印当前哈希表中的所有数据。traverse_table();printf("\n**************\n");// 查找哈希表中是否有 "wangwu" 的数据。Hsnode_t *p = fine_table("wangwu");if(p != NULL)  // 如果找到,则打印该用户的信息。{printf("%s %s\n", p->data.name, p->data.tel);}printf("\n**************\n");// 删除哈希表中的 "longjunlin" 数据,并将删除的节点数据保存到 `data` 中。HsDatetype data;int ret = delete_hatable("longjunlin", &data);if(1 == ret)  // 如果成功删除,打印删除的用户信息。{printf("%s %s\n", data.name, data.tel);}printf("\n**************\n");// 再次遍历并打印当前哈希表中的所有数据,显示删除后的结果。traverse_table();// 删除整个哈希表,释放所有节点的内存。delete_table();return 0;  // 程序正常退出。
}

运行结果

a 
waa  156l 
lisi  120
longjunlin  114m 
maqi  10086w 
wangwu  119z 
zhansan  110

 

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

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

相关文章

Spring Boot属性注入的多种方式!

Spring Boot的一个问题&#xff0c;证明你是不是真正的 "会用" Spring boot ?Spring Boot的一个问题&#xff0c;直接暴露你是不是真正使用Spring Boothttps://mp.weixin.qq.com/s?__bizMzkzMTY0Mjc0Ng&mid2247484040&idx1&sn64ad15d95e44c874cc890973…

2024年CCPC网络赛A题题解 —— 军训Ⅰ(gym105336A)

个人认为很唐的一道题&#xff0c;考虑到不少人可能懒得写&#xff0c;我这里给大家发个代码叭&#xff0c;还有一点点题解&#xff08;因为真的不是很难&#xff09;。这是题面&#xff1a; 然后我来讲讲怎么做&#xff0c;不觉得会有多少人题目意思都理解不了叭&#xff1f;这…

码上进阶_刷题模块测试_用例设计

码上进阶_刷题模块测试_用例设计 系统概述&#xff1a; 码上进阶是为程序员专门打造的交流平台&#xff0c;采用主流的微服务框架和C端技术栈作为技术基础。在这个平台上&#xff0c;程序员 可以通过刷题、练习和模拟面试来提升自己的面试能力。 功能测试&#xff1a; 登录…

深度学习从入门到精通——感知损失介绍及基本实现

Perceptual Losses 感知损失&#xff08;Perceptual Loss&#xff09;感知损失的定义 图像转换问题&#xff08;Image Transformation Tasks&#xff09;现有方法代码解释感知损失&#xff08;Perceptual Loss&#xff09;1. 感知损失的背景2. 感知损失的定义3. 感知损失的优点…

Linux 常用命令 - tail 【显示文件最后几行内容】

简介 tail 这个命令源自英文单词 “尾巴”&#xff0c;它的主要功能是显示文件的最后几行内容。通过使用 tail&#xff0c;用户可以查看文件的最新添加内容&#xff0c;特别是对于监控日志文件来说非常有用。tail 命令默认显示文件的最后 10 行&#xff0c;但这可以通过参数调…

数学建模_数据预处理流程(全)

数据预处理整体流程图 一般数据预处理流程 处理缺失值&#xff1a;填补或删除缺失值。处理异常值&#xff1a;检测并处理异常值。数据编码&#xff1a;将分类变量进行标签编码或独热编码。数据标准化/归一化&#xff1a;对数据进行标准化或归一化处理。连续变量离散化&#xff…

基于JAVA+SpringBoot+Vue的企业级工位管理系统

基于JAVASpringBootVue的企业级工位管理系统 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末附源码下载链接&#x1f345; …

docker 启动ElasticSearch

拉取es镜像 docker pull docker.elastic.co/elasticsearch/elasticsearch:7.15.0运行 Elasticsearch 容器 docker run -d \--name elasticsearch \-p 9200:9200 \-p 9300:9300 \-e "discovery.typesingle-node" \-e "ES_JAVA_OPTS-Xms512m -Xmx512m" \# -…

WinCC 中对 VBS 进行单步执行调试

以前应该写过文章给各位展示如何在WinCC 中通过自身控件对脚本&#xff08;C、VBS&#xff09;进行脚本诊断和排错。但是也有用户反馈说在编写了一些相对复杂的脚本后&#xff0c;WinCC自身控件无法做到单步调试&#xff0c;也会影响脚本的诊断调试效率。如果能够对WinCC 中的脚…

论文解读:《LAMM: Label Alignment for Multi-Modal Prompt Learning》

系列文章目录 文章目录 系列文章目录LAMM: Label Alignment for Multi-Modal Prompt Learning学习1、论文细节理解1、研究背景2、论文贡献3、方法框架4、研究思路5、实验6、限制 LAMM: Label Alignment for Multi-Modal Prompt Learning学习 1、论文细节理解 VL模型和下游任务…

数学建模笔记—— 最大最小化规划模型

数学建模笔记—— 最大最小化规划模型 最大最小化规划模型1. 模型原理2. 典型例题3. matlab代码求解 最大最小化规划模型 1. 模型原理 在博弈论中有一个经典理论一一最大最小策略( Minimax strategy)&#xff0c;是由博弈论奠基人约翰冯诺伊曼(John von Neumann)在1928年提出…

LeetCode:2181. 合并零之间的节点 遍历链表

2181. 合并零之间的节点 today 2181. 合并零之间的节点 题目描述 给你一个链表的头节点 head &#xff0c;该链表包含由 0 分隔开的一连串整数。链表的开端和末尾的节点都满足Node.val 0 。 对于每两个相邻的0&#xff0c;请你将它们之间的所有节点合并成一个节点&#xf…

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统 在产品将要上线之前&#xff0c;需要制作不同类型格式的根文件系统 在产品研发阶段&#xff0c;我们还是需要使用nfs的方式挂载根文件系统 优点&#xff1a;可以直接在上位机中修改文件系统内容&#xff0c;延长EMMC的寿命 【1】重启上位机nfs服…

Docker初识(Docker技术集群与应用)

一、基础设施即服务 IaaS&#xff08;Infrastructure as a Service&#xff09; eg&#xff1a;购买的云服务器&#xff0c;就是IaaS 提供给客户的服务是对所有设施的利用&#xff0c;包括处理、存储、网络和其他基本的计算资源。客户能够部署和运行任意软件&#xff0c;包括…

【CTF】MISC常用工具集锦/使用方法简介

前言# MISC题型多变而且工具繁杂&#xff0c;因此自己花时间整理了一份工具列表&#xff0c;以便日后参考用流畅地阅读这篇博客&#xff0c;你可能需要&#xff1a; Python2.7.18 Python3.8 任何一个更高版本的Python&#xff0c;使用conda管理Linux虚拟机&#xff0c;kali…

人工智能安全治理框架导图

资源链接&#xff1a;《人工智能安全治理框架》1.0版发布_中央网络安全和信息化委员会办公室

MAT:一款针对MSSQL服务器的安全检测与审计工具

关于MAT MAT是一款针对MSSQL服务器的安全检测与审计工具&#xff0c;该工具使用C#开发&#xff0c;可以帮助广大研究人员快速识别和发现MSSQL 服务器中的安全问题&#xff0c;并实现安全检测与审计目的。 功能介绍 1、执行自动检查并识别安全问题&#xff1b; 2、允许通过 Win…

java黑马微项目

1 飞机票 代码实现&#xff1a; import java.util.Scanner; public class F1 {public static void main(String[] args) {Scanner input new Scanner(System.in);System.out.print("请输入票价&#xff1a; ");double jia input.nextDouble();System.out.print(&…

Threejs之纹理Texture

本文目录 前言一、Texture的基本概念1.1 定义及作用1.2 常用属性 二、代码及效果2.1 代码2.2 效果 前言 在Three.js中&#xff0c;Texture&#xff08;纹理&#xff09;是一项核心功能&#xff0c;创建一个纹理贴图&#xff0c;将其应用到一个表面&#xff0c;或者作为反射/折射…

web基础之信息泄露

1、目录遍历漏洞 &#xff08;1&#xff09;原理&#xff1a;本质是没有过滤用户输入的 ../ 相关的目录跳转符&#xff0c;使得攻击者通过目录跳转符来遍历服务器中的任意文件。 &#xff08;2&#xff09;题解&#xff1a; eg:根据提示遍历网页目录信息&#xff0c;会在某一个…