算法思维入门:从二分搜索到分治——easy-vibe 计算机基础附录精讲
2026/9/17 2:52:59 网站建设 项目流程

算法思维入门:从二分搜索到分治——easy-vibe 计算机基础附录精讲

【免费下载链接】easy-vibe💻 vibe coding 101|The first course for AI-native product builders.项目地址: https://gitcode.com/GitHub_Trending/ea/easy-vibe

本文基于 easy-vibe 开源项目(vibe coding 101|面向 AI 原生产品构建者的第一门课)日文版文档 docs/ja-jp/appendix/1-computer-fundamentals/algorithm-thinking.md 编写,是该课程"计算机基础"附录中关于算法思维的核心章节。文章完整继承原文档的概念、对照表、代码示例与实操演示,并补充复杂度分析、可运行的算法实现与仓库内的关联学习路径,帮助 AI 时代的开发者建立可迁移的算法心智。

你能从本文获得什么:

  • 问题分解能力:面对复杂问题不再急于动手写代码,而是用分治、递归等策略先拆解问题;
  • 效率判断能力:用大 O 记法判断两个方案哪个更高效,告别"凭感觉猜";
  • 计算量思维:写代码之前先估算数据规模与时间需求,选择合适量级的算法;
  • 后续学习基础:为高级数据结构、分布式系统、机器学习等内容打好地基。
章节内容核心概念
第 1 章二分搜索分治思想、O(log n)
第 2 章排序算法冒泡排序、快速排序、归并排序
第 3 章复杂度分析时间复杂度、空间复杂度

0. 全景图:算法的本质是什么

想象在词典里查一个词:

  • 方法 1:从第一页开始一页一页翻(线性搜索);
  • 方法 2:先按首字母定位到章节,再在章节内二分查找(二分搜索)。

两种方法都能找到,但效率天差地别。算法,就是解决问题的方法——同样的输入,不同的方法决定了是"几秒出结果"还是"几分钟还在转"。

算法的三个核心指标

指标含义为什么重要
时间复杂度数据量增长时,运行时间如何增长预测大规模数据下的性能
空间复杂度数据量增长时,内存占用如何增长评估内存消耗
正确性是否总能得到正确结果算法的基本要求

逐项说明:

  • 时间复杂度用大 O 记法描述。O(n) 表示数据量翻倍、时间也翻倍;O(n²) 表示数据量翻倍、时间变为 4 倍。
  • 空间复杂度同样使用大 O 记法。有的算法用空间换时间(如哈希表),有的用时间换空间(如压缩算法)。
  • 正确性要求算法对所有可能的输入都给出正确结果。边界条件——空输入、超大输入——恰恰是最容易出错的环节。

在 easy-vibe 仓库中,本章通过<AlgorithmDemo />交互组件在文档站点中呈现搜索对比演示;从仓库 docs 目录结构可以确认,同一章节在 ar-sa、de-de、en、es-es、fr-fr、ja-jp、ko-kr、vi-vn、zh-cn、zh-tw 共 10 种语言版本中保持完全一致的内容骨架,便于多语言学习者对照阅读(参见 docs/en/appendix/1-computer-fundamentals/algorithm-thinking.md、docs/zh-cn/appendix/1-computer-fundamentals/algorithm-thinking.md)。


1. 二分搜索:每次排除一半

1.1 二分搜索的原理

前提:数据必须是已排序的。

执行步骤:

  1. 找到中间元素;
  2. 中间元素等于目标值——找到了!
  3. 目标值小于中间元素——继续在左半部分查找;
  4. 目标值大于中间元素——继续在右半部分查找;
  5. 每次都排除一半,直到找到目标或确认不存在。

时间复杂度:O(log n)。

生活类比:猜数字游戏。我心中想一个 1~100 的数字,你每次都猜中间值,我回答"大了/小了"。最多 7 次就能猜中——因为 2⁷ = 128 > 100。

一个标准的 JS 实现如下(对应原文档"每次排除一半"的步骤描述):

function binarySearch(sortedArr, target) { let left = 0 let right = sortedArr.length - 1 while (left <= right) { const mid = Math.floor((left + right) / 2) // 步骤 1:找中间元素 if (sortedArr[mid] === target) return mid // 步骤 2:命中 if (target < sortedArr[mid]) { right = mid - 1 // 步骤 3:去左半部分 } else { left = mid + 1 // 步骤 4:去右半部分 } } return -1 // 步骤 5:不存在 }

原文档通过<SearchAlgorithmDemo />组件提供可交互演示,你可以在线选择"线性搜索 / 二分搜索"对比两者的执行过程;该组件由文档站点的前端渲染层提供,各语言版本文档中以同一组件标签引用。

1.2 二分搜索的性能原理

数据量线性搜索二分搜索
100100 次7 次
1,0001,000 次10 次
1,000,0001,000,000 次20 次
1,000,000,0001,000,000,000 次30 次

逐列解读:

  • 第 1 列(数据量):从 100 一路增长到 10 亿,扩大了 1000 万倍;
  • 第 2 列(线性搜索):最"笨"的方法,从头到尾逐个找,搜索次数等于数据量;
  • 第 3 列(二分搜索):聪明的方法,每次排除一半,搜索次数只与数据量的对数相关——10 亿数据也只要 30 次!
  • 对比结论:数据量达到 100 万时,线性搜索要 100 万次,二分搜索只要 20 次——差距高达 5 万倍。

对数增长的威力

二分搜索的时间复杂度是 O(log n),这意味着:

  • 10 亿条数据,最多搜索 30 次;
  • 1 万亿条数据,最多搜索 40 次。

这就是对数增长的威力——数据量扩大 1000 倍,搜索次数只增加 10 次。这也是为什么数据库索引、有序集合等基础组件都建立在对数级查找之上。


2. 排序:把无序变成有序

2.1 主要排序算法一览

算法时间复杂度特点适用场景
冒泡排序O(n²)简单但慢教学、小规模数据
选择排序O(n²)简单但慢小规模数据
插入排序O(n²)对近乎有序的数据很快小规模、近乎有序的数据
快速排序O(n log n)实际使用中最快通用排序
归并排序O(n log n)稳定排序需要稳定性的场景
堆排序O(n log n)原地排序有内存约束的场景

逐项解读:

  • 冒泡排序:像水底的泡泡不断上浮,是最基础的排序算法。容易理解,但速度最慢。适合学习排序思想,不适合实战。
  • 选择排序:每次都选出最小的放到前面。同样简单,但无论数据是否有序,比较次数都一样。
  • 插入排序:像整理手中的扑克牌,把每张牌插入到前面已排好的部分。对近乎有序的数据效率很高。
  • 快速排序:实际开发中最常用的排序。平均情况下最快,但最坏情况(数据已经有序)会退化到 O(n²)。
  • 归并排序:采用分治思想,始终是 O(n log n),但需要额外空间。适合要求稳定性的场景。
  • 堆排序:基于堆这种数据结构,是原地排序(不需要额外空间),但实际运行速度通常比快速排序慢。

2.2 快速排序的性能原理

核心思想:分治法。

  1. 选取一个"基准"(pivot)元素;
  2. 把比基准小的放左边,比基准大的放右边;
  3. 递归地对左右两部分排序;
  4. 合并结果。

为什么快?

  • 每次分区后,基准元素就到达了它的最终位置;
  • 平均情况下,每次分区大约排除一半元素;
  • 时间复杂度 O(n log n)。

生活类比:整理书架。先抽出一本书,比它薄的放左边、比它厚的放右边,然后对左右两堆重复同样的操作。

参考实现(分区思路,对应原文档 4 个步骤):

function quickSort(arr) { if (arr.length <= 1) return arr // 递归出口 const pivot = arr[Math.floor(arr.length / 2)] // 步骤 1:选基准 const left = [], right = [] for (let i = 0; i < arr.length; i++) { if (i === Math.floor(arr.length / 2)) continue if (arr[i] < pivot) left.push(arr[i]) // 步骤 2:小的放左边 else right.push(arr[i]) // 步骤 2:大的放右边 } // 步骤 3:递归排序 return [...quickSort(left), pivot, ...quickSort(right)] // 步骤 4:合并 }

原文档通过<SortingAlgorithmDemo />组件提供排序过程可视化,你可以生成数组后对比冒泡排序与快速排序的完整执行过程。


3. 递归:调用自己

3.1 递归的原理

递归是函数调用自身的编程技巧。

两个关键要素:

  1. 基准情形(base case):什么时候停止递归?
  2. 递归步骤:如何把问题分解成更小的子问题?

经典例子:阶乘

function factorial(n) { if (n <= 1) return 1 // 基准情形 return n * factorial(n - 1) // 递归步骤 }

生活类比:俄罗斯套娃。打开一个娃娃,里面还有更小的娃娃,直到最小的娃娃打不开为止。

3.2 递归 vs 迭代

特性递归迭代(循环)
代码简洁度通常更简洁往往更复杂
内存消耗高(调用栈)
性能略慢(函数调用开销)更快
适用场景树遍历、分治算法简单重复任务

逐项解读:

  • 代码简洁度:递归通常几行代码就能表达复杂逻辑(如遍历树结构),而循环往往需要更多变量和嵌套。
  • 内存消耗:递归用"调用栈"保存每一层的信息,像叠盘子,每深入一层就多叠一个盘子;循环没有这种开销。
  • 性能:每次函数调用都有开销(参数传递、栈操作等),所以递归通常比循环略慢。
  • 适用场景:递归擅长处理本身具有递归结构的问题(文件树、DOM 树),循环擅长简单重复操作(遍历数组)。

⚠️ 递归的陷阱

栈溢出:递归太深,调用栈空间耗尽。

解决办法:

  • 改用迭代;
  • 使用尾递归优化(部分语言支持);
  • 限制递归深度。

原文档通过<RecursiveThinkingDemo />组件可视化递归的调用过程,直观观察函数如何一层层调用自己。


4. 贪心算法:每一步选最优

4.1 贪心思想

贪心算法:每一步都做出当前看起来最优的选择,期望最终得到全局最优解。

适用条件:

  1. 贪心选择性质:局部最优能导向全局最优;
  2. 最优子结构:问题的最优解包含子问题的最优解。

经典例子:硬币找零

  • 目标:用最少数量的硬币凑出指定金额;
  • 贪心策略:每次都选面值最大的硬币;
  • 结果:67 元 = 50 + 10 + 5 + 1 + 1(5 枚)。

生活类比:爬山时每次都选最陡的路往上爬。不一定能到达最高峰,但通常能到相当不错的位置。

4.2 ⚠️ 贪心并不总是最优

反例:硬币找零

假设硬币面值为 [1, 3, 4],要凑出 6 元:

  • 贪心法:4 + 1 + 1 = 3 枚;
  • 最优解:3 + 3 = 2 枚。

贪心算法在这里失败了!

教训:贪心算法简单高效,但并非总能得到最优解。使用之前必须证明问题满足贪心条件。比如上面这个反例,用代码验证会更直观:

// 贪心策略:每次都选最大面额 function greedyChange(coins, amount) { coins.sort((a, b) => b - a) // 从大到小 let count = 0 for (const c of coins) { while (amount >= c) { amount -= c count++ } } return amount === 0 ? count : Infinity } greedyChange([1, 3, 4], 6) // 得到 3,但真实最优是 3 + 3 = 2 枚

原文档通过<GreedyThinkingDemo />组件让你尝试不同的硬币组合,观察贪心策略的实际表现——当你试到 [1, 3, 4] 凑 6 时,就能亲眼看到贪心的失败。


5. 四大算法设计范式

范式思想代表算法适合的问题
分治法把问题分解成小问题快速排序、归并排序可分解的问题
贪心法每次选最优最小生成树、哈夫曼编码具有贪心性质的问题
动态规划记录子问题的解背包问题、最短路径有重叠子问题的问题
回溯法试错,走不通就回退八皇后、全排列搜索问题

逐项解读:

  • 分治法:把大问题拆成小问题、分别解决再合并。就像打扫房间,分成客厅、卧室、厨房分别打扫,最后整体干净了。
  • 贪心法:每次选当前最好的,不考虑长远结果。就像吃饭时先吃最喜欢的菜,未必是最优吃法,但速度快。
  • 动态规划:记忆中间结果,避免重复计算。就像做笔记,下次遇到同样的问题直接查答案,不用重新推导。
  • 回溯法:走不通就回退重试。就像走迷宫,这条路不通就退回上一个岔路口换一条路。

动态规划的一个直观示例——斐波那契数列,正好演示"记录子问题的解"这一核心思想:

// 朴素递归:大量重复计算(重叠子问题) function fibNaive(n) { if (n <= 1) return n return fibNaive(n - 1) + fibNaive(n - 2) // 时间复杂度 O(2ⁿ) } // 动态规划:自底向上记录子问题结果 function fibDp(n) { if (n <= 1) return n const dp = [0, 1] for (let i = 2; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2] // 只算一次,后面直接查表 } return dp[n] // 时间复杂度 O(n) }

原文档通过<AlgorithmParadigmDemo />组件对比不同设计范式的特点与适用场景。


6. 复杂度分析入门:选对量级

原文档在章节总表中承诺了"第 3 章 计算量分析",并强调写代码前先估算数据规模与时间需求。这里把散落在各章中的复杂度知识汇总成一张速查表:

复杂度名称数据量翻倍后的表现典型例子
O(1)常数级时间不变哈希表查找、数组按下标访问
O(log n)对数级只增加固定次数二分搜索(本章第 1 节)
O(n)线性级时间翻倍线性搜索、单次遍历
O(n log n)线性对数级略超翻倍快速排序、归并排序(本章第 2 节)
O(n²)平方级时间变为 4 倍冒泡排序、嵌套循环
O(2ⁿ)指数级爆炸式增长朴素递归斐波那契

估算三步法(对应原文档"计算量思维"目标):

  1. 先问数据规模:输入是 100、100 万还是 10 亿?这直接决定你需要的复杂度量级;
  2. 再定算法量级:O(n²) 算法在 100 万数据下意味着约 10¹² 次操作,几乎是不可接受的;
  3. 最后权衡空间:需要 O(n) 额外空间的算法(如归并排序)在内存受限的环境里可能不如原地排序(如堆排序)。

7. 总结:算法是解决问题的艺术

用比喻把各种算法思想收束起来:

思想比喻核心要点
二分搜索猜数字每次排除一半
排序整理书架建立秩序
递归俄罗斯套娃化大为小
贪心法爬山选路局部最优

核心启示:算法的本质是"效率"与"正确性"的平衡。

  • 好算法能让程序效率产生数量级的提升;
  • 但过度优化也可能带来不必要的复杂度;
  • 先保证正确性,再追求效率。

理解算法思想比死记具体算法更重要:

  • 分治:把大问题拆成小问题;
  • 贪心:每次选最优;
  • 动态规划:记录子问题的解;
  • 回溯:试错,走不通就回头。

与 easy-vibe 课程体系的衔接

本章属于 easy-vibe 计算机基础附录(附录索引),建议按以下路径继续深入学习:

  • 下一站·数据结构:算法离不开数据结构,"程序 = 数据结构 + 算法"。紧接本章的 数据结构序论 会讲解数组、链表、哈希表、树、图,以及它们与算法复杂度的关系——你在这里学到的二分搜索,正是有序数组/平衡树的查询基础;
  • 关联·编程语言:编程语言 章节从语言层面解释递归、函数调用的底层机制,可与本章递归一节互相印证;
  • 落地·Vibe Coding 全栈:Vibe Coding 全栈 展示如何把"问题分解 → 复杂度估算 → 编写实现"的算法思维应用到 AI 辅助的完整项目开发中;
  • 语言对照:若需中文对照,可阅读 docs/zh-cn/appendix/1-computer-fundamentals/algorithm-thinking.md,英文版见 docs/en/appendix/1-computer-fundamentals/algorithm-thinking.md,其余语言版本位于 docs 下对应目录;
  • 课程入口:完整的附录导读见 docs/ja-jp/guide/introduction.md。

参考资料

  • 《算法导论》:系统学习算法的经典教材;
  • LeetCode:通过刷题实战提升算法能力;
  • 算法可视化工具:直观理解算法的执行过程(本仓库文档中的<AlgorithmDemo /><SearchAlgorithmDemo /><SortingAlgorithmDemo /><RecursiveThinkingDemo /><GreedyThinkingDemo /><AlgorithmParadigmDemo />交互组件即承担类似可视化作用);
  • 竞赛编程:学习更高级的算法技巧。

【免费下载链接】easy-vibe💻 vibe coding 101|The first course for AI-native product builders.项目地址: https://gitcode.com/GitHub_Trending/ea/easy-vibe

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询