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

【数据结构与算法】第25篇:静态查找(一):顺序查找与折半查找

一、顺序查找

1.1 基本思想

从头到尾逐个比较,直到找到目标或遍历完整个表。适用于任何线性表,不要求有序。

1.2 哨兵优化

在数组末尾设置一个“哨兵”值为目标值,这样循环中不需要判断是否越界,只需要比较是否相等。

普通版本:每次循环需要检查是否越界

c

int search(int *arr, int n, int key) { for (int i = 0; i < n; i++) { if (arr[i] == key) return i; } return -1; }

哨兵版本:减少一次判断

c

int searchWithSentinel(int *arr, int n, int key) { int i = 0; arr[n] = key; // 设置哨兵 while (arr[i] != key) { i++; } return i == n ? -1 : i; }

1.3 代码实现

c

#include <stdio.h> #include <stdlib.h> // 普通顺序查找 int seqSearch(int *arr, int n, int key) { for (int i = 0; i < n; i++) { if (arr[i] == key) return i; } return -1; } // 哨兵优化顺序查找 int seqSearchWithSentinel(int *arr, int n, int key) { int i = 0; arr[n] = key; // 哨兵放在末尾 while (arr[i] != key) { i++; } return i == n ? -1 : i; } int main() { int arr[] = {3, 7, 1, 9, 5, 2, 8}; int n = sizeof(arr) / sizeof(arr[0]); // 需要额外空间放哨兵 int *arrWithSentinel = (int*)malloc((n + 1) * sizeof(int)); for (int i = 0; i < n; i++) { arrWithSentinel[i] = arr[i]; } printf("数组: "); for (int i = 0; i < n; i++) printf("%d ", arr[i]); printf("\n"); int key = 5; int pos = seqSearch(arr, n, key); printf("顺序查找 %d 的位置: %d\n", key, pos); pos = seqSearchWithSentinel(arrWithSentinel, n, key); printf("哨兵优化查找 %d 的位置: %d\n", key, pos); key = 10; pos = seqSearchWithSentinel(arrWithSentinel, n, key); printf("哨兵优化查找 %d 的结果: %d\n", key, pos); free(arrWithSentinel); return 0; }

运行结果:

text

数组: 3 7 1 9 5 2 8 顺序查找 5 的位置: 4 哨兵优化查找 5 的位置: 4 哨兵优化查找 10 的结果: -1

二、折半查找(二分查找)

2.1 基本思想

折半查找要求数据有序(通常升序)。每次取中间元素与目标比较:

  • 相等 → 找到

  • 目标小于中间 → 在左半部分继续查找

  • 目标大于中间 → 在右半部分继续查找

2.2 非递归实现

c

int binarySearch(int *arr, int n, int key) { int left = 0, right = n - 1; while (left <= right) { int mid = left + (right - left) / 2; // 防止溢出 if (arr[mid] == key) { return mid; } else if (arr[mid] < key) { left = mid + 1; } else { right = mid - 1; } } return -1; }

注意mid = (left + right) / 2在 left 和 right 很大时可能溢出,用left + (right - left) / 2更安全。

2.3 递归实现

c

int binarySearchRecursive(int *arr, int left, int right, int key) { if (left > right) return -1; int mid = left + (right - left) / 2; if (arr[mid] == key) { return mid; } else if (arr[mid] < key) { return binarySearchRecursive(arr, mid + 1, right, key); } else { return binarySearchRecursive(arr, left, mid - 1, key); } }

三、折半查找的判定树

3.1 判定树的概念

折半查找的过程可以用一棵二叉树来描述,称为判定树。树中每个节点代表一次比较。

以有序数组[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]为例:

text

6 / \ 3 8 / \ / \ 2 5 7 9 / / \ 1 4 10

3.2 查找成功的比较次数

查找成功的比较次数 = 节点在判定树中的深度(根深度为1)

节点深度比较次数
611
3,822
2,5,7,933
1,4,1044

平均查找长度(ASL)

text

ASL = (1×1 + 2×2 + 4×3 + 3×4) / 10 = (1+4+12+12)/10 = 29/10 = 2.9

3.3 查找失败的比较次数

查找失败时,比较次数 = 从根到外部节点(空指针)路径上的节点数。


四、完整代码演示

c

#include <stdio.h> #include <stdlib.h> // 顺序查找(哨兵版) int seqSearchWithSentinel(int *arr, int n, int key) { int i = 0; arr[n] = key; while (arr[i] != key) i++; return i == n ? -1 : i; } // 折半查找(非递归) int binarySearch(int *arr, int n, int key) { int left = 0, right = n - 1; while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] == key) return mid; else if (arr[mid] < key) left = mid + 1; else right = mid - 1; } return -1; } // 折半查找(递归) int binarySearchRec(int *arr, int left, int right, int key) { if (left > right) return -1; int mid = left + (right - left) / 2; if (arr[mid] == key) return mid; else if (arr[mid] < key) return binarySearchRec(arr, mid + 1, right, key); else return binarySearchRec(arr, left, mid - 1, key); } // 打印判定树(简化版) void printDecisionTree(int *arr, int left, int right, int depth) { if (left > right) { for (int i = 0; i < depth; i++) printf(" "); printf("NULL\n"); return; } int mid = left + (right - left) / 2; for (int i = 0; i < depth; i++) printf(" "); printf("%d\n", arr[mid]); printDecisionTree(arr, left, mid - 1, depth + 1); printDecisionTree(arr, mid + 1, right, depth + 1); } int main() { // 顺序查找测试 int arrSeq[] = {3, 7, 1, 9, 5, 2, 8}; int nSeq = sizeof(arrSeq) / sizeof(arrSeq[0]); int *arrWithSentinel = (int*)malloc((nSeq + 1) * sizeof(int)); for (int i = 0; i < nSeq; i++) arrWithSentinel[i] = arrSeq[i]; printf("=== 顺序查找 ===\n"); printf("数组: "); for (int i = 0; i < nSeq; i++) printf("%d ", arrSeq[i]); printf("\n"); int key = 5; int pos = seqSearchWithSentinel(arrWithSentinel, nSeq, key); printf("查找 %d 的位置: %d\n", key, pos); // 折半查找测试 int arrBin[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int nBin = sizeof(arrBin) / sizeof(arrBin[0]); printf("\n=== 折半查找 ===\n"); printf("有序数组: "); for (int i = 0; i < nBin; i++) printf("%d ", arrBin[i]); printf("\n"); key = 7; pos = binarySearch(arrBin, nBin, key); printf("非递归查找 %d 的位置: %d\n", key, pos); key = 3; pos = binarySearchRec(arrBin, 0, nBin - 1, key); printf("递归查找 %d 的位置: %d\n", key, pos); key = 11; pos = binarySearch(arrBin, nBin, key); printf("查找不存在的 %d 结果: %d\n", key, pos); printf("\n=== 判定树(前3层示意)===\n"); printf("折半查找判定树:\n"); printDecisionTree(arrBin, 0, nBin - 1, 0); free(arrWithSentinel); return 0; }

运行结果:

text

=== 顺序查找 === 数组: 3 7 1 9 5 2 8 查找 5 的位置: 4 === 折半查找 === 有序数组: 1 2 3 4 5 6 7 8 9 10 非递归查找 7 的位置: 6 递归查找 3 的位置: 2 查找不存在的 11 结果: -1 === 判定树(前3层示意)=== 折半查找判定树: 6 3 2 1 NULL NULL NULL 5 4 NULL NULL NULL 8 7 NULL NULL 9 NULL 10 NULL NULL

五、复杂度分析

查找算法时间复杂度(最好)时间复杂度(最坏)平均时间复杂度空间复杂度
顺序查找O(1)O(n)O(n)O(1)
折半查找O(1)O(log n)O(log n)O(1)非递归 / O(log n)递归

六、顺序查找 vs 折半查找

对比项顺序查找折半查找
数据要求必须有序
时间复杂度O(n)O(log n)
适用场景小规模、无序、频繁插入删除大规模、有序、静态
实现难度简单中等

七、小结

这一篇我们学习了两种静态查找算法:

要点说明
顺序查找逐个比较,哨兵优化减少边界判断
折半查找二分思想,要求数据有序
折半查找非递归while循环,注意mid计算防溢出
折半查找递归分治思想,代码简洁
判定树描述折半查找过程,用于计算ASL

折半查找的核心:每次将查找范围缩小一半,时间复杂度O(log n)。

下一篇我们讲插值查找与斐波那契查找。


八、思考题

  1. 顺序查找的哨兵优化能提升多少性能?为什么?

  2. 折半查找中,为什么用mid = left + (right - left) / 2而不是(left + right) / 2

  3. 对于长度为15的有序数组,折半查找的判定树是什么样的?ASL是多少?

  4. 如果数据是有序但频繁插入删除,适合用折半查找吗?为什么?

欢迎在评论区讨论你的答案。

http://www.jsqmd.com/news/588727/

相关文章:

  • 文件存储Minio学习指南
  • NumPy张量缩并怎么用_np.einsum()爱因斯坦求和约定高级索引魔法
  • CMake赋能持续集成|自动化测试落地的进阶指南 ✨
  • 收藏!从房价暴跌看风口:小白/程序员必抓的AI大模型红利,零基础也能逆袭
  • CSS知识概述
  • 2026届毕业生推荐的五大AI论文网站实际效果
  • text2vec-base-chinese中文语义向量实战指南
  • 大语言模型部署时怎么解决显存爆炸问题
  • AquaticCLIP: A Vision-Language Foundation Model and Dataset for Underwater Scene Analysis
  • 【豆包从入门到精通】001、初识豆包:大模型时代的入门钥匙
  • 【教程4>第12章>第8节】基于FPGA的图像缩放实现——图像横向压缩仿真测试以及MATLAB辅助验证
  • AI算力芯片黑马!“图灵进化”完成新一轮数千万级别融资
  • 【数据结构与算法】第26篇:静态查找(二):插值查找与斐波那契查找
  • 大模型Agent-应用小记【转载】
  • 植物大战僵尸版本所有版本合集下载含杂交版 融合版 火影版 二战版 无双版 抽卡版 β版等等
  • 启动Comsol本地服务
  • 特定域名的proxy访问
  • WarcraftHelper:魔兽争霸III终极优化指南 - 解决宽屏、帧率、地图限制三大痛点
  • 【完整源码+数据集+部署教程】人脸遮挡检测系统源码分享[一条龙教学YOLOV8标注好的数据集一键训练_70+全套改进创新点发刊_Web前端展示]
  • PVE虚拟环境下Ubuntu24.04.3虚拟机安装OpenClaw
  • 2026 AI简历工具排行榜:写出专业简历,助你直通面试
  • MongoDB单节点转副本集(Docker安装版本)
  • 国内支持全网手机/座机/400/95/96号码认证的服务商清单 - 企业服务推荐
  • 9.3LED点阵屏显示动画
  • 全域数学理论宇宙本源正式宣言(乖乖数学)
  • 3步高效获取电子课本:tchMaterial-parser让国家中小学智慧教育平台资源轻松到手
  • YOLO系列算法改进 | C3k2改进篇 | 融合SACF光谱引导自适应跨层融合 | 光谱聚合与空间细节协同增强,跨层融合信息零损失,适用于多光谱遥感检测与边缘部署场景 | AAAI 2026
  • 【完整源码+数据集+部署教程】喷嘴检测系统源码分享[一条龙教学YOLOV8标注好的数据集一键训练_70+全套改进创新点发刊_Web前端展示]
  • 大模型指令微调入门基础教程(非常详细),从通才到专才全景解剖,收藏这一篇就够了!
  • 2026洛氏硬度计品牌深度盘点:金属材料行业洛氏硬度计企业推荐 - 品牌推荐大师