IT技术博客大学习 共学习 共进步

算法

共 606 篇文章

IT 2014-11-22 23:50:00 / 累计浏览 1,806

PHP中字符串截取的效率

这篇讲的是 PHP 中一个性能细节:截取单个字符时,`substr()` 函数与直接使用 `$string{$start}` 或方括号访问的效率差异。 作者从算法优化的角度切入,因为在密集循环中,单个操作的微小差异会被放大。他设计了一个简单的基准测试,循环十万次来对比两种写法。结论很直观:直接使用 `$string{$start}` 的方式,执行速度大约是调用 `substr()` 函数的 **10 倍**。 文章还附上了这段测试代码,方法清晰易懂。这个发现意味着,在 PHP 中进行高频字符串操作(例如实现某些算法或处理大量文本)时,优先使用数组式的直接下标访问,不仅能写出更简洁的代码,还能获得显著的性能提升。对于追求代码效率的开发者来说,这是一个值得记住的实用技巧。

IT 2014-11-22 23:10:09 / 累计浏览 1,887

深入剖析 redis 数据结构 dict

这篇深度技术文章从源码层面拆解了 Redis 的核心数据结构——字典(dict)。作者首先指明,Redis 的每个数据库(db)本质上由两个哈希表(dictht)构成,真正存储键值对的是这两个表。文章重点剖析了 Redis 哈希表设计最精妙的部分:为何需要两个哈希表,以及如何利用它们实现 **渐进式 rehash(重哈希)**,从而在服务不中断的前提下完成表的扩容。 具体实现上,当触发扩展时,Redis 会为第二个哈希表分配新空间,并在后续的每次增删改查操作中,分批次地将数据从旧表迁移至新表。文章结合源码(`dictRehash` 函数)展示了这一“逐步搬家”的过程,并点明了其背后的设计考量:在服务器空闲时,定时任务会推进 rehash;在高负载时,操作本身的开销也会承担部分 rehash 工作,以此平衡性能。 此外,文章还分析了这种设计带来的“副作用”:由于查找操作需同时兼顾两个表,加上写操作本身包含多次查找,导致 Redis 在执行 SET 等写命令时效率并不高,这也从底层解释了其“重读轻写”的特性。最后,文章简要介绍了在涉及持久化(如 RDB/AOF)遍历哈希表时,也需要正确处理这两个表的过渡状态。全文逻辑清晰,从结构定义到核心算法,再到其对上层行为的影响,层层递进,非常适合想深入理解 Redis 高性能背后实现细节的开发者。

IT 2014-11-21 23:22:05 / 累计浏览 3,432

深入剖析 redis 数据结构 skiplist

这篇讲的是Redis有序集合ZSet背后的灵魂——跳表(skiplist)。作者从Redis源码出发,一层层拆解了这个经典数据结构。 文章首先点明跳表的核心价值:它用空间换时间,通过预先在有序链表上建立多级“索引”,实现了类似二分查找的高效查询。Redis正是利用它来支撑ZSet的排序和范围查询操作。 更精彩的部分在于对Redis具体实现的剖析。文章不仅给出了核心结构体`zskiplistNode`和`zskiplist`的定义,还深入到了插入和删除操作的算法细节。比如,插入时如何随机生成新节点的层数,以及如何通过`update`数组和`rank`数组来精确地调整每一层的前驱指针和`span`值。`span`这个设计很巧妙,它记录了两个节点之间跳过了多少元素,是实现按排名查询的关键。 作者没有停留在理论,而是结合代码注释,把查找、插入、删除的完整流程都梳理了一遍。从概念到实现,从宏观到微观,清晰地展现了Redis是如何用这套机制来保障其高性能的。对于想理解Redis内部原理的开发者来说,这篇源码分析对数据结构的剖析很到位。

IT 2014-11-21 00:04:47 / 累计浏览 2,558

深入剖析 redis 事务机制

这篇讲的是 Redis 事务的内部运作原理。作者从 MULTI、EXEC、DISCARD、WATCH 四个基础命令入手,但不止步于表面用法,而是深入到服务端源码,揭秘了事务背后的命令队列机制。 核心在于理解 Redis 的单线程模型。当客户端发送 MULTI 后,后续命令并不会立即执行,而是被服务端通过一个 `multiState` 结构体缓存在命令队列中。文章详细展示了 `multiCmd` 和 `multiState` 的结构,并结合 `processCommand` 函数的代码,清晰说明了命令是如何在“入队”和“执行”两个状态间切换的。 另一个巧妙之处是 WATCH 命令如何实现类 CAS(检查并设置)功能。文章通过对比有无 CAS 特性的表格例子,生动解释了并发修改的冲突场景。随后剖析了 `watchForKey` 等函数,展示了 Redis 如何通过监视键值对,在事务执行前检测到数据变化,从而自动取消事务,保证了操作的原子性。 整体来看,文章将事务机制拆解为命令缓存和乐观锁两个核心,并提供了关键的数据结构和源码片段,让读者能从实现层面真正理解 Redis 事务“一次性、顺序执行”的特性是如何保障的。

IT 2014-11-20 23:57:18 / 累计浏览 3,127

实现动态验证码的思路

这篇讲的是如何用JavaScript在前端实现动态GIF验证码。作者从传统静态验证码易被机器识别的问题出发,选择GIF作为载体——它兼容性好、体积小,能有效增加破解难度。 核心思路围绕三个任务展开:绘制旋转的文字、计算字符随机位置与角度、最终合成GIF图片。实现的关键技巧在于利用Canvas的`rotate`和`translate`API,并正确设置`textAlign: 'center'`与`textBaseline: 'middle'`,让文字能围绕自身的中心点旋转,而非默认的左下角,效果自然得多。 生成GIF环节则调用了gif.js库,作者特别指出其依赖Web Worker,需在HTTP环境下运行,并使用了`copy`模式来保留每一帧的独立图像数据。文章最终提供了完整的HTML示例代码与在线演示,从封装的`rotateText`函数到主流程逻辑一应俱全,是一次清晰的前端动画与图像生成实践。

IT 2014-11-20 23:53:27 / 累计浏览 1,589

深入剖析 redis 数据结构 intset

这篇讲的是 Redis 中整数集合 intset 的底层实现细节。当 set 中所有元素都是整数时,Redis 会优先使用 intset 这种紧凑的数据结构,只有遇到非整数时才升级为更通用的 dict。作者深入源码,拆解了 intset 如何做到高效存储与操作。 intset 本质是一个有序、不重复的整型数组。它的精巧之处在于通过 `encoding` 字段动态记录当前数组中整数的位宽(16、32或64位),从而在保证功能的前提下极致节省内存。查找操作直接利用数组的有序特性,采用经典的二分查找算法,效率很高。 文章的重点和亮点在于对插入过程的剖析。当插入的新整数超出了当前编码范围(例如向一个全是 16 位整数的集合插入一个 32 位整数),intset 不会简单拒绝,而是会触发一次“编码升级”(`intsetUpgradeAndAdd`)。升级过程非常巧妙:它会重新分配内存,将现有所有元素转换为新编码,并逆序移动元素以避免数据覆盖。由于新整数必然是最大或最小值,最终将其放置在数组头部或尾部即可。这种按需升级的设计,平衡了内存效率与灵活性。 整体来看,intset 是一个为特定场景高度优化的微型数据结构。它通过有序数组+二分查找+动态编码升级,为 Redis 提供了一个内存极其友好且高效的整数集合实现,是理解 Redis 空间优化哲学的一个绝佳范例。

IT 2014-11-20 23:33:34 / 累计浏览 1,714

翻译文档:TokuMX的分形索引是什么?

这篇讲的是TokuMX如何通过一种叫“分形树索引”的数据结构,颠覆了数据库性能优化的传统认知。 作者从数据库领域一条看似不可动摇的规则说起:要想写入快,索引必须能装进内存。因为传统B树索引在写数据时,几乎每次操作都需要访问磁盘上的叶子节点,一旦内存装不下,频繁的I/O就会让性能急剧下降。 而分形树索引的解法很巧妙:它在B树内部节点里加入了缓冲区。写操作先快速存入根节点的缓冲区,满了再像瀑布一样向下“刷”到子节点的缓冲区。这样一来,许多小写入就被合并成一次磁盘I/O,效率大幅提升。 文章通过对比清晰地指出:关键差异就在于这个缓冲设计,它让分形树索引在索引工作集远超内存时,依然能保持出色的写入性能。不过作者也补充,如果内存足够大,B树其实也很快,分形树的优势主要体现在应对重度I/O的场景。

IT 2014-10-15 22:56:44 / 累计浏览 1,908

关于Cookie长度的思考

这篇讲的是如何在有限的存储空间(比如浏览器的Cookie)里“挤”下更多信息。 作者从一个常见的问题出发:如何让存储的数据变得更小?文章没有停留在理论上,而是直接列举了我们熟悉的“key_len + key + value_len + value”这种存储格式作为分析起点。接着,作者给出了一系列非常具体的“瘦身”技巧:如果字段长度固定,对应的长度标识就能省掉;如果给字段编个号,字段名本身也能缩短;甚至可以完全依赖顺序来定位,连字段名和长度都能一并去除。 更巧妙的是处理变长数据和空值的思路。比如,用一个单独的“元字段”来标记哪些值是空的,从而省掉原本用于表示空值的长度字节。文章还提出了一个很实用的“默认值”策略:把频繁出现的值设为默认,不实际存储,仅用极少的位数(如2比特)来标识当前用的是哪个默认值。 所有这些优化背后有一个统一的原理:信息并没有丢失,而是巧妙地“藏”进了代码逻辑和预设规则之中。这篇文章就像一位经验丰富的工程师在分享他的存储空间优化心得,把看似简单的数据结构玩出了很多节省空间的花样。

IT 2014-08-15 13:49:20 / 累计浏览 3,516

用三段 140 字符以内的代码生成一张 1024×1024 的图片

这篇讲的是 StackExchange 上一个名为 “Tweetable Mathematical Art” 的硬核编程挑战。参赛者需要仅用三段不超过 140 字符(相当于一条推文长度)的 C++ 代码,生成一张 1024×1024 像素的彩色图片。规则是实现 RD、GR、BL 三个函数,分别对应红、绿、蓝三原色通道,通过数学计算为每个像素点赋值。 文章的核心亮点在于展示了极简代码如何创造出令人惊叹的视觉艺术。例如,Martin Büttner 的作品仅用寥寥几个三角函数就生成了绚丽的渐变图案;而排名第一的作品更是利用随机数和递归,在极短的代码内实现了类似分形的纹理效果。文章还列举了用极简代码绘制 Mandelbrot 分形集的实例,颠覆了人们对复杂图形生成代码量的认知。 这些参赛代码巧妙利用数学公式、随机种子甚至静态变量,在字符限制内实现了图像算法的核心逻辑。文章不仅分享了有趣的代码片段,更展现了在极限约束下编程的创造力和美学,对于理解图形学原理和代码压缩技巧都很有启发。

IT 2014-07-15 22:40:54 / 累计浏览 2,707

Dijkstra算法求解最短路径分析

这篇讲的是如何用Dijkstra算法在图中寻找最短路径,尤其针对无向图且边权为正的常见场景。作者通过一个清晰的六节点图示例,生动演示了算法的核心机制:从起点开始,通过一轮轮的计算,逐步确定每个节点最短距离及前驱节点,最终构建出完整的路径。例如,从节点1出发,第一轮就找到了到节点2的最短距离为7,后续轮次中不断用新发现的更短路径去更新之前节点的估计值,像“经由节点3再到节点6的距离14,优于直接到6的无穷大”这种逐步松弛的过程。 文章不仅讲解原理,还提供了算法的伪代码和一段可运行的Java实现。代码使用邻接矩阵存储图,并定义了`shortest`数组记录距离、`visited`数组标记已确定的节点。其中最核心的循环包含两个步骤:先在未确定节点中找出当前距离最小的节点,再以该节点为跳板,尝试更新其所有邻居节点的距离值。 通过这个从原理到代码的完整剖析,文章让Dijkstra算法这个经典图论问题的求解过程变得具体而易于理解,展现了算法设计的精巧逻辑。

IT 2014-06-10 12:40:46 / 累计浏览 6,912

15道使用频率极高的基础算法题

这篇讲的是程序员面试中常见的15道基础算法题的思路解析与实现。作者从链表操作、数组处理、二叉树和栈队列等经典数据结构入手,详细拆解了合并排序、删除节点、查找倒数第K个节点、反转链表等高频考点。文章不仅列出了问题,更关键的是提供了具体的解题策略,比如用双指针在O(1)时间内删除节点,通过两个栈实现队列,以及利用后序遍历构建二叉树镜像等。每道题都附有清晰的C++代码示例和关键步骤注释,将抽象的算法逻辑转化为了可运行的实现。这些题目覆盖了排序、查找、递归、位运算等多个核心算法思想,对于夯实编程基础和准备技术面试都是不错的实战参考。

IT 2014-04-21 12:45:28 / 累计浏览 3,294

lock free的理解

这篇文章帮读者厘清了一个常见误解:很多人以为“无锁”(lock free)就是指程序不使用互斥锁,但实际上这个概念与“用不用锁”并无直接关系。作者指出,lock free的正确定义是:程序能够保证在所有线程中,至少有一个线程可以持续推进,而不会互相阻塞。这意味着即使某个线程挂掉,整个程序的执行流依然能够向前。 文章用一个典型的无锁循环代码举例——两个线程不断交替修改同一个变量,结果却可能互相卡死,这恰恰说明“不用锁”未必就是 lock free。相反,使用锁也可能实现 lock free 的特性,关键在于设计是否保证了系统整体的进展性。 最后,作者提到在实际编码中,lock free 的实现通常依赖 CAS(Compare and Swap)这类硬件支持的原子操作,从而在避免传统锁开销的同时,保障并发安全与性能。

IT 2014-04-07 22:39:44 / 累计浏览 6,296

2048-AI程序算法分析

这篇技术博客深入剖析了2048游戏背后AI程序的决策核心。作者从游戏可被抽象为信息对称双人对弈模型入手,揭示了AI以超过90%概率通关的关键,源于对经典博弈算法的巧妙运用。 文章首先用“三张纸币”的直白例子,阐释了Minimax算法的“悲观决策”思想:它假设对手完美应对,于是AI总在自身可能的最坏结果中选取最优路径。紧接着,针对Minimax随搜索深度指数级增长的计算瓶颈,文章详细拆解了Alpha-beta剪枝的工作原理。通过逐步推演搜索树的构建过程,生动展示了算法如何利用α和β两个边界值,提前裁剪掉那些“不可能更优”的分支,从而在不改变最优解的前提下,将搜索效率提升近一倍。 最后,文章分析了开发者将这套算法落地于2048的具体实现。这篇分析不仅清晰传达了核心算法的逻辑,更通过完整的决策树推演,让读者直观感受到算法如何从理论走向实践,为理解类似游戏AI的开发提供了扎实的范例。

IT 2014-04-07 22:19:00 / 累计浏览 2,095

趣题:用 k × 1 的矩形覆盖 n × n 的正方形棋盘

这篇讲的是一个有趣的棋盘覆盖问题:用 k×1 的小矩形去铺满 n×n 的大棋盘,如果无法完全盖住,那么总有一种方案能使得剩余的空格数 m(n, k) 达到最少。文章的核心结论是,这个最小的剩余格子数,无论 n 和 k 取何值,永远是一个完全平方数。 作者从问题的直观定义出发,首先处理了 n k/2,作者则展示了一种更精巧的“风车式”填充策略,可以使得最终只留下一个边长为 k-r 的小正方形空白,此时 m(n, k) = (k-r)²。由于 r 和 k-r 互为补数,在这两种情况下,m(n, k) 的表达式都自然形成了一个平方数。 整个证明过程中,作者引入了一个在棋盘格子上按对角线循环填数的技巧,直观地解释了为什么当空白区域边长不超过 k/2 时,该覆盖方案已达到最优。这个数学谜题最终指向了一个简洁而普适的结论,展现了离散几何中一种精妙的优化与对称性。

IT 2013-10-29 23:00:59 / 累计浏览 14,055

二维码的生成细节和原理

这篇讲的是二维码背后的生成细节和原理,带读者像解密一样,拆解这个日常生活中处处可见的“黑方块”。作者从QR Code能存储字符、数字、中日文等丰富信息的特点入手,指出其生成过程犹如一套精密的编码与纠错算法。 文章系统梳理了二维码从结构到编码的完整流程。它首先解释了二维码的40个版本尺寸及其矩阵构成,然后详细剖析了用于定位的图案、存放格式信息的区域,以及核心的数据码与纠错码区域。重点在于数据编码部分,文章对比了数字、字母数字、字节、日文(Kanji)等不同模式的编码规则与转换过程,并用“HELLO WORLD”等实例具体演示了如何从文本一步步转换为二进制序列。 此外,文章还揭示了二维码能够部分破损仍可识别的关键——纠错码机制,介绍了L、M、Q、H四种纠错级别的不同容错能力。整体而言,这是一篇深入底层原理的技术解读,将二维码的生成拆解为清晰的步骤,适合希望理解“扫一扫”背后发生了什么的读者。

IT 2013-10-29 12:24:22 / 累计浏览 2,370

怎样把一个钝角三角形分成若干个锐角三角形

这篇讲的是一个经典的几何谜题:能否把一个钝角三角形分割成若干个锐角三角形?文章从这个引人入胜的问题出发,带读者经历了一场从直觉尝试到严谨证明的思维旅程。 作者首先指出,单纯尝试划分很难成功,关键在于必须从钝角内部引线,并且这条线在中途分岔。他以一个顶角108°的等腰三角形(由正五边形构造)为例,给出了一个确切可验证的分割方案。 但这仅解决了“某一个”钝角三角形的问题。更关键的是,任意钝角三角形是否都能如此分割?文章接着介绍了1960年Wallace Manheimer提出的通用解法:利用内心和内切圆,可以将任意钝角三角形稳定地分为7个锐角三角形。 有趣的故事还未结束。1961年,Verner Hoggatt Jr. 提出了一个更强的结论:不仅能分成锐角三角形,还能分成全是等腰的锐角三角形!他利用以内心和顶点距离为半径的圆进行构造,并巧妙地处理了初始条件不满足的情况,最终证明总能将任意钝角三角形分为不超过8个等腰锐角三角形。 文章最后还延伸讨论了“一个正方形最少能分成多少个锐角三角形”这个问题,提到了马丁·加德纳的思考以及它作为国际数学奥林匹克候选题的历史,展现了数学趣题背后严谨而迷人的推导逻辑。

IT 2013-10-15 13:48:34 / 累计浏览 3,349

C++11的Lambda使用一例:华容道求解

这篇文章展示了一个用广度优先搜索求解华容道的经典算法实现,并聚焦于如何运用 C++11 的 Lambda 表达式来优化关键函数调用。作者从基本的搜索步骤出发,演示了如何将“考虑所有可能移动”这一逻辑转换为 `curr.moves()` 函数,并指出了该函数返回 `std::vector` 临时对象可能带来的性能开销。 文章的核心巧妙之处在于,通过将处理每个新局面的逻辑封装为一个 Lambda 表达式,并直接作为参数传递给 `curr.move()` 函数,从而避免了额外的容器分配与复制。这种实现方式不仅使主循环的代码结构保持清晰,也显著降低了不必要的开销。作者还分享了一个实用经验:将 `curr.move()` 实现为函数模板,能够直接接受 Lambda(通常是一个具体的 struct),比使用 `std::function` 包装器更高效,后者每次构造都可能涉及内存分配。 最终,通过 Lambda 的这一应用,算法实现得以在保持代码可读性的同时,追求更高的运行时性能。文章附有完整的 GitHub 代码链接,并指出该程序能在几十毫秒内求解典型的华容道开局。

IT 2013-10-15 13:47:47 / 累计浏览 1,496

伙伴分配器的一个极简实现

这篇讲的是内存分配算法“伙伴分配器”的一个极简实现。文章从Linux内核经典的伙伴系统出发,将其核心思想抽象出来,并聚焦于GitHub上wuwenbin的一个极简版本,详细拆解了它的设计与实现。 作者将复杂的内存分配问题,巧妙转化为对一棵完全二叉树的管理。每个节点用一个数字(`longest`)标记,直接表示其对应内存块中最大连续空闲单元的大小。分配时,深度优先搜索找到合适节点并将其标记为0;释放时,则回溯并检查相邻节点,通过简单相加判断能否合并。整个过程在O(logN)时间内完成。 文章的精妙之处在于对比了另一种用四个状态(UNUSED/USED/SPLIT/FULL)管理节点的实现。极简版通过“`longest`”这一个数值,同时承载了状态和权重信息,避免了复杂的状态机和额外的条件判断,让分配、释放的逻辑变得异常清晰和优雅。这种“少即是多”的突破常规思维,正是其被称道的原因。

IT 2013-10-15 13:42:14 / 累计浏览 3,413

广度优先搜索解决“营救公主”问题

作者重新审视了“营救公主”这个经典迷宫搜索问题,指出之前采用深度优先搜索的方案存在缺陷,且未正确处理节点重复访问。这次他选择用广度优先搜索(BFS)重新实现,核心在于借助一个队列来逐层探索迷宫。 实现的关键有两点:一是用一个二维数组 `step` 记录从起点到每个节点的最小步数,每次从当前节点扩展邻居时累加距离;二是用 `'#'` 标记已访问过的节点,彻底避免了“回头路”和重复遍历。伪代码清晰地展示了状态转移逻辑——遇到墙则跳过,遇到通道则入队并更新距离,一旦遇到公主便结束搜索。 文章附带了完整的Java实现,通过 `Queue` 管理待探索节点,并在处理每个节点时计算步数。最终判断逻辑很直接:如果搜索到的公主所在位置距离小于给定时间 `T`,则营救成功。这种BFS解法自然保证了在网格中寻路的最短路径特性,对于此类问题比DFS更为直观和可靠。

IT 2013-09-23 23:08:03 / 累计浏览 2,455

大整数乘法

这篇讲的是大整数乘法的经典实现思路与代码实践。作者用一个整形链表来存储大整数的每一位,核心计算部分通过两层循环遍历乘数与被乘数,将每对数字的乘积累加到结果链表的对应位置上,并通过一个递归辅助函数处理进位。 实现的亮点在于对基本数据结构的运用:自定义的BigInt结构体包含了数值、指针和符号位,通过重载运算符(如乘法、输入输出流)让操作更直观。不过作者也坦诚地指出了代码的几处“非典型”设计:乘法运算返回指针而非对象,递归计算存在栈溢出风险,以及指针管理上不够严谨。这些反思本身也是很有价值的经验点。 从代码片段可以看到具体的实现细节,比如如何构建数字链表、处理进位和负号。整体上,这是一次从零开始构建大整数运算的扎实尝试,既展示了基本算法,也揭示了手动管理内存时需要面对的复杂性。