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

[实战] 二分查找与哈希表查找:原理、对比与C语言实现(附完整C代码)

二分查找与哈希表查找:原理、对比与C语言实现

一、引言

在计算机科学中,高效的数据查找是核心问题之一。本文深入解析两种经典查找算法:二分查找哈希表查找,从算法原理、时间复杂度、适用场景到完整C语言实现,提供系统化的对比与实践指南。


二、算法原理详解

1. 二分查找 (Binary Search)

核心思想

通过有序数据集的中间元素与目标值的比较,将搜索范围缩小一半,重复此过程直至找到目标或范围为空。

算法流程
  1. 初始化左右边界 left=0, right=n-1
  2. 循环直到 left > right
    • 计算中点 mid = left + (right-left)/2
    • arr[mid] == target,返回索引
    • arr[mid] < target,调整左边界 left = mid + 1
    • 否则调整右边界 right = mid - 1
  3. 返回未找到标志 -1
时间复杂度
  • 最优:O(1)
  • 平均/最差:O(log n)
空间复杂度
  • 迭代实现:O(1)
  • 递归实现:O(log n)(栈空间)

2. 哈希表查找 (Hash Table)

核心思想

通过哈希函数将键(key)映射到存储位置,实现近似O(1)时间复杂度的查找。

关键组件
  1. 哈希函数:将键转换为数组索引
    (示例:hash(key) = key % capacity
  2. 冲突解决
    • 链地址法:每个槽位存储链表
    • 开放寻址法:线性探测/二次探测
算法流程
  1. 计算键的哈希值 hash_index = hash(key)
  2. 根据冲突解决策略查找目标:
    • 链地址法:遍历链表
    • 开放寻址法:按探测序列查找空槽
  3. 找到返回数据,未找到返回特定标志
时间复杂度
  • 最优:O(1)
  • 最差:O(n)(所有键冲突时)
空间复杂度
  • 取决于负载因子 α = n/m
    通常为 O(n)

三、算法对比分析

特性二分查找哈希表查找
数据结构要求必须有序无要求
查找时间复杂度O(log n)O(1)(平均)
插入/删除效率O(n)(需维护有序性)O(1)(平均)
空间复杂度O(1)O(n)(含指针/探测开销)
实现复杂度简单较高(需处理冲突/扩容)
适用场景静态或低频更新数据集高频更新数据集

四、使用场景选择指南

适用二分查找的场景:

  1. 数据预先有序且不频繁修改
  2. 内存受限环境(无额外指针开销)
  3. 需要范围查询(如找最小大于某值的元素)

适用哈希表的场景:

  1. 需要极速查找且数据动态变化
  2. 不依赖数据顺序
  3. 允许牺牲空间换时间

五、C语言完整实现

1. 二分查找实现

迭代版本
int binarySearch(int arr[], int size, int target) {int left = 0, right = size - 1;while (left <= right) {int mid = left + (right - left) / 2;if (arr[mid] == target) return mid;if (arr[mid] < target) left = mid + 1;else right = mid - 1;}return -1;
}
递归版本
int binarySearchRecursive(int arr[], int left, int right, int target) {if (left > right) return -1;int mid = left + (right - left) / 2;if (arr[mid] == target) return mid;if (arr[mid] < target) return binarySearchRecursive(arr, mid + 1, right, target);else return binarySearchRecursive(arr, left, mid - 1, target);
}

2. 哈希表实现(链地址法)

#include <stdio.h>
#include <stdlib.h>#define INIT_CAPACITY 10
#define LOAD_FACTOR 0.75typedef struct Node {int key;int value;struct Node* next;
} Node;typedef struct {Node** buckets;int size;int capacity;
} HashTable;// 初始化哈希表
HashTable* createHashTable() {HashTable* ht = malloc(sizeof(HashTable));ht->capacity = INIT_CAPACITY;ht->size = 0;ht->buckets = calloc(ht->capacity, sizeof(Node*));return ht;
}// 哈希函数
int hash(int key, int capacity) {return key % capacity;
}// 扩容操作
void resize(HashTable* ht) {int new_cap = ht->capacity * 2;Node** new_buckets = calloc(new_cap, sizeof(Node*));for (int i = 0; i < ht->capacity; i++) {Node* curr = ht->buckets[i];while (curr) {Node* next = curr->next;int idx = hash(curr->key, new_cap);curr->next = new_buckets[idx];new_buckets[idx] = curr;curr = next;}}free(ht->buckets);ht->buckets = new_buckets;ht->capacity = new_cap;
}// 插入键值对
void put(HashTable* ht, int key, int value) {if ((double)ht->size / ht->capacity > LOAD_FACTOR) {resize(ht);}int idx = hash(key, ht->capacity);Node* newNode = malloc(sizeof(Node));newNode->key = key;newNode->value = value;newNode->next = ht->buckets[idx];ht->buckets[idx] = newNode;ht->size++;
}// 查找键
int get(HashTable* ht, int key) {int idx = hash(key, ht->capacity);Node* curr = ht->buckets[idx];while (curr) {if (curr->key == key) return curr->value;curr = curr->next;}return -1; // 未找到
}

六、验证代码

1. 二分查找验证

void testBinarySearch() {int arr[] = {2,5,8,12,16,23,38,56,72,91};int size = sizeof(arr)/sizeof(arr[0]);// 测试存在元素printf("测试存在元素:\n");printf("查找23: 索引%d\n", binarySearch(arr, size, 23));  // 应输出5printf("查找2: 索引%d\n", binarySearch(arr, size, 2));    // 应输出0// 测试不存在元素printf("\n测试不存在元素:\n");printf("查找99: 索引%d\n", binarySearch(arr, size, 99));  // 应输出-1
}

执行结果:

=== 二分查找测试 ===
查找23 => 索引5 (期望5)
查找2  => 索引0 (期望0)
查找99 => 索引-1 (期望-1)

2. 哈希表验证

void testHashTable() {HashTable* ht = createHashTable();put(ht, 1001, 50);put(ht, 2002, 80);put(ht, 3003, 95);printf("测试哈希表查找:\n");printf("查找1001: 值%d\n", get(ht, 1001));  // 应输出50printf("查找3003: 值%d\n", get(ht, 3003));  // 应输出95printf("查找9999: 值%d\n", get(ht, 9999));  // 应输出-1
}
=== 哈希表测试 ===
查找1001 => 值50 (期望50)
查找3003 => 值95 (期望95)
查找9999 => 值-1 (期望-1)

执行结果:

七、性能对比测试

#include <time.h>#define DATA_SIZE 1000000000void performanceTest() {// 准备有序数组int* sortedArr = malloc(DATA_SIZE * sizeof(int));for (int i = 0; i < DATA_SIZE; i++) {sortedArr[i] = i * 2;}// 准备哈希表数据HashTable* ht = createHashTable();for (int i = 0; i < DATA_SIZE; i++) {put(ht, i * 2, i);}clock_t start, end;// 测试二分查找start = clock();binarySearch(sortedArr, DATA_SIZE, DATA_SIZE * 2 - 2);end = clock();printf("二分查找耗时: %f秒\n", (double)(end - start)/CLOCKS_PER_SEC);// 测试哈希查找start = clock();get(ht, DATA_SIZE * 2 - 2);end = clock();printf("哈希查找耗时: %f秒\n", (double)(end - start)/CLOCKS_PER_SEC);
}

对比结果显示:哈希表查找比二分法查找在大量数据时,优势明显。
1000000数据对比情况,不分胜负

=== 性能对比测试(数据量:1000000)===
二分查找耗时: 0.000000秒 (结果:999999)
哈希查找耗时: 0.000000秒 (结果:999999)

100000000数据对比情况,哈希优势明显

=== 性能对比测试(数据量:100000000)===
二分查找耗时: 0.005000秒 (结果:99999999)
哈希查找耗时: 0.000000秒 (结果:99999999)

八、结论

  1. 二分查找在有序数据集上表现出色,适合静态数据场景
  2. 哈希表在动态数据环境下提供最优查找性能,但需要处理冲突与扩容
  3. 实际选择需综合考虑数据特征、操作频率和资源限制

通过本文的代码实现与对比测试,开发者可以根据具体需求选择最适合的查找策略。两种算法各有千秋,理解其底层原理是优化系统性能的关键。

完整实现和测试代码免费免分下载


相关文章:

[实战] 二分查找与哈希表查找:原理、对比与C语言实现(附完整C代码)

二分查找与哈希表查找&#xff1a;原理、对比与C语言实现 一、引言 在计算机科学中&#xff0c;高效的数据查找是核心问题之一。本文深入解析两种经典查找算法&#xff1a;二分查找与哈希表查找&#xff0c;从算法原理、时间复杂度、适用场景到完整C语言实现&#xff0c;提供…...

游戏引擎学习第215天

总结并为今天做铺垫 今天的工作内容是解决调试系统中的一个小问题。昨天我们已经完成了大部分的调试系统工作&#xff0c;但还有一个小部分没有完全处理&#xff0c;那就是关于如何层次化组织数据的问题。我们遇到的一个问题是&#xff0c;演示代码中仍有一个尚未解决的部分&a…...

【Redis】redis事物与管道

Redis 事务&#xff08;Transaction&#xff09; 事务概念 事务&#xff1a;是一组操作的集合&#xff0c;是不可分割的工作单元。Redis 事务特点&#xff1a; 一个事务可以一次执行多个命令。所有命令都被顺序化&#xff0c;形成一个队列。所有命令在执行 EXEC 时一次性、顺…...

Django信号使用完全指南示例

推荐超级课程: 本地离线DeepSeek AI方案部署实战教程【完全版】Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战目录 **引言:****先决条件:****目录:****1. 什么是Django信号?****2:设置你的Django项目****2.1. 安装Django**2.2. 创建一个Django项…...

DeepSeek BLEU和ROUGE(Recall)的计算

以下是 BLEU Score (Bilingual ​Evaluation ​Understudy)和 ROUGE Score(​Recall-Oriented ​Understudy for ​Gisting ​Evaluation) 的原生Python实现&#xff08;不依赖第三方库&#xff09;&#xff0c;通过分步计算逻辑和示例详细说明。 一、BLEU Score 实现 核心逻辑…...

vulkanscenegraph显示倾斜模型(5.9)-vsg中vulkan资源的编译

前言 上一章深入剖析了GPU资源内存及其管理&#xff0c;vsg中为了提高设备内存的利用率&#xff0c;同时减少内存(GPU)碎片&#xff0c;采用GPU资源内存池机制(vsg::MemoryBufferPools)管理逻辑缓存(VkBuffer)与物理内存(VkDeviceMemory)。本章将深入vsg中vulkan资源的编译(包含…...

今日行情明日机会——20250409

今日行情还需要考虑关税对抗~ 2025年4月8日涨停的主要行业方向分析 1. 军工&#xff08;19家涨停&#xff09; 细分领域&#xff1a;国防装备、航空航天、军民融合。催化因素&#xff1a;国家安全战略升级、国防预算增加、重大军工项目落地预期。 2. 免税&#xff08;15家涨…...

XHR、FetchAxios详解网络相关大片文件上传下载

以下是 XHR(XMLHttpRequest) 与 Fetch API 的全面对比分析,涵盖语法、功能、兼容性等核心差异: 一、语法与代码风格 XHR(基于事件驱动) 需要手动管理请求状态(如 onreadystatechange 事件)和错误处理,代码冗长且易出现回调地狱。 const xhr = new XMLHttpRequest(); x…...

Python基础总结(四)之元组

文章目录 一、元组格式二、元组操作2.1 转换元组 与 列表一样&#xff0c;元组也是序列&#xff0c;唯一的区别在于元组是不能修改的&#xff0c;与字符串一样。 一、元组格式 元组的创建方式很简单&#xff0c;秩序用逗号将元素隔开就能自动创建一个元组 示例&#xff1a; …...

系统分析师(六)-- 计算机网络

概述 TCP/IP 协议族 DNS DHCP 网络规划与设计 逻辑网络设计 物理网络设计 题目 层次化网络设计 网络冗余设计 综合布线系统 IP地址 网络接入技术 其他网络技术应用 物联网...

【前端】【React】useCallback的作用与使用场景总结

一、useCallback 的作用与使用场景总结 useCallback 是 React 提供的一个 Hook&#xff0c;用于缓存函数的引用&#xff0c;避免因为组件重新渲染而导致函数地址发生变化。它返回一个记忆&#xff08;memoized&#xff09;后的回调函数&#xff0c;只有当依赖项发生变化时才会…...

Qwen2.5-VL Technical Report 论文翻译和理解

一、TL&#xff1b;DR Qwen2.5-VL是QwenVL的最新模型&#xff0c;在视觉识别、精准目标定位、稳健文档解析以及长视频理解等方面实现了重大突破引入了动态分辨率处理和绝对时间编码&#xff0c;使其能够处理不同尺寸的图像以及长达数小时的视频&#xff0c;并实现秒级事件定位…...

Foxmail邮件客户端跨站脚本攻击漏洞(CNVD-2025-06036)技术分析

Foxmail邮件客户端跨站脚本攻击漏洞&#xff08;CNVD-2025-06036&#xff09;技术分析 漏洞背景 ‌漏洞编号‌&#xff1a;CNVD-2025-06036 ‌CVE编号‌&#xff1a;待分配 ‌厂商‌&#xff1a;腾讯Foxmail ‌影响版本‌&#xff1a;Foxmail < 7.2.25 ‌漏洞类型‌&#x…...

C语言打印的坑

使用下面的代码buf dprt("data: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]); 明明是一个字节一个字节的打&#xff0c;打出来的数据有些却是4个字节 0xffffffff 0xffffffff 0xffffffff 0x7f 0xffffffff 0x7f0…...

高并发内存池(三):PageCache(页缓存)的实现

前言&#xff1a; 在前两期内容中&#xff0c;我们深入探讨了内存管理机制中在 ThreadCache 和 CentralCache两个层级进行内存申请的具体实现。这两层缓存作为高效的内存分配策略&#xff0c;能够快速响应线程的内存需求&#xff0c;减少锁竞争&#xff0c;提升程序性能。 本期…...

pycharm已有python3.7,如何新增Run Configurations中的Python interpreter为python 3.9

在 PyCharm 中&#xff0c;如果你已经安装了 Python 3.9&#xff0c;并且希望在 Run Configurations 中新增一个 Python 3.9 的解释器&#xff0c;可以按照以下步骤操作&#xff1a; 步骤 1&#xff1a;打开 PyCharm 设置 点击 PyCharm 左上角的 File 菜单。选择 Settings&am…...

Linux驱动开发-网络设备驱动

Linux驱动开发-网络设备驱动 一&#xff0c;网络设备总体结构1.1 总体架构1.2 NAPI数据处理机制 二&#xff0c;RMII和MDIO2.1 RMII接口2.2 MDIO接口 三&#xff0c;MAC和PHY模块3.1 MAC模块3.2 PHY模块 四&#xff0c;网络模型4.1 网络的OSI和TCP/IP分层模型4.1.1 传输层&…...

学习笔记083——Java Stream API

文章目录 1、过滤数据 filter()2、转换元素 map()3、排序 sorted()3.1、自定义排序规则 4、去重 distinct()5、限制元素数量 limit()6、收集结果 collect()6.1、收集为List6.2、收集为Set6.3、转为Map6.4、基本用法&#xff08;注意键冲突会抛异常&#xff09;6.5、处理键冲突&…...

DataEase同比环比

DataEase同比环比 前言术语实现表结构设计DataEase设计创建数据集创建仪表盘最后前言 某大数据项目,需要比较展示今年跟去年的数据,如下图: 说明:比较24,25的产品销量,相同月份做一组,并排放一块 还有更进一步: 说明:比较24,25相同月份,相同产品的销量 直接用DataE…...

RAG 工程基础

RAG 概念 RAG&#xff08;Retrieval - Augmented Generation&#xff09;技术是一种将检索与生成相结合的人工智能技术&#xff0c;旨在利用外部知识源来增强语言模型的生成能力&#xff0c;提高生成内容的质量、准确性和相关性。 具体来说&#xff0c;RAG 技术在处理用户输入的…...

基础算法:滑动窗口_python版本

能使用滑动窗口的题&#xff0c;基本都需要数字为正整数&#xff0c;这样才能保证滑入一个数字总和是增加的(单调性) 一、209. 长度最小的子数组 思路&#xff1a; 已每个位置为右端点&#xff0c;依次加大左端点&#xff0c;最短不满足 sum(num[left,right]) < target的。…...

Qt 之opengl shader language

着色器示例代码 实际运行效果...

PyRoboPlan 库,给 panda 机械臂微分 IK 上大分,关节限位、碰撞全不怕

视频讲解&#xff1a; PyRoboPlan 库&#xff0c;给 panda 机械臂微分 IK 上大分&#xff0c;关节限位、碰撞全不怕 代码仓库&#xff1a;https://github.com/LitchiCheng/mujoco-learning 今天分享PyRoboPlan库&#xff0c;比之前的方式优点在于&#xff0c;这个库考虑了机械…...

GPT - TransformerDecoderBlock

本节代码定义了一个 TransformerDecoderBlock 类&#xff0c;它是 Transformer 架构中解码器的一个基本模块。这个模块包含了多头自注意力&#xff08;Multi-Head Attention&#xff09;、前馈网络&#xff08;Feed-Forward Network, FFN&#xff09;和层归一化&#xff08;Lay…...

LabVIEW 控制电机需注意的关键问题

在自动化控制系统中&#xff0c;LabVIEW 作为图形化编程平台&#xff0c;因其高度可视化、易于集成硬件等优势&#xff0c;被广泛应用于电机控制场景。然而&#xff0c;要实现稳定、精确、高效的电机控制&#xff0c;仅有软件并不足够&#xff0c;还需结合硬件选型、控制逻辑设…...

CSS 定位属性的生动比喻:以排队为例理解 relative 与 absolute

目录 一、理解标准流与队伍的类比 二、relative 定位:队伍中 “小范围活动” 的人 三、absolute 定位:队伍中 “彻底离队” 的人 在学习 CSS 的过程中,定位属性relative和absolute常常让初学者感到困惑。它们的行为方式和对页面布局的影响较为抽象,不过,我们可以通过一个…...

Jenkins 发送钉钉消息

这里不介绍 Jenkins 的安装&#xff0c;可以网上找到很多安装教程&#xff0c;重点介绍如何集成钉钉消息。 需要提前准备钉钉机器人的 webhook 地址。&#xff08;网上找下&#xff0c;很多教程&#xff09; 下面开始配置钉钉机器人&#xff0c;登录 Jenkins&#xff0c;下载 …...

nt!KeRemoveQueue 函数分析之加入队列后进入等待状态

第一部分&#xff1a; 参考例子&#xff1a;应用程序调用kernel32!GetQueuedCompletionStatus后会调用nt!KeRemoveQueue函数进入进入等待状态 0: kd> g Breakpoint 8 hit nt!KiDeliverApc: 80a3c776 55 push ebp 0: kd> kc # 00 nt!KiDeliverApc 01 nt…...

OpenCV 风格迁移

一、引言​ 在计算机视觉和图像处理领域&#xff0c;风格迁移是一项令人着迷的技术。它能够将一幅图像&#xff08;风格图像&#xff09;的艺术风格&#xff0c;如梵高画作的笔触风格、莫奈的色彩风格等&#xff0c;迁移到另一幅图像&#xff08;内容图像&#xff09;上&#x…...

35.Java线程池(线程池概述、线程池的架构、线程池的种类与创建、线程池的底层原理、线程池的工作流程、线程池的拒绝策略、自定义线程池)

一、线程池概述 1、线程池的优势 线程池是一种线程使用模式&#xff0c;线程过多会带来调度开销&#xff0c;进而影响缓存局部性和整体性能&#xff0c;而线程池维护着多个线程&#xff0c;等待着监督管理者分配可并发执行的任务&#xff0c;这避免了在处理短时间任务时创建与…...