2026/1/12 7:54:30
网站建设
项目流程
如何在360网页上做公司网站,网页制作基础入门教程,有人用wordpress做企业,品牌设计网站有哪些一、先给结论#xff08;一句话记死#xff09;可扩展哈希是「普通哈希表」的「扩容升级版」#xff0c;完全解决了普通哈希表「扩容卡顿、效率极低」的致命缺点#xff0c;其余功能#xff08;增删改查#xff09;和普通哈希表完全一样#xff0c;上手零门槛。二、先回…一、先给结论一句话记死可扩展哈希是「普通哈希表」的「扩容升级版」完全解决了普通哈希表「扩容卡顿、效率极低」的致命缺点其余功能增删改查和普通哈希表完全一样上手零门槛。二、先回顾「普通哈希表」核心痛点普通哈希表数组链表Rehash是最基础的哈希表实现日常用没问题但一扩容就「拉胯」3个核心痛点新手也能直观感受到❌ 痛点1扩容必「全量搬家」数据越多越卡普通哈希表扩容时会创建一个全新的更大数组然后把所有旧数据全部重新计算索引、全部搬到新数组。✅ 大白话举例你租了个10平的小房子哈希表容量10东西多了要换30平的大房子 → 必须把衣柜、床、冰箱、所有东西全部打包、搬运、重新摆放过程中完全没法正常生活。数据量越大东西越多搬家耗时越久程序会直接「卡顿、卡死」。❌ 痛点2扩容是「一刀切」全局整体变大普通哈希表的容量是全局统一的扩容时必须「整体翻倍」比如11→23、23→47哪怕只有1个桶的数据满了也要把所有桶一起扩容属于「小题大做、浪费资源」。✅ 大白话举例小区里只有1栋楼的住户住满了物业却要求整个小区所有楼栋全部推倒重建、扩大面积其他空楼栋也跟着折腾纯纯浪费人力物力。❌ 痛点3哈希冲突越积越多查询变慢普通哈希表用「链表挂接」解决冲突一个桶里多个数据扩容不及时的话链表会变得越来越长 → 原本O(1)的查询会慢慢变成O(n)查数据越来越慢。三、可扩展哈希的「3大核心改进」可扩展哈希的所有设计全部精准解决普通哈希表的3个痛点核心改进只有3点✅ 改进1扩容从「全局搬家」→「局部搬家」解决「扩容卡顿」✅ 核心变化哪个桶满了只动哪个桶其余桶完全不动普通哈希表是「一动全动」可扩展哈希是「一动其余全不动」这是最核心、最关键的改进扩容时只迁移「满桶」里的少量数据其余数据完全不用动程序全程不卡顿、无延迟。✅ 改进2扩容从「全局一刀切」→「局部按需分裂」解决「资源浪费」✅ 核心变化容量按需增长不搞「整体翻倍」哪个桶不够用就「分裂」哪个桶可扩展哈希没有「全局容量」的概念而是用「桶」作为最小存储单元每个桶独立管理容量桶没满 → 正常存数据不做任何操作桶存满 → 仅把这个桶「分裂」成2个新桶仅此而已。✅ 改进3冲突从「链表挂接」→「桶分裂化解」解决「查询变慢」✅ 核心变化用「桶分裂」替代「链表挂接」永远没有超长链表查询速度永远是O(1)普通哈希表冲突的数据往链表上挂链表越长查询越慢可扩展哈希没有链表每个桶里用数组存数据桶满了就分裂数据会自动分散到新桶里永远不会出现「一个桶里堆一堆数据」的情况查询速度永远保持最快。四、普通哈希 vs 可扩展哈希对比维度新手关心的点「普通哈希表」可扩展哈希改造版谁更优扩容时是否卡顿✅ 必卡顿全量迁移所有数据❌ 不卡顿仅迁移满桶数据✔️ 可扩展哈希扩容是否浪费资源✅ 浪费全局整体扩容❌ 不浪费局部按需扩容✔️ 可扩展哈希解决冲突的方式✅ 链表挂接越长越慢❌ 桶分裂永远均匀✔️ 可扩展哈希查询速度稳定性✅ 不稳定链表长则变慢✅ 绝对稳定永远O(1)✔️ 可扩展哈希实现难度✅ 超简单新手友好✅ 稍复杂✔️ 普通哈希上手数据量大时性能✅ 越来越差✅ 始终稳定✔️ 可扩展哈希五、可扩展哈希2个核心概念不用纠结复杂原理只需要记住2个最核心的概念就能彻底理解可扩展哈希和普通哈希的区别也会更清晰✅ 概念1「桶」—— 可扩展哈希的「最小存储单元」桶 一个「固定大小的小数组」比如最多存4个键值对所有数据都存在「桶」里一个桶存满了就「分裂」成2个桶普通哈希表的「桶」是「被动承载数据」可扩展哈希的「桶」是「主动管理自己」。✅ 概念2「目录」—— 可扩展哈希的「导航地图」目录 一张「索引表」记录「每个哈希值 → 对应哪个桶」你要查/存数据时先查目录 → 找到对应的桶 → 直接操作桶里的数据桶分裂时只需要更新目录里的「一条导航记录」其余记录完全不变效率极高。✅ 选「普通哈希表」如果 你是新手只想快速上手、理解哈希表基础原理 数据量不大几百/几千条数据扩容卡顿完全可以接受 追求「代码简单、容易调试、出错率低」。✅ 选「可扩展哈希」如果 你需要存储大量数据几万/几十万条担心扩容卡顿 要求程序运行稳定、查询速度快不能有性能抖动 想学习「工业级高性能哈希表」的实现思路面试高频考点。七、核心原理极简总结普通哈希表扩容全量迁移、冲突挂链表、数据越多越卡可扩展哈希扩容局部分裂、无链表冲突、性能永远稳定可扩展哈希的核心优势解决了普通哈希表「扩容代价高」的致命问题是更适合大数据量的哈希表实现。最终总结✅ 可扩展哈希 对 普通哈希表本质就是「扩容方式的升级」✅ 核心改进只有1个把「全局全量扩容」改成「局部按需扩容」其余所有优点不卡顿、不浪费、速度快都是这个改进带来的✅ 你不用纠结底层复杂逻辑直接用我改好的可扩展哈希代码用法和你原来的普通哈希表完全一样增删改查接口一个没动上手零成本代码头文件#ifndef __EXTENDIBLE_HASH_H__ #define __EXTENDIBLE_HASH_H__ #include stdio.h #include stdlib.h #include string.h #include stdbool.h #define INITIAL_GLOBAL_K 2 // 初始全局前缀位数 (目录长度2^k4) #define BUCKET_CAPACITY 4 // 每个桶的最大容量可自定义 #define MAX_KEY_LEN 64 // 键的最大长度 // 1. 键值对结构体桶内存储的最小单元 typedef struct { char key[MAX_KEY_LEN]; int value; } KVNode; // 2. 哈希桶结构体可扩展哈希核心最小存储单元满了则分裂 typedef struct HashBucket { KVNode* entries; // 桶内的键值对数组 int size; // 桶内已存储元素数量 int local_k; // 桶的局部前缀位数关键实现局部分裂 } HashBucket; // 3. 可扩展哈希表主结构体替代你原有的HashTable typedef struct ExtendibleHashTable { HashBucket** dir; // 全局目录表核心存桶的指针实现映射 int global_k; // 全局前缀位数目录长度 2^global_k int bucket_cap; // 单个桶的最大容量 int total_size; // 哈希表总元素数量 } ExtendibleHashTable; // 1. 创建可扩展哈希表 ExtendibleHashTable* createHashTable(); // 2. 销毁可扩展哈希表 void destroyHashTable(ExtendibleHashTable* table); // 3. 插入键值对存在则更新 int hashInsert(ExtendibleHashTable* table, const char* key, int value); // 4. 查找键对应的值返回NULL表示不存在 int* hashSearch(ExtendibleHashTable* table, const char* key); // 5. 删除键值对 int hashDelete(ExtendibleHashTable* table, const char* key); // 6. 获取哈希表总元素数 int getSize(ExtendibleHashTable* table); // 7. 判断哈希表是否为空 int isEmpty(ExtendibleHashTable* table); // 8. 打印哈希表完整结构桶目录数据 void printHashTable(ExtendibleHashTable* table); // 9. 清空哈希表所有数据 void clearHashTable(ExtendibleHashTable* table); // 10. 获取当前负载因子总元素/总桶容量 double getLoadFactor(ExtendibleHashTable* table); #endif // __EXTENDIBLE_HASH_H__源文件#include 可扩展哈希.h // 1. djb2字符串哈希函数 static unsigned long hashString(const char* str) { unsigned long hash 5381; int c; while ((c *str)) { hash ((hash 5) hash) c; // hash * 33 c } return hash; } // 2. 判断是否为质数 static int isPrime(int n) { if (n 1) return 0; if (n 3) return 1; if (n % 2 0 || n % 3 0) return 0; for (int i 5; i * i n; i 6) { if (n % i 0 || n % (i 2) 0) return 0; } return 1; } // 3. 获取下一个质数 static int nextPrime(int n) { while (!isPrime(n)) { n; } return n; } /** * 创建新的哈希桶 * return 初始化完成的桶指针 */ static HashBucket* createBucket() { HashBucket* bucket (HashBucket*)malloc(sizeof(HashBucket)); if (bucket NULL) { printf(createBucket err: 内存分配失败\n); return NULL; } bucket-entries (KVNode*)malloc(sizeof(KVNode) * BUCKET_CAPACITY); if (bucket-entries NULL) { free(bucket); printf(createBucket err: 桶数据区分配失败\n); return NULL; } bucket-size 0; bucket-local_k INITIAL_GLOBAL_K; // 初始局部k 全局k return bucket; } /** * 释放单个哈希桶内存 */ static void freeBucket(HashBucket* bucket) { if (bucket) { free(bucket-entries); free(bucket); } } /** * 计算目录索引可扩展哈希核心哈希值高位前缀映射 * param hash 字符串哈希值 * param k 前缀位数全局k/局部k * return 目录表的索引位置 */ static int getDirIndex(unsigned long hash, int k) { // 取哈希值的高k位转为十进制索引32位无符号数 return (int)(hash (32 - k)); } /** * 桶分裂可扩展哈希核心替代你的全量Rehash * param table 哈希表指针 * param old_bucket 待分裂的桶 * param dir_idx 桶在目录中的索引 */ static void splitBucket(ExtendibleHashTable* table, HashBucket* old_bucket, int dir_idx) { printf(触发桶分裂: 全局k%d → %d | 桶局部k%d → %d\n, table-global_k, table-global_k 1, old_bucket-local_k, old_bucket-local_k 1); // 1. 旧桶局部k1创建新桶 int old_local_k old_bucket-local_k; old_bucket-local_k; HashBucket* new_bucket createBucket(); new_bucket-local_k old_bucket-local_k; // 2. 迁移旧桶数据重新分配到旧桶/新桶 int idx 0; while (idx old_bucket-size) { KVNode* entry old_bucket-entries[idx]; unsigned long hash hashString(entry-key); // 用新的局部k计算索引判断归属 int new_idx getDirIndex(hash, old_bucket-local_k); // 掩码区分新旧桶 (2^(old_local_k)) int mask 1 old_local_k; if ((new_idx mask) ! 0) { // 数据归新桶移过去并删除旧桶中的数据 memcpy(new_bucket-entries[new_bucket-size], entry, sizeof(KVNode)); new_bucket-size; // 覆盖旧桶当前位置前移 old_bucket-entries[idx] old_bucket-entries[--old_bucket-size]; } else { idx; // 数据归旧桶指针后移 } } // 3. 扩展目录表全局k1目录长度翻倍 if (old_bucket-local_k table-global_k) { int old_dir_len 1 table-global_k; table-global_k; int new_dir_len 1 table-global_k; // 重新分配目录内存 table-dir (HashBucket**)realloc(table-dir, sizeof(HashBucket*) * new_dir_len); if (table-dir NULL) { printf(splitBucket err: 目录扩容失败\n); return; } // 目录映射旧索引复制新索引指向对应桶 for (int i old_dir_len; i new_dir_len; i) { table-dir[i] table-dir[i ^ old_dir_len]; } } // 4. 更新目录映射新索引指向新桶 int new_local_k old_bucket-local_k; int dir_mask (1 new_local_k) - 1; int base_idx dir_idx (~dir_mask); for (int i 0; i (1 new_local_k); i) { int curr_idx base_idx | i; unsigned long hash hashString(old_bucket-entries[0].key); if (getDirIndex(hash, new_local_k) curr_idx % (1 new_local_k)) { table-dir[curr_idx] old_bucket; } else { table-dir[curr_idx] new_bucket; } } printf(桶分裂完成: 旧桶剩余%d个元素 | 新桶新增%d个元素\n, old_bucket-size, new_bucket-size); } /** * 检查桶是否已满满则触发分裂 */ static void checkSplit(ExtendibleHashTable* table, HashBucket* bucket, int dir_idx) { if (bucket-size table-bucket_cap) { splitBucket(table, bucket, dir_idx); } } // 1. 创建可扩展哈希表替代你的原函数 ExtendibleHashTable* createHashTable() { ExtendibleHashTable* table (ExtendibleHashTable*)malloc(sizeof(ExtendibleHashTable)); if (table NULL) { printf(createHashTable err: 内存分配失败\n); return NULL; } table-global_k INITIAL_GLOBAL_K; table-bucket_cap BUCKET_CAPACITY; table-total_size 0; // 初始目录长度 2^global_k int init_dir_len 1 table-global_k; table-dir (HashBucket**)malloc(sizeof(HashBucket*) * init_dir_len); if (table-dir NULL) { free(table); printf(createHashTable err: 目录分配失败\n); return NULL; } // 初始化目录所有索引指向同一个桶 HashBucket* init_bucket createBucket(); for (int i 0; i init_dir_len; i) { table-dir[i] init_bucket; } printf(创建可扩展哈希表成功 | 初始全局k%d | 目录长度%d | 单桶容量%d\n, table-global_k, init_dir_len, table-bucket_cap); return table; } // 2. 销毁可扩展哈希表替代你的原函数 void destroyHashTable(ExtendibleHashTable* table) { if (table NULL) return; clearHashTable(table); free(table-dir); free(table); printf(可扩展哈希表已销毁\n); } // 3. 插入键值对核心支持更新自动分裂 int hashInsert(ExtendibleHashTable* table, const char* key, int value) { if (table NULL || key NULL || strlen(key) MAX_KEY_LEN) return 0; unsigned long hash hashString(key); int dir_idx getDirIndex(hash, table-global_k); HashBucket* bucket table-dir[dir_idx]; // 步骤1检查键是否存在存在则更新值 for (int i 0; i bucket-size; i) { if (strcmp(bucket-entries[i].key, key) 0) { printf(更新键: %s (旧值: %d → 新值: %d)\n, key, bucket-entries[i].value, value); bucket-entries[i].value value; return 1; } } // 步骤2键不存在检查桶容量满则分裂 checkSplit(table, bucket, dir_idx); // 分裂后桶可能变化重新获取 dir_idx getDirIndex(hash, table-global_k); bucket table-dir[dir_idx]; // 步骤3插入新键值对 KVNode* new_entry bucket-entries[bucket-size]; strncpy(new_entry-key, key, MAX_KEY_LEN - 1); new_entry-key[MAX_KEY_LEN - 1] \0; new_entry-value value; bucket-size; table-total_size; printf(插入键值对: [%s: %d] | 目录索引: %d | 桶内位置: %d\n, key, value, dir_idx, bucket-size - 1); return 1; } // 4. 查找键对应的值和你原函数用法一致 int* hashSearch(ExtendibleHashTable* table, const char* key) { if (table NULL || key NULL) return NULL; unsigned long hash hashString(key); int dir_idx getDirIndex(hash, table-global_k); HashBucket* bucket table-dir[dir_idx]; for (int i 0; i bucket-size; i) { if (strcmp(bucket-entries[i].key, key) 0) { return bucket-entries[i].value; } } return NULL; } // 5. 删除键值对和你原函数用法一致 int hashDelete(ExtendibleHashTable* table, const char* key) { if (table NULL || key NULL) return 0; unsigned long hash hashString(key); int dir_idx getDirIndex(hash, table-global_k); HashBucket* bucket table-dir[dir_idx]; for (int i 0; i bucket-size; i) { if (strcmp(bucket-entries[i].key, key) 0) { // 覆盖删除最后一个元素前移 bucket-entries[i] bucket-entries[--bucket-size]; table-total_size--; printf(删除键值对: [%s: %d]\n, key, value); return 1; } } printf(删除失败: 键 %s 不存在\n, key); return 0; } // 6. 获取哈希表大小和你原函数一致 int getSize(ExtendibleHashTable* table) { return (table NULL) ? 0 : table-total_size; } // 7. 判断哈希表是否为空和你原函数一致 int isEmpty(ExtendibleHashTable* table) { return (table NULL) ? 1 : (table-total_size 0); } // 8. 打印哈希表增强版打印目录桶数据 void printHashTable(ExtendibleHashTable* table) { if (table NULL) { printf(哈希表为空指针\n); return; } if (isEmpty(table)) { printf(哈希表为空\n); return; } int dir_len 1 table-global_k; printf(\n 可扩展哈希表详情 \n); printf(全局k: %d | 目录长度: %d | 总元素数: %d | 单桶容量: %d | 负载因子: %.2f\n, table-global_k, dir_len, table-total_size, table-bucket_cap, getLoadFactor(table)); printf(\n【全局目录映射表】\n); for (int i 0; i dir_len; i) { printf(目录[%2d] → 桶地址: %p | 桶局部k: %d | 桶内元素: %d\n, i, table-dir[i], table-dir[i]-local_k, table-dir[i]-size); } printf(\n【所有桶数据详情】\n); for (int i 0; i dir_len; i) { HashBucket* bucket table-dir[i]; // 去重打印桶同一个桶会被多个目录索引指向 static HashBucket* printed_buckets[1024]; static int printed_cnt 0; int is_printed 0; for (int j 0; j printed_cnt; j) { if (printed_buckets[j] bucket) { is_printed 1; break; } } if (is_printed) continue; printed_buckets[printed_cnt] bucket; printf(桶%p (局部k%d, 元素数%d): , bucket, bucket-local_k, bucket-size); for (int j 0; j bucket-size; j) { printf([%s:%d], bucket-entries[j].key, bucket-entries[j].value); if (j bucket-size - 1) printf( → ); } printf(\n); } printf(\n); } // 9. 清空哈希表和你原函数一致 void clearHashTable(ExtendibleHashTable* table) { if (table NULL) return; int dir_len 1 table-global_k; // 去重释放桶 HashBucket* freed_buckets[1024]; int freed_cnt 0; for (int i 0; i dir_len; i) { HashBucket* bucket table-dir[i]; int is_freed 0; for (int j 0; j freed_cnt; j) { if (freed_buckets[j] bucket) { is_freed 1; break; } } if (!is_freed) { freed_buckets[freed_cnt] bucket; freeBucket(bucket); } } // 重建初始状态 HashBucket* init_bucket createBucket(); for (int i 0; i dir_len; i) { table-dir[i] init_bucket; } table-total_size 0; table-global_k INITIAL_GLOBAL_K; printf(可扩展哈希表已清空恢复初始状态\n); } // 10. 获取负载因子和你原函数一致 double getLoadFactor(ExtendibleHashTable* table) { if (table NULL) return 0.0; int dir_len 1 table-global_k; int total_bucket_cap dir_len * table-bucket_cap; return (total_bucket_cap 0) ? 0.0 : (double)table-total_size / total_bucket_cap; }