从O(k*n)到O(1):如何用哈希表终结多层if判断的性能困局

【前言】
  本文将以哈希表重构实战为核心,完整展示如何将传统条件匹配逻辑(上千层if-else判断)转化为O(1)的哈希表高效实现。通过指纹验证场景的代码级解剖,您将深入理解:
  1.哈希函数设计如何规避冲突陷阱
  2.链式寻址法的工程实现细节
  若有出错之处,望各位大哥大姐指出(●’◡’●)

Ⅰ 背景

  最近,拿到一个场景,有一个研判规则中,需要一次匹配上千个以上规则的规则,一开始采用的是多层if判断,但是这种在高频事件中,明显性能遭不住,而且在研判速度上远远达不到预期

最初代码如下

bool is_finger(char finger[]){if (strlen(finger) == yyy){return 0;}if (strlen(finger) == xxx){return 0;}//………………还有几千个规则研判
}

【目标】将程序的时间复杂度O(k*n),降至O(1)
【实现】可以采用两种,一是哈希表,二是字典树

Ⅱ C程序优化实践

说那么多,没啥用,直接实操,冲冲冲
先定义下变量和结构体


#define HASH_SIZE 1024  // 哈希表大小,应该是质数以减少冲突typedef struct HashNode {char* key;struct HashNode* next;  // 处理冲突用的链表
} HashNode;typedef struct {HashNode* table[HASH_SIZE];
} HashMap;

初始化哈希表


// 哈希函数
unsigned int hash(const char* str) {unsigned int hash = 5381;int c;while ((c = *str++)) {hash = ((hash << 5) + hash) + c; // hash * 33 + c}return hash % HASH_SIZE;
}
// 初始化哈希表
HashMap* init_fingerprint_map() {HashMap* map = (HashMap*)malloc(sizeof(HashMap));memset(map->table, 0, sizeof(HashNode*) * HASH_SIZE);// 需要过滤的指纹列表const char* fingerprints[] = {"En", "nTf.n", "kno:n", "n)on", "fknn","kn", "n&n", "nn", "n&nn", "Ton",};// 插入所有指纹for (int i = 0; i < sizeof(fingerprints)/sizeof(char*); i++) {unsigned int index = hash(fingerprints[i]);HashNode* node = (HashNode*)malloc(sizeof(HashNode));node->key = strdup(fingerprints[i]);node->next = map->table[index];map->table[index] = node;}return map;
}

关键实现,哈希查找

// 查找函数 - O(1) 平均时间复杂度
bool is_fingerprint(HashMap* map, const char* fingerprint) {unsigned int index = hash(fingerprint);HashNode* current = map->table[index];// 在链表中查找while (current != NULL) {if (strcmp(current->key, fingerprint) == 0) {return false;  // 找到匹配项,返回false}current = current->next;}return true;  // 未找到匹配项
}

记得要释放内存

// 释放哈希表内存
void free_hashmap(HashMap* map) {for (int i = 0; i < HASH_SIZE; i++) {HashNode* current = map->table[i];while (current != NULL) {HashNode* temp = current;current = current->next;free(temp->key);free(temp);}}free(map);
}

完整代码

#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>#define HASH_SIZE 1024  // 哈希表大小,应该是质数以减少冲突typedef struct HashNode {char* key;struct HashNode* next;  // 处理冲突用的链表
} HashNode;typedef struct {HashNode* table[HASH_SIZE];
} HashMap;// 哈希函数
unsigned int hash(const char* str) {unsigned int hash = 5381;int c;while ((c = *str++)) {hash = ((hash << 5) + hash) + c; // hash * 33 + c}return hash % HASH_SIZE;
}// 初始化哈希表
HashMap* init_fingerprint_map() {HashMap* map = (HashMap*)malloc(sizeof(HashMap));memset(map->table, 0, sizeof(HashNode*) * HASH_SIZE);// 需要过滤的指纹列表const char* fingerprints[] = {"En", "nTf.n", "kno:n", "n)on", "fknn","kn", "n&n", "nn", "n&nn", "Ton",};// 插入所有指纹for (int i = 0; i < sizeof(fingerprints)/sizeof(char*); i++) {unsigned int index = hash(fingerprints[i]);HashNode* node = (HashNode*)malloc(sizeof(HashNode));node->key = strdup(fingerprints[i]);node->next = map->table[index];map->table[index] = node;}return map;
}// 查找函数 - O(1) 平均时间复杂度
bool is_fingerprint(HashMap* map, const char* fingerprint) {unsigned int index = hash(fingerprint);HashNode* current = map->table[index];// 在链表中查找while (current != NULL) {if (strcmp(current->key, fingerprint) == 0) {return false;  // 找到匹配项,返回false}current = current->next;}return true;  // 未找到匹配项
}// 释放哈希表内存
void free_hashmap(HashMap* map) {for (int i = 0; i < HASH_SIZE; i++) {HashNode* current = map->table[i];while (current != NULL) {HashNode* temp = current;current = current->next;free(temp->key);free(temp);}}free(map);
}
int main() {// 初始化(只需要一次)HashMap* map = init_fingerprint_map();// 快速查找并打印结果bool result1 = is_fingerprint(map, "En");printf("Test 'En': %s\n", result1 ? "true" : "false");bool result2 = is_fingerprint(map, "kn");printf("Test 'kn': %s\n", result2 ? "true" : "false");bool result3 = is_fingerprint(map, "other");printf("Test 'other': %s\n", result3 ? "true" : "false");// 清理资源free_hashmap(map);return 0;
}

Ⅲ 深度解析哈希表为啥能O(1)

1. 先了解下什么是哈希表?

想象你有一个带编号的储物柜(这就是哈希表):
在这里插入图片描述

  • 哈希函数就像一个规则,告诉你把东西放在哪个柜子里
  • 比如:把字符串 “hello” 放到 3 号柜子
    在这里插入图片描述

回到一开始说的"为什么说查找是 O(1)"!
当你要找 “hello” 时:

  • 用哈希函数算出位置:3
  • 我们直接去 3 号柜子找
    这样子,是不是就不需要查看其他柜子了,直接O(1),起飞芜湖~~

2. 哈希冲突到底是什么

了解了什么是哈希表,那开始熟悉下哈希冲突
假设现在:

  • “hello” -> 3号柜子
  • “world” -> 也是3号柜子
    在这里插入图片描述
    处理冲突的方式:储物柜用链子连接
    在这里插入图片描述

好了,了解了基本逻辑,基本可以上C代码

// 假设我们有一个小型哈希表,存储常见编程语言
#define HASH_SIZE 8  // 8个储物柜// 存储数据
hash("Python") -> 3
hash("Java") -> 3    // 冲突!
hash("Go") -> 5储物柜:
0    1    2    3          4    5     6    7
[  ] [  ] [  ] [Python]-> [Java] [Go] [  ] [  ]// 查找"Java"的过程
1. hash("Java") = 3           // 计算位置
2. 检查3号柜子的 Python      // 不是
3. 检查下一个 Java          // 找到了!

3.哈希表为什么快

  • 想象一个真实的哈希表
#define HASH_SIZE 1024  // 1024个储物柜// 如果存100个数据
// 平均每个柜子只会有 100/1024 ≈ 0.1 个数据
// 也就是说,大多数柜子是空的!

联想实际场景:图书馆找书

  • 不需要从第一本找到最后一本
  • 直接根据编号去对应书架
  • 即使这个位置有几本书,也只需要看很少几本

  这样子,就是不是很清晰了,其实哈希表,就是拿着key,拿到索引,然后去对应柜子找东西
按照这个思路,来解读下刚刚写的哈希查找代码

bool is_fingerprint(HashMap* map, const char* fingerprint) {// 1. 计算应该去哪个储物柜unsigned int index = hash(fingerprint);// 2. 去到那个储物柜HashNode* current = map->table[index];// 3. 如果这个储物柜有多个物品,挨个检查while (current != NULL) {// 4. 检查是不是要找的东西if (strcmp(current->key, fingerprint) == 0) {return false;  // 找到了!}current = current->next;  // 看下一个}return true;  // 没找到
}

值得注意的是:这里的循环是很少执行,因为柜子的东西不会太多,甚至有些规则还是空的

  • 哈希表很大(比如1024个位置)
  • 数据相对较少(比如100个)
  • 哈希函数会尽量均匀分布
  • 所以每个位置平均不到1个数据

所以虽然代码里有 while 循环,但实际上:

  • 直接定位到具体位置(像图书馆找书架)
  • 即使需要循环,也只需要看很少的几个

  所以说哈希表,这就是为什么说它是 O(1) 的原因了,如果东西太多了,柜子设置太多了,就可以要用另一种方式了,那就是字典树
再次感谢各位大哥大姐们捧场,阅读到此,本篇结束,如有其他疑问,评论区相见~~

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

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

相关文章

离线统信系统的python第三方库批量安装流程

一、关于UOS本机 操作系统&#xff1a;UOS&#xff08;基于Debian的Linux发行版&#xff09; CPU&#xff1a;海光x86 二、具体步骤 1、在联网的电脑上用控制台的pip命令批量下载指定版本的第三方库 方法A cd <目标位置的绝对路径> pip download -d . --platform many…

第 26 场 蓝桥入门赛

3.电子舞龙【算法赛】 - 蓝桥云课 问题描述 话说这年头&#xff0c;连舞龙都得电子化&#xff01;这不&#xff0c;蓝桥村的老程序员王大爷突发奇想&#xff0c;用LED灯带和一堆传感器鼓捣出了一条“电子舞龙”&#xff0c;它能根据程序指令在村里的广场上“翩翩起舞”。 广…

0012—数组

存取一组数据&#xff0c;使用数组。 数组是一组相同类型元素的集合。 要存储1-10的数字&#xff0c;怎么存储&#xff1f; C语言中给了数组的定义&#xff1a;一组相同类型元素的集合。 创建一个空间创建一组数&#xff1a; 一、数组的定义 int arr[10] {1,2,3,4,5,6,7,8,…

详细教程 | 如何使用DolphinScheduler调度Flink实时任务

Apache DolphinScheduler 非常适用于实时数据处理场景&#xff0c;尤其是与 Apache Flink 的集成。DolphinScheduler 提供了丰富的功能&#xff0c;包括任务依赖管理、动态调度、实时监控和日志管理&#xff0c;能够有效简化 Flink 实时任务的管理和部署。通过 DolphinSchedule…

Redis Copilot:基于Redis为AI打造的副驾工具

我们最近发布了Redis Copilot&#xff0c;以帮助开发者更快地使用Redis构建应用。我们的使命是使应用程序快速运行&#xff0c;并简化构建过程。为此&#xff0c;Redis Copilot作为您的AI助手&#xff0c;能够让您更迅速地完成与Redis相关的任务。您今天就可以在Redis Insight中…

了解传输层TCP协议

目录 一、TCP协议段格式 二、TCP原理 1.确认应答 2.超时重传 3.连接管理 建立连接 断开连接 4.滑动窗口 5.流量控制 6.拥塞控制 7.延时应答 8.捎带应答 9.面向字节流 10.TCP异常情况 TCP&#xff0c;即Transmission Control Protocol&#xff0c;传输控制协议。人如…

idea 如何使用deepseek 保姆级教程

1.安装idea插件codegpt 2.注册deepseek并生成apikey deepseek 开发平台&#xff1a; DeepSeek​​​​​​​ 3.在idea进行codegpt配置 打开idea的File->Settings->Tools->CodeGPT->Providers->Custom OpenAI Chat Completions的URL填写 https://api.deepseek…

面试真题 | 超图骏科 C++

构造函数的类型及其描述 在C++中,构造函数是用于初始化对象的特殊成员函数。根据用途和参数的不同,可以将构造函数分为以下几种类型: 默认构造函数(Default Constructor) 描述:没有参数的构造函数。如果类中没有定义任何构造函数,编译器会自动生成一个默认构造函数。但…

华为OD机试E卷 --矩阵扩散--24年OD统一考试(Java JS Python C C++)

文章目录 题目描述输入描述输出描述用例题目解析JS算法源码Java算法源码python算法源码c算法源码题目描述 存在一个 m n 的 二维数组 ,其成员取值范围为 0 或 1。 其中值为 1 的成员具备扩散性,每经过 1s,将上下左右值为 0 的成员同化为 1。 二维数组的成员 初始值 都为 0…

系统URL整合系列视频五(后端技术实现)

视频 系统URL整合系列视频五&#xff08;后端技术实现&#xff09; 视频介绍 &#xff08;全国&#xff09;大型分布式系统Web资源URL整合需求后端技术实现。当今社会各行各业对软件系统的web资源访问权限控制越来越严格&#xff0c;控制粒度也越来越细。安全级别提高的同时也…

二叉树理论基础详解:从零开始理解数据结构的核心

二叉树理论基础详解&#xff1a;从零开始理解数据结构的核心 在算法与数据结构的学习中&#xff0c;二叉树是一种非常基础但又极其重要的数据结构。无论是编程面试还是实际开发&#xff0c;对二叉树的 理解都是必不可少的技能。本文将从头开始&#xff0c;系统地介绍二叉树的基…

Linux之kernel(1)系统基础理论(1)

Linux之Kernel(1)系统基础理论(1) Author: Once Day Date: 2025年2月6日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: Linux内核知识_Once-Day的…

Deepseek部署的模型参数要求

DeepSeek 模型部署硬件要求 模型名称参数量显存需求&#xff08;推理&#xff09;显存需求&#xff08;微调&#xff09;CPU 配置内存要求硬盘空间适用场景DeepSeek-R1-1.5B1.5B4GB8GB最低 4 核&#xff08;推荐多核&#xff09;8GB3GB低资源设备部署&#xff0c;如树莓派、旧…

如何解决 javax.xml.crypto.dsig.TransformException: 转换异常问题?亲测有效的解决方法!

1. 问题分析 1.1 异常描述 javax.xml.crypto.dsig.TransformException 是在使用 Java XML 加密和签名 API 时&#xff0c;发生的一个常见异常。它通常出现在 XML 数字签名的转换过程中&#xff0c;可能是由于签名、加密或验证过程中发生了错误。 1.2 异常场景 该异常通常发…

【读书笔记·VLSI电路设计方法解密】问题46:什么是bug覆盖率

在IC设计项目的验证过程中&#xff0c;功能测试&#xff08;通过使用测试平台&#xff09;有助于定位设计错误或漏洞。这个验证过程有三个阶段&#xff1a;构建和启动测试平台、验证基本测试用例以及验证边界情况。 在前两个阶段&#xff0c;漏洞很容易被检测到&#xff0c;因…

【python】简单的flask做页面。一组字母组成的所有单词。这里的输入是一组字母,而输出是所有可能得字母组成的单词列表

目录结构如下&#xff1a; . ├── static │ ├── css │ │ └── styles.css │ └── js │ └── scripts.js ├── templates │ ├── base.html │ ├── case_converter.html │ ├── index.html │ └── word_finder.html ├── app.py ├── tree.py…

借助 Cursor 快速实现小程序前端开发

借助 Cursor 快速实现小程序前端开发 在当今快节奏的互联网时代&#xff0c;小程序因其便捷性、高效性以及无需下载安装的特点&#xff0c;成为众多企业和开发者关注的焦点。然而&#xff0c;小程序的开发往往需要耗费大量的时间和精力&#xff0c;尤其是在前端开发阶段。幸运…

【ArcGIS Pro 简介1】

ArcGIS Pro 是由 Esri &#xff08;Environmental Systems Research Institute&#xff09;公司开发的下一代桌面地理信息系统&#xff08;GIS&#xff09;软件&#xff0c;是传统 ArcMap 的现代化替代产品。它结合了强大的空间分析能力、直观的用户界面和先进的三维可视化技术…

JAVA安全—FastJson反序列化利用链跟踪autoType绕过

前言 FastJson这个漏洞我们之前讲过了,今天主要是对它的链条进行分析一下,明白链条的构造原理。 Java安全—log4j日志&FastJson序列化&JNDI注入_log4j漏洞-CSDN博客 漏洞版本 1.2.24及以下没有对序列化的类做校验,导致漏洞产生 1.2.25-1.2.41增加了黑名单限制,…

力扣240 搜索二维矩阵 ll

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。每列的元素从上到下升序排列。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,…