從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.pswp.cn/news/894967.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/894967.shtml
英文地址,請注明出處:http://en.pswp.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,…