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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

后端java工程师经验之谈,工作7年,mysql使用心得

mysql 工作7年&#xff0c;mysql使用心得 mysql1.创建变量2.创建存储过程2.1&#xff1a;WHILE循环2.2&#xff1a;repeat循环2.3&#xff1a;loop循环2.4&#xff1a;存储过程&#xff0c;游标2.5&#xff1a;存储过程&#xff0c;有输入参数和输出参数 3.三种注释写法4.case …

【WB 深度学习实验管理】利用 Hugging Face 实现高效的自然语言处理实验跟踪与可视化

本文使用到的 Jupyter Notebook 可在GitHub仓库002文件夹找到&#xff0c;别忘了给仓库点个小心心~~~ https://github.com/LFF8888/FF-Studio-Resources 在自然语言处理领域&#xff0c;使用Hugging Face的Transformers库进行模型训练已经成为主流。然而&#xff0c;随着模型复…

智能理解 PPT 内容,快速生成讲解视频

当我们想根据一版 PPT 制作出相对应的解锁视频时&#xff0c;从撰写解锁词&#xff0c;录制音频到剪辑视频&#xff0c;每一个环节都需要投入大量的时间和精力&#xff0c;本方案将依托于阿里云函数计算 FC 和百炼模型服务&#xff0c;实现从 PPT 到视频的全自动转换&#xff0…

如何使用Gemini模型,国内如何订阅购买Gemini Pro的教程,Gemini Pro 免费试用操作步骤, 谷歌 aistudio 使用入口

最近的榜首又被Gemini给霸占了&#xff0c;很多童鞋想要体验一翻 Gemini免费库模型更新了 Gemini2.0向所有人开放了&#xff01;使用了真香 目前呢2.0flash和Gemini-2.0-Flash-Thinking-Exp、Gemini-2.0-Flash-Thinking-Exp-with-apps已经免费给所有注册用户开放了&#xff0c…

【学术投稿】第五届计算机网络安全与软件工程(CNSSE 2025)

重要信息 官网&#xff1a;www.cnsse.org 时间&#xff1a;2025年2月21-23日 地点&#xff1a;中国-青岛 简介 第五届计算机网络安全与软件工程&#xff08;CNSSE 2025&#xff09;将于2025年2月21-23日在中国-青岛举行。CNSSE 2025专注于计算机网络安全、软件工程、信号处…

Python----Python高级(网络编程:网络基础:发展历程,IP地址,MAC地址,域名,端口,子网掩码,网关,URL,DHCP,交换机)

一、网络 早期的计算机程序都是在本机上运行的&#xff0c;数据存储和处理都在同一台机器上完成。随着技术的发展&#xff0c;人 们开始有了让计算机之间相互通信的需求。例如安装在个人计算机上的计算器或记事本应用&#xff0c;其运行环 境仅限于个人计算机内部。这种设置虽然…

即梦(Dreamina)技术浅析(六):多模态生成模型

多模态生成模型是即梦(Dreamina)的核心技术之一,旨在结合文本和图像信息,生成更符合用户需求的视觉内容。多模态生成模型通过整合不同类型的数据(如文本和图像),能够实现更丰富、更精准的生成效果。 1. 基本原理 1.1 多模态生成模型概述 多模态生成模型的目标是结合不…

全程Kali linux---CTFshow misc入门(38-50)

第三十八题&#xff1a; ctfshow{48b722b570c603ef58cc0b83bbf7680d} 第三十九题&#xff1a; 37换成1&#xff0c;36换成0&#xff0c;就得到长度为287的二进制字符串&#xff0c;因为不能被8整除所以&#xff0c;考虑每7位转换一个字符&#xff0c;得到flag。 ctfshow{5281…

学习数据结构(6)单链表OJ上

1.移除链表元素 解法一&#xff1a;&#xff08;我的做法&#xff09;在遍历的同时移除&#xff0c;代码写法比较复杂 解法二&#xff1a;创建新的链表&#xff0c;遍历原链表&#xff0c;将非val的节点尾插到新链表&#xff0c;注意&#xff0c;如果原链表结尾是val节点需要将…

x64、aarch64、arm与RISC-V64:详解四种处理器架构

x64、aarch64、arm与RISC-V64:详解四种处理器架构 x64架构aarch64架构ARM架构RISC-V64架构总结与展望在计算机科学领域,处理器架构是构建计算机系统的基石,它决定了计算机如何执行指令、管理内存和处理数据。x64、aarch64、arm与RISC-V64是当前主流的四种处理器架构,它们在…

LVSNAT服务搭建

LVSNAT实验环境搭建 在虚拟机上&#xff0c;我的NAT模式ip划分为&#xff1a;172.25.254.0 仅主机模式IP为&#xff1a;192.168.0.0 拓补图如下 配置服务&#xff1a;LVS服务端添加两个网卡&#xff0c;分别为NAT模式和仅主机模式 LVS服务端配置&#xff1a; systemctl st…

【实用技能】如何借助3D文档控件Aspose.3D, 在Java中无缝制作 3D 球体

概述 创建 3D 球体是 3D 图形设计的一个基本方面。无论您是在开发游戏、模拟还是可视化&#xff0c;无缝创建 3D 球体模型的能力都至关重要。Aspose.3D通过提供强大的 3D 图形 SDK 在各个行业中发挥着重要作用。它允许开发人员轻松创建、操作和转换 3D 模型。此 SDK 对于希望将…

两台1200之间的S7通信

1.组态两个PLC&#xff0c;分别开启时钟&#xff0c;勾选允许远方的PUT/GET通信 2.网络视图把两台PLC连接起来 3.在第一台PLC中建立DB1&#xff0c;建立一个位&#xff0c;作为发送&#xff0c;调用PUT指令 点开始组态&#xff0c;进行连接 ADDR收 SD发 一条指令即可 4.在第…

Android studio怎么创建assets目录

在Android Studio中创建assets文件夹是一个简单的步骤&#xff0c;通常用于存储不需要编译的资源文件&#xff0c;如文本文件、图片、音频等 main文件夹&#xff0c;邮件new->folder-assets folder

数据结构 day01

大纲 1.数据结构 2.算法 3.线性表 顺序表&#xff1a;数组 链表&#xff1a;单向链表&#xff0c;单向循环链表&#xff0c;双向链表&#xff0c;双向循环链表 栈&#xff1a;顺序栈&#xff0c;链式栈 队列&#xff1a;顺序队列&#xff0c;链式队列 4.树&#xff1a;特性…

Linux 系统搭建 Python 开发环境全流程

Linux 系统搭建 Python 开发环境全流程 Python 解释器下载 Pycharm 对应版本解压安装包进入解压后的目录启动 Pycharm创建桌面快捷方式&#xff08;可选&#xff09;Pycharm 配置创建第一个目录第一个程序运行补充 Python 解释器 确保电脑里已经有了python解释器&#xff0c;没…

SQL Server查询计划操作符(7.3)——查询计划相关操作符(6)

7.3. 查询计划相关操作符 48)Key Lookup:该操作符对一个有簇索引的表进行书签查找。参数列包含簇索引的名字和用于查找簇索引中数据行的簇键。该操作符总是伴随一个Nested Loops操作符。如果其参数列中出现WITH PREFETCH子句,则查询处理器已决定使用异步预取(预读,read-ah…

如何通过 ESPN API 获取 NBA 球队的赛程表

对于 NBA 爱好者和开发者来说&#xff0c;通过 API 获取球队赛程表是一项非常实用的功能&#xff0c;尤其是如果你正在构建一个应用或网站&#xff0c;需要自动化获取比赛安排的情况下。今天&#xff0c;我将为大家介绍如何通过 ESPN 提供的 API 获取 NBA 球队的赛程表。 1. ES…

LMM-3DP:集成 LMM 规划器和 3D 技能策略实现可泛化操作

25年1月来自UCSD的论文“Integrating LMM Planners and 3D Skill Policies for Generalizable Manipulation”。 大型多模态模型 (LMM) 的视觉推理能力和 3D 特征场语义丰富性的最新进展&#xff0c;拓展了机器人能力的范围。这些发展对于弥合 LMM 高级推理与利用 3D 特征场低…

idea整合deepseek实现AI辅助编程

1.File->Settings 2.安装插件codegpt 3.注册deepseek开发者账号&#xff0c;DeepSeek开放平台 4.按下图指示创建API KEY 5.回到idea配置api信息&#xff0c;File->Settings->Tools->CodeGPT->Providers->Custom OpenAI API key填写deepseek的api key Chat…