当前位置: 首页 > news >正文

从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) 的原因了,如果东西太多了,柜子设置太多了,就可以要用另一种方式了,那就是字典树
再次感谢各位大哥大姐们捧场,阅读到此,本篇结束,如有其他疑问,评论区相见~~

相关文章:

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

【前言】   本文将以哈希表重构实战为核心&#xff0c;完整展示如何将传统条件匹配逻辑(上千层if-else判断)转化为O(1)的哈希表高效实现。通过指纹验证场景的代码级解剖&#xff0c;您将深入理解&#xff1a;   1.哈希函数设计如何规避冲突陷阱   2.链式寻址法的工程实现…...

视频采集卡接口

采集卡的正面有MIC IN、LINE IN以及AUDIO OUT三个接口&#xff0c; MIC IN为麦克风输入&#xff0c;我们如果要给采集到的视频实时配音或者是在直播的时候进行讲解&#xff0c;就可以在这里插入一个麦克风&#xff0c; LINE IN为音频线路输入&#xff0c;可以外接播放背景音乐…...

蓝桥杯真题 - 像素放置 - 题解

题目链接&#xff1a;https://www.lanqiao.cn/problems/3508/learning/ 个人评价&#xff1a;难度 3 星&#xff08;满星&#xff1a;5&#xff09; 前置知识&#xff1a;深度优先搜索 整体思路 深搜&#xff0c;在搜索过程中进行剪枝&#xff0c;剪枝有以下限制条件&#xf…...

vue基础(三)

常用指令 1. v-bind 固定绑定与动态绑定&#xff1a; 语法&#xff1a; 标准语法&#xff1a;v-bind:属性"动态数据" 简写语法&#xff1a;:属性"动态数拓" <!DOCTYPE html> <html lang"en"><head><me…...

使用Python开发PPTX压缩工具

引言 在日常办公中&#xff0c;PPT文件往往因为图片过大而导致文件体积过大&#xff0c;不便于传输和存储。为了应对这一问题&#xff0c;我们可以使用Python的wxPython图形界面库结合python-pptx和Pillow&#xff0c;开发一个简单的PPTX压缩工具。本文将详细介绍如何实现这一…...

ubuntu24.04安装布置ros

最近换电脑布置机器人环境&#xff0c;下了24.04&#xff0c;但是网上的都不太合适&#xff0c;于是自己试着布置好了&#xff0c;留作有需要的人一起看看。 文章目录 目录 前言 一、确认 ROS 发行版名称 二、检查你的 Ubuntu 版本 三、安装正确的 ROS 发行版 四、对于Ubuntu24…...

SQL 秒变 ER 图 sql转er图

&#x1f680;SQL 秒变 ER 图&#xff0c;校园小助手神了&#xff01; 学数据库的宝子们集合&#x1f64b;‍♀️ 是不是每次碰到 SQL 转 ER 图就头皮发麻&#xff1f;看着密密麻麻的代码&#xff0c;脑子直接死机&#xff0c;好不容易理清一点头绪&#xff0c;又被复杂的表关…...

【AI知识点】如何判断数据集是否噪声过大?

【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】【读书与思考】【AI应用】 判断数据集是否 噪声过大 是数据分析和机器学习建模过程中至关重要的一步。噪声数据会导致模型难以学习数据的真实模式&#xff0c;从而影响预测效果。以下是一些常见的方法来判断数据…...

网络安全治理架构图 网络安全管理架构

网站安全攻防战 XSS攻击 防御手段&#xff1a; - 消毒。 因为恶意脚本中有一些特殊字符&#xff0c;可以通过转义的方式来进行防范 - HttpOnly 对cookie添加httpOnly属性则脚本不能修改cookie。就能防止恶意脚本篡改cookie 注入攻击 SQL注入攻击需要攻击者对数据库结构有所…...

如何写出优秀的单元测试?

写出优秀的单元测试需要考虑以下几个方面&#xff1a; 1. 测试用例设计 测试用例应该覆盖被测试代码的不同场景和边界情况&#xff0c;以尽可能发现潜在的问题。在设计测试用例时需要关注以下几点&#xff1a; 输入输出数据&#xff1a;要测试的函数或方法可能有多个输入参数…...

数据留痕的方法

在项目中&#xff0c;数据变更时&#xff0c;经常需要记录上次的数据&#xff0c;以便查看对比&#xff0c;专业术语叫做数据留痕。数据变更留痕&#xff08;即记录数据的变更历史&#xff09;是一个常见的需求&#xff0c;例如在审计、追踪数据变化或满足合规性要求的场景中。…...

机器学习数学基础:19.线性相关与线性无关

一、线性相关与线性无关的定义 &#xff08;一&#xff09;线性相关 想象我们有一组向量&#xff0c;就好比是一群有着不同“力量”和“方向”的小伙伴。给定的向量组 α ⃗ 1 , α ⃗ 2 , ⋯ , α ⃗ m \vec{\alpha}_1, \vec{\alpha}_2, \cdots, \vec{\alpha}_m α 1​,α 2…...

ArgoCD实战指南:GitOps驱动下的Kubernetes自动化部署与Helm/Kustomize集成

摘要 ArgoCD 是一种 GitOps 持续交付工具,专为 Kubernetes 设计。它能够自动同步 Git 仓库中的声明性配置,并将其应用到 Kubernetes 集群中。本文将介绍 ArgoCD 的架构、安装步骤,以及如何结合 Helm 和 Kustomize 进行 Kubernetes 自动化部署。 引言 为什么选择 ArgoCD?…...

JVM虚拟机以及跨平台原理

相信大家已经了解到Java具有跨平台的特性&#xff0c;即“一次编译&#xff0c;到处运行”&#xff0c;例如在Windows下编写的程序&#xff0c;无需任何修改就可以在Linux下运行&#xff0c;这是C和C很难做到的。 那么&#xff0c;跨平台是怎样实现的呢&#xff1f;这就要谈及…...

【AIGC提示词系统】基于 DeepSeek R1 + ClaudeAI 易经占卜系统

上篇因为是VIP&#xff0c;这篇来一个免费的 提示词在最下方&#xff0c;喜欢的点个关注吧 引言 在人工智能与传统文化交融的今天&#xff0c;如何让AI系统能够传递传统易经文化的智慧&#xff0c;同时保持易经本身的神秘感和权威性&#xff0c;是一个极具挑战性的课题。本文将…...

电路笔记 : opa 运放失调电压失调电流输入偏置电流 + 反向放大器的平衡电阻 R3 = R1 // R2 以减小输出直流噪声

目录 定义影响和解决失调电压输入偏置电流平衡电阻R3推导公式&#xff1a; 失调电流 实际的运算放大器&#xff08;Op-Amp&#xff09;存在一些非理想特性&#xff0c;如失调电压&#xff08;VIO&#xff09;、失调电流&#xff08;IIO&#xff09;和输入偏置电流&#xff08;I…...

ScrapeGraphAI颠覆传统网络爬虫技术

ScrapeGraphAI颠覆传统网络爬虫技术&#xff01; 引言 在互联网时代&#xff0c;数据如同油田&#xff0c;丰富而深邃。但如何有效地提取这些数据&#xff0c;仍然是许多开发者面临的艰巨任务。你有没有想过&#xff0c;传统的网络爬虫技术是否已经过时&#xff1f;如今&…...

通过多层混合MTL结构提升股票市场预测的准确性,R²最高为0.98

“Boosting the Accuracy of Stock Market Prediction via Multi-Layer Hybrid MTL Structure” 论文地址&#xff1a;https://arxiv.org/pdf/2501.09760 ​​​​​​​ 摘要 本研究引入了一种创新的多层次混合多任务学习架构&#xff0c;致力于提升股市预测的效能。此架构融…...

java将list转成树结构

首先是实体类 public class DwdCusPtlSelectDto {//idprivate String key;//值private String value;//中文名private String title;private List<DwdCusPtlSelectDto> children;private String parentId;public void addChild(DwdCusPtlSelectDto child) {if(this.chil…...

互联网分布式ID解决方案

业界实现方案 1. 基于UUID 2. 基于DB数据库多种模式(自增主键、segment) 3. 基于Redis 4. 基于ZK、ETCD 5. 基于SnowFlake 6. 美团Leaf(DB-Segment、zkSnowFlake) 7. 百度uid-generator() 基于UUID生成唯一ID UUID生成策略 推荐阅读 DDD领域驱动与微服务架构设计设计模…...

HTML 语义化

目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案&#xff1a; 语义化标签&#xff1a; <header>&#xff1a;页头<nav>&#xff1a;导航<main>&#xff1a;主要内容<article>&#x…...

MongoDB学习和应用(高效的非关系型数据库)

一丶 MongoDB简介 对于社交类软件的功能&#xff0c;我们需要对它的功能特点进行分析&#xff1a; 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具&#xff1a; mysql&#xff1a;关系型数据库&am…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

大语言模型如何处理长文本?常用文本分割技术详解

为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)

本期内容并不是很难&#xff0c;相信大家会学的很愉快&#xff0c;当然对于有后端基础的朋友来说&#xff0c;本期内容更加容易了解&#xff0c;当然没有基础的也别担心&#xff0c;本期内容会详细解释有关内容 本期用到的软件&#xff1a;yakit&#xff08;因为经过之前好多期…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek

文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama&#xff08;有网络的电脑&#xff09;2.2.3 安装Ollama&#xff08;无网络的电脑&#xff09;2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...

C++:多态机制详解

目录 一. 多态的概念 1.静态多态&#xff08;编译时多态&#xff09; 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1&#xff09;.协变 2&#xff09;.析构函数的重写 5.override 和 final关键字 1&#…...

MyBatis中关于缓存的理解

MyBatis缓存 MyBatis系统当中默认定义两级缓存&#xff1a;一级缓存、二级缓存 默认情况下&#xff0c;只有一级缓存开启&#xff08;sqlSession级别的缓存&#xff09;二级缓存需要手动开启配置&#xff0c;需要局域namespace级别的缓存 一级缓存&#xff08;本地缓存&#…...

使用SSE解决获取状态不一致问题

使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件&#xff0c;这个上传文件是整体功能的一部分&#xff0c;文件在上传的过程中…...