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

经典算法实现:二分查找、全排列与子集生成

在算法学习中,二分查找、全排列、子集生成是非常基础且重要的内容。本文将结合 C++ 代码,详细讲解这三种经典算法的实现思路与核心逻辑,帮助大家理解算法的底层原理和代码落地方式。

一、二分查找(Binary Search)

二分查找是一种高效的查找算法,适用于有序数组的查找场景,时间复杂度为 O (logn),相比顺序查找的 O (n),效率提升显著。

1. 核心思路

二分查找的核心是 “折半思想”:

  • 定义左右指针leftright,分别指向数组首尾;
  • 计算中间位置mid,比较目标值与ar[mid]的大小:
    • 若目标值小于ar[mid],则目标值在左半区间,调整右指针;
    • 若目标值大于ar[mid],则目标值在右半区间,调整左指针;
    • 若相等,则找到目标值(本文额外处理了 “找第一个匹配值” 的场景);
  • 重复上述过程,直到找到目标值或指针越界。

2. 代码实现(含 “找第一个匹配值”)

#include<stdio.h> #include<iostream> using namespace std; // 二分查找:找到第一个匹配val的位置 int FindValue(const int* ar, int n, int val) { if (nullptr == ar || n < 1) return -1; // 边界校验 int left = 0, right = n - 1; int pos = -1; while (left <= right) { // 避免(left+right)/2的溢出问题,等价于(left+right)/2 int mid = (right - left + 1) / 2 + left; if (val < ar[mid]) { right = mid - 1; // 目标在左半区间 } else if (val > ar[mid]) { left = mid + 1; // 目标在右半区间 } else { // 找到匹配值后,继续向左找第一个匹配项 if (mid > left && ar[mid - 1] == val) { right = mid - 1; } else { pos = mid; break; } } } return pos; } // 递归版二分查找(基础版,不找第一个匹配值) int BinFindValue(const int* ar, int left, int right, int val) { int pos = -1; if (left <= right) { int mid = (right - left + 1) / 2 + left; if (val < ar[mid]) { pos = BinFindValue(ar, left, mid - 1, val); } else if (val > ar[mid]) { pos = BinFindValue(ar, mid + 1, right, val); } else { pos = mid; } } return pos; } // 递归二分查找入口 int BinaryFindValue(const int* ar, int n, int val) { if (nullptr == ar || n < 1) return -1; return BinFindValue(ar, 0, n - 1, val); } // 测试二分查找 int main() { int ar[] = { 12,23,34,45,56,67,78,89,90,100,110 }; int n = sizeof(ar) / sizeof(ar[0]); int val; while (cin >> val, val != -1) { int pos = FindValue(ar, n, val); printf("目标值%d的位置:%d\n", val, pos); } return 0; }

3. 关键说明

  • 非递归版FindValue处理了 “数组中有重复元素,找第一个匹配值” 的场景,这是二分查找的常见变种;
  • 递归版BinaryFindValue更简洁,但递归深度过大会导致栈溢出,实际场景中更推荐非递归版;
  • mid的计算方式(right - left + 1) / 2 + left避免了(left+right)/2可能出现的整数溢出问题。

二、全排列(Permutation)

全排列是指从 n 个不同元素中取出 m 个元素,按照一定的顺序排列起来,当 m=n 时即为全排列。本文实现的是无重复元素的全排列,核心思路是回溯法

1. 核心思路

  • 定义递归函数Perm(ar, k, m),表示 “处理第 k 个位置,数组末尾为 m”;
  • k == m时,说明已处理完所有位置,输出当前排列;
  • 否则,遍历从 k 到 m 的元素,将每个元素与第 k 个位置交换,递归处理下一个位置,递归返回后再交换回来(回溯),恢复原数组。

2. 代码实现

#include<stdio.h> #include<iostream> using namespace std; // 全排列核心函数 void Perm(int* ar, int k, int m) { if (k == m) // 递归终止:已处理完所有位置 { for (int i = 0; i <= m; ++i) { printf("%3d", ar[i]); } printf("\n"); } else { for (int j = k; j <= m; ++j) { swap(ar[k], ar[j]); // 交换当前位置与后续位置的元素 Perm(ar, k + 1, m); // 递归处理下一个位置 swap(ar[k], ar[j]); // 回溯:恢复原数组 } } } // 测试全排列 int main() { int ar[] = { 1,2,3 }; int n = sizeof(ar) / sizeof(ar[0]); Perm(ar, 0, n - 1); return 0; }

3. 关键说明

  • 回溯法是全排列的核心,通过 “交换 - 递归 - 回溯” 的方式遍历所有可能的排列;
  • 该代码适用于无重复元素的数组,若数组有重复元素,需额外增加 “去重” 逻辑(如跳过相同元素的交换)。

三、子集生成(Subset Generation)

子集生成是找出一个集合的所有子集,本文通过 “0-1 标记法” 结合回溯实现,核心思想是 “每个元素有选或不选两种状态”。

1. 核心思路

  • 定义辅助数组brbr[i]=1表示选择ar[i]br[i]=0表示不选;
  • 递归函数func(ar, br, k, n)表示 “处理第 k 个元素,数组长度为 n”;
  • k >= n时,遍历辅助数组,输出选中的元素(未选中的用#占位);
  • 否则,先标记第 k 个元素为 “选中”(br[k]=1),递归处理下一个元素;再标记为 “不选中”(br[k]=0),递归处理下一个元素。

2. 代码实现

#include<stdio.h> #include<iostream> using namespace std; // 子集生成核心函数 void func(const int* ar, int* br, int k, int n) { if (k >= n) // 递归终止:处理完所有元素 { for (int i = 0; i < n; ++i) { if (br[i] == 1) { printf("%5d", ar[i]); } else { printf("%5c", '#'); } } printf("\n"); } else { br[k] = 1; func(ar, br, k + 1, n); // 选择第k个元素,递归 br[k] = 0; func(ar, br, k + 1, n); // 不选择第k个元素,递归 } } // 测试子集生成 int main() { int ar[] = { 1,2,3 }; int br[] = { 0,0,0 }; // 辅助数组,标记元素是否被选中 func(ar, br, 0, 3); return 0; }

3. 关键说明

  • 该方法的时间复杂度为 O (2^n),因为每个元素有两种状态,n 个元素的子集总数为 2^n;
  • 辅助数组br的作用是记录元素的选择状态,是回溯法中 “状态记录” 的典型应用;
  • 输出时用#占位未选中的元素,便于直观看到所有子集的构成(空集对应全#)。

四、总结

本文实现了三种经典算法:

  • 二分查找:高效查找有序数组,重点掌握折半思想和边界处理,尤其是重复元素场景的变种;
  • 全排列:回溯法的经典应用,核心是 “交换 - 递归 - 回溯”,需理解回溯的本质是恢复现场;
  • 子集生成:通过 0-1 标记法遍历所有元素的选择状态,是回溯法解决组合问题的典型思路。

这些算法是算法学习的基础,掌握其核心思想后,可拓展到更复杂的场景(如带重复元素的全排列、子集去重、二分查找的变种问题等)。建议大家手动敲写代码,调试理解每一步的执行逻辑,加深对算法的理解。

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

相关文章:

  • Windows 使用free-claude-code中转实现 claude code 调用 英伟达NVIDIA API
  • OpenClaw 是基于 Node.js 开发的本地 AI 智能体网关,部署核心是先装 **Node.js ≥ 22**,再用 npm 全局安装并完成配置向导
  • SSM+Vue医院食堂订餐系统源码+论文
  • 保姆级教程:在YOLOv8中手把手集成EMA注意力模块(附完整代码与配置文件)
  • 从CPython 3.12到3.14:我们逆向了217个AOT相关PR,提炼出6个决定编译成功率的核心宏定义(含Py_BUILD_CORE_MODULE与Py_LIMITED_API冲突解决方案)
  • 网站内链布局对SEO有什么影响_网站安全和SSL对SEO的影响是什么
  • OpenClaw安全指南:千问3.5-27B本地化执行权限管控
  • 【STM32】幻尔16路舵机控制板串口协议解析与实战编程
  • Flutter 鸿蒙(OpenHarmony)化适配实战:从零实现「点击按钮退出应用」插件
  • 2025最权威的六大AI学术工具实测分析
  • SEO和PPC广告之间的关系是什么_如何通过定期分析优化网站的SEO表现
  • SEO优化的基本流程有哪些
  • OpenClaw多模态编程助手:Qwen2.5-VL-7B解析代码截图生成注释
  • python工程项目任务分配管理系统
  • SpringBoot+Vue物业管理系统源码+论文
  • 从零到一:手把手教你用CANoe和Python脚本实现UDS诊断自动化测试(附完整代码)
  • 告别命令行!用3CDaemon在Windows上5分钟搞定FTP/TFTP服务器(附Ubuntu客户端测试)
  • ESP32/ESP8266轻量级MQTT连接管理库espMqttManager
  • LabelImg标注神器:如何一键导入预设标签避免YOLO训练翻车
  • 纯前端 PNG/JPG 转 PDF 工具(无需服务器,源码分享)
  • 我劝退了 3 个想装 OpenClaw 的朋友,直到他们看到这个工作流
  • 中医AI革命:如何用70亿参数模型破解千年诊疗难题
  • 2026年内蒙古钢结构施工服务商综合评估与选择策略 - 2026年企业推荐榜
  • Escornabot-lib:面向教育机器人的Arduino语义化控制库
  • 手把手教你用Buildroot给i.MX6ULL定制一个带摄像头推流的轻量级Linux系统(含ffmpeg、nginx配置)
  • 矿井底下干活最怕啥?通风不畅分分钟要命。今天咱们用S7-200 PLC和MCGS组态软件搭个硬核通风控制系统,手把手教你怎么让矿井呼吸起来
  • 用Multisim复刻经典:手把手教你搭建一个带分数显示的四人抢答器(附仿真文件)
  • KDD_CUP99数据集预处理与模型性能验证(附处理代码与数据集)
  • 如何高效利用孔祥仁线性代数网课?我的实战笔记与技巧分享
  • SEO 外联有哪些常见的方法和策略_SEO 外联需要多长时间才能见效