数据结构查找章节总结:ASL计算、B树与散列表核心考点
1. 为什么“查找”这一章值得单独拎出来总结如果你正在学《数据结构》翻到第七章“查找”的时候大概率会有一种错觉前面线性表、树、图已经把人折腾得够呛查找这章看起来好像就是“在一堆数据里找个东西”能有多难我当初也是这么想的直到第一次做真题被“平均查找长度”“装填因子”“再散列”这几个词按在地上摩擦才发现这章的水比想象中深得多。查找这一章的核心其实就回答两个问题第一数据怎么组织才能让找东西这件事变快第二为了变快我们愿意付出多少额外代价。这两个问题贯穿了顺序查找、折半查找、分块查找、二叉排序树、平衡二叉树、B树、B树、散列表这一整条线。它不是一个孤立的算法章节而是前面线性结构、树形结构的“应用出口”——你前面学的数组、链表、二叉树在这里全都要拿出来接受“查找效率”的检验。这篇总结适合三类人正在准备期末或考研、需要把零散知识点串成体系的人已经学过一遍但一做题就乱、分不清各种查找结构适用场景的人以及想从工程角度理解“为什么数据库索引用B树而不用二叉树”的人。我会把这一章拆成设计思路、核心结构细节、实操计算过程、常见坑排查四个部分尽量做到你看完能直接上手做题、上手给别人讲。需要先说明一点不同教材对“查找”章节的编排略有差异有的把散列表单独成章有的把B树放到外存查找里。我这里按最主流的编排来组织也就是把静态查找、动态查找、散列查找三大块都覆盖到具体细节以你手头教材为准但底层逻辑是通用的。2. 查找这一章的整体设计思路拆解2.1 查找的本质用“比较次数”换“存储结构”很多人学查找的时候脑子里只有一个个孤立的算法没有一条主线。我建议你先把这条主线刻进去所有查找算法的优化本质上都是在减少平均比较次数而减少比较次数的手段要么是让数据有序要么是建立索引要么是直接算出位置。顺序查找是最朴素的从头到尾一个个比平均查找长度是(n1)/2时间复杂度O(n)。它的优点是啥都不要求数据乱序也行、用链表存也行。折半查找就要求数据必须有序且顺序存储每次砍一半比较次数降到log₂n级别。你看这里就出现了第一个权衡有序性是有代价的你得先排序而且插入删除会破坏有序性维护成本高。再往上走二叉排序树把“有序”和“动态插入删除”结合起来了查找路径就是一条从根到叶的路径。但普通二叉排序树有个致命问题如果插入顺序是递增的它会退化成一条链查找又变回O(n)。于是有了平衡二叉树通过旋转强制树高维持在log₂n级别。再往上当数据量大到内存装不下、要放到磁盘上时二叉树的高度还是太高磁盘I/O次数太多于是有了B树和B树一个节点存多个关键字把树高压到很低。散列表则是另一条路我不比较了我直接通过哈希函数算出位置。理想情况下一次就能找到平均查找长度接近O(1)。但代价是可能冲突需要处理冲突而且散列表不支持范围查询和有序遍历。把这条主线理清楚你再看每一个具体结构就不会觉得它们是天上掉下来的而是“为了解决上一个结构的问题而诞生的”。2.2 静态查找与动态查找的分界线教材通常把查找分成静态查找和动态查找。这个分界线的判断标准很简单查找过程中数据集会不会发生变化。静态查找表数据一旦建好就不动了只查不改。顺序查找、折半查找、分块查找都属于这一类。它们的共同特点是实现简单但插入删除代价大或者根本不支持。动态查找表查找过程中可能插入或删除元素。二叉排序树、平衡二叉树、B树、B树、散列表都属于这一类。它们的设计重点不只是“查得快”还要“改得快”。这个区分很重要因为考试里经常出这种题“给你一个场景数据经常插入删除还要频繁查找选哪种结构”如果你分不清静态动态就很容易选错。比如折半查找虽然查找快但它基于数组插入删除要移动大量元素动态场景下就不合适。2.3 平均查找长度ASL这一章的“通用货币”整章出现频率最高的一个指标就是平均查找长度Average Search LengthASL。它的定义是查找过程中关键字比较次数的期望值。公式是ASL Σ(Pᵢ × Cᵢ)其中Pᵢ是查找第i个元素的概率Cᵢ是找到第i个元素所需的比较次数。这里有个坑很多人第一次学的时候会踩ASL分成功和失败两种情况而且失败的情况经常被忽略。成功ASL是找到某个已存在元素的平均比较次数失败ASL是查找一个不存在元素最终确定它不存在所需的平均比较次数。折半查找的判定树里失败结点是那些空指针位置算失败ASL的时候要把这些外部结点也算进去。我个人的经验是做ASL计算题先画判定树或者查找树然后一层一层数结点成功ASL就是(每层结点数×层数)之和除以总结点数失败ASL就是(每层失败结点数×层数)之和除以失败结点总数。这个方法比套公式靠谱得多尤其是折半查找和二叉排序树。2.4 为什么散列表放在最后讲散列表放在查找这一章的最后是有道理的。前面所有的查找方法都基于“比较”而散列表跳出了比较这个框架直接通过哈希函数建立关键字到存储位置的映射。它代表了一种完全不同的思路用空间换时间用哈希函数的计算代替关键字的比较。但散列表也不是完美的。哈希函数可能把两个不同的关键字映射到同一个位置这就是冲突。处理冲突的方法有开放定址法、链地址法、再哈希法等。不同的冲突处理方法对应的ASL计算方式也不一样。而且散列表的ASL和装填因子α直接相关α越大冲突越多查找越慢。把散列表放在最后是因为它需要你前面已经理解了“查找效率”这个概念才能体会到“O(1)查找”有多香以及为了这个O(1)付出了什么代价。3. 核心结构细节与实操要点逐个拆3.1 顺序查找最笨但最通用的方法顺序查找又叫线性查找从表的一端开始逐个比较。它的ASL成功是(n1)/2失败是n1或者n取决于教材对哨兵的处理。时间复杂度O(n)。很多人觉得顺序查找太简单不值得花时间。但我要说顺序查找有两个容易被忽略的价值。第一它对存储结构没有任何要求顺序表行、链表也行有序无序都行。第二如果数据是按查找概率从高到低排列的顺序查找的ASL可以显著降低。比如概率分别是0.5、0.3、0.2按概率降序排列后ASL 1×0.5 2×0.3 3×0.2 1.7比等概率的2小。实操要点如果考试让你优化顺序查找优先考虑“按概率降序排列”这个点。另外设置哨兵可以避免每次循环都判断是否越界这是一个常见的代码优化技巧。注意顺序查找的失败ASL计算如果表长为n查找不存在的元素需要比较n次无哨兵或n1次有哨兵不同教材定义不同做题时先确认教材口径。3.2 折半查找有序数组上的二分艺术折半查找要求数据有序且顺序存储。每次取中间位置mid (low high) / 2比较后缩小范围。时间复杂度O(log₂n)ASL成功大约是log₂(n1) - 1。折半查找的核心是判定树。判定树是一棵平衡二叉树每个结点对应一个mid位置左子树对应左半区间右子树对应右半区间。成功ASL就是判定树中每个结点的层数之和除以n失败ASL就是每个外部空结点代表查找失败的位置的层数之和除以(n1)。我见过最多的错误是mid取整方式不同判定树形状不同ASL也不同。有的教材用向下取整有的用向上取整有的用(lowhigh)/2直接截断。做题时一定要看清楚题目用的是哪种。一般来说向下取整更常见。还有一个高频考点折半查找的判定树中比较次数最多不超过⌈log₂(n1)⌉。这个结论在选择题里经常出现记住它能省不少时间。实操心得画判定树的时候不要一个一个算mid而是先确定根结点整个区间的中点然后递归地确定左右子树的根。这样画出来的树结构清晰层数一目了然。算ASL的时候成功ASL按层累加结点数失败ASL按层累加空指针数最后分别除以n和n1。3.3 分块查找顺序和折半的折中方案分块查找又叫索引顺序查找。它把数据分成若干块块内无序块间有序。建立一个索引表索引表中每个元素记录对应块的最大关键字和起始地址。查找时先在索引表中确定目标在哪一块然后在块内顺序查找。分块查找的ASL由两部分组成索引表查找的ASL加上块内查找的ASL。如果索引表用折半查找块内用顺序查找且块长为s总元素数为n那么ASL ≈ log₂(b1) s/2其中b n/s。当s取√n时ASL最小约为√n log₂√n。分块查找的设计哲学是“分级管理”。它不像折半查找那样要求全局有序也不像顺序查找那样完全无序。它允许块内无序只要块间有序就行这在某些动态场景下比折半查找更灵活。实操要点分块查找的索引表通常用数组存储每个索引项包含maxKey和startPos。查找时先比较maxKey确定块号再在块内顺序查找。如果题目问“最优块长”记住s √n这个结论推导过程是ASL对s求导令其为零。提示分块查找的ASL计算中如果索引表也用顺序查找那么ASL (b1)/2 (s1)/2其中b是块数。这个公式在选择题里出现的频率很高。3.4 二叉排序树动态查找的入门结构二叉排序树BST的定义是左子树所有结点小于根右子树所有结点大于根左右子树也分别是BST。查找时从根开始小于往左走大于往右走。BST的查找效率取决于树的高度。最好情况是平衡的高度为log₂nASL为O(log₂n)。最坏情况是退化成单支树高度为nASL为O(n)。这就是为什么需要平衡二叉树。BST的插入操作是从根开始比较找到空位置插入。删除操作分三种情况叶子结点直接删只有一个孩子的结点用孩子替代有两个孩子的结点用中序前驱或后继替代然后删除那个前驱或后继。我踩过的一个坑是删除有两个孩子的结点时用中序前驱还是中序后继会导致不同的树形进而影响后续的ASL。教材通常推荐用中序前驱左子树最大或中序后继右子树最小两者都可以但同一道题里要统一。实操心得判断一棵树是不是BST最可靠的方法是中序遍历看结果是否递增。这个结论在选择题里经常用来快速排除错误选项。3.5 平衡二叉树AVL用旋转换平衡AVL树是最早提出的自平衡二叉排序树。它的定义是任意结点的左右子树高度差不超过1。插入或删除后如果不满足这个条件就通过旋转来调整。旋转分四种LL型右旋、RR型左旋、LR型先左旋后右旋、RL型先右旋后左旋。判断旋转类型的方法是从插入点往上找第一个不平衡的结点看插入位置在它的左子树的左边LL、左子树的右边LR、右子树的右边RR、右子树的左边RL。AVL树的查找、插入、删除时间复杂度都是O(log₂n)。它的ASL和折半查找的判定树类似但AVL树是动态维护的插入删除后仍然保持平衡。我个人的经验是旋转操作不要死记硬背画图推一遍就明白了。比如LL型不平衡结点A的左孩子B的左子树插入导致A失衡右旋就是以B为轴A变成B的右孩子B原来的右子树变成A的左子树。画一次图比背十遍口诀管用。注意AVL树的删除操作比插入复杂因为删除可能导致多个结点失衡需要从下往上依次调整。考试里如果考删除通常只考删除后的一次调整不会考连锁调整。3.6 B树与B树为磁盘而生的多路平衡树B树是一种多路平衡查找树。一棵m阶B树满足每个结点最多m个孩子根结点至少2个孩子除非是叶子其他非叶结点至少⌈m/2⌉个孩子所有叶子在同一层。B树的查找过程是在结点内部用顺序或折半查找找到则返回否则沿着对应的孩子指针往下走。B树的高度是O(log_m n)比二叉树的log₂n低得多这意味着磁盘I/O次数更少。B树是B树的变种。区别在于B树的所有关键字都出现在叶子结点非叶结点只做索引叶子结点之间用指针链接支持范围查询。数据库索引普遍用B树就是因为范围查询效率高。B树和B树的插入删除涉及结点的分裂和合并这是考试的重点也是难点。分裂的规则是结点关键字数超过m-1时从中间位置分裂中间关键字上移到父结点。合并的规则是结点关键字数少于⌈m/2⌉-1时向兄弟借或与兄弟合并。实操要点B树的阶m和结点关键字数的关系是m阶B树每个结点最多m-1个关键字最少⌈m/2⌉-1个关键字根除外。这个关系在计算题里经常用到比如“一棵5阶B树每个结点最少几个关键字”答案是⌈5/2⌉-1 2个。提示B树和B树的选择题高频考点是“B树支持顺序查找B树不支持”和“B树所有关键字都在叶子B树不是”。记住这两点大部分选择题都能做对。3.7 散列表用哈希函数直接定位散列表通过哈希函数H(key)计算存储位置。理想情况下H(key)不同关键字得到不同位置一次查找成功。但实际中可能冲突需要处理。常见的哈希函数有除留余数法H(key) key mod p直接定址法H(key) a×key b数字分析法平方取中法等。除留余数法最常用p通常取小于表长的最大质数。处理冲突的方法分两大类开放定址法和链地址法。开放定址法包括线性探测、二次探测、再散列等。线性探测的缺点是容易产生堆积二次探测可以缓解但可能探测不到所有位置。链地址法把同义词放在一个链表里查找时先算位置再遍历链表。散列表的ASL和装填因子α n/m直接相关。α越大冲突越多ASL越大。开放定址法的成功ASL大约是(11/(1-α))/2链地址法的成功ASL大约是1α/2。这些公式在考试里经常直接套用。我踩过的一个坑是线性探测法删除元素时不能直接删要标记为“已删除”否则会断开探测链导致后面的元素找不到。这个点在选择题里经常考很多人第一次做都会错。实操心得散列表的ASL计算最稳妥的方法是画出散列表的存储状态然后逐个元素数比较次数。比如线性探测法插入时比较了几次查找时就是几次。链地址法链表长度为k平均查找长度就是(12...k)/k (k1)/2。4. 完整实操流程与核心计算过程演示4.1 折半查找判定树与ASL计算全流程假设有一个有序表{10, 20, 30, 40, 50, 60, 70, 80, 90, 100}共10个元素用折半查找mid向下取整。我们来完整计算成功ASL和失败ASL。第一步确定根结点。low1, high10, mid(110)/25向下取整所以根是第5个元素50。第二步递归确定左右子树。左区间[1,4]mid(14)/22左子树根是20。右区间[6,10]mid(610)/28右子树根是80。第三步继续递归。左子树的左区间[1,1]根是10左子树的右区间[3,4]mid(34)/23根是3030的右孩子是40。右子树的左区间[6,7]mid(67)/26根是6060的右孩子是70右子树的右区间[9,10]mid(910)/29根是9090的右孩子是100。第四步画判定树。第1层50第2层20, 80第3层10, 30, 60, 90第4层40, 70, 100。第五步算成功ASL。成功结点共10个第1层1个第2层2个第3层4个第4层3个。ASL成功 (1×1 2×2 3×4 4×3) / 10 (141212)/10 29/10 2.9。第六步算失败ASL。失败结点是判定树中的空指针共11个。第3层的空指针10的左右空指针2个30的左空指针1个60的左空指针1个90的左空指针1个共5个。第4层的空指针30的右孩子40的左右空指针2个60的右孩子70的左右空指针2个90的右孩子100的左右空指针2个共6个。失败ASL (3×5 4×6) / 11 (1524)/11 39/11 ≈ 3.55。这个计算过程看起来繁琐但只要你把判定树画对剩下的就是数数。我建议考试时先画树再标层数最后算ASL不要试图套公式容易记混。4.2 二叉排序树的构建与删除实操给定关键字序列{45, 24, 53, 12, 37, 93}依次插入构建BST。插入45作为根。插入24小于45作为45的左孩子。插入53大于45作为45的右孩子。插入12小于45小于24作为24的左孩子。插入37小于45大于24作为24的右孩子。插入93大于45大于53作为53的右孩子。得到的BST根45左子树根24左孩子12右孩子37右子树根53右孩子93。查找37的过程45→24→37比较3次。查找12的过程45→24→12比较3次。查找93的过程45→53→93比较3次。现在删除45。45有两个孩子用中序前驱左子树最大即37替代。替代后删除原来的37结点。37是24的右孩子且37没有孩子直接删除。删除后的BST根37左子树根24左孩子12右子树根53右孩子93。这个删除过程的关键是找到中序前驱后不是把前驱结点搬到被删结点位置而是把前驱的值赋给被删结点然后删除前驱结点。这样树的结构变化最小。4.3 AVL树旋转的完整推演给定关键字序列{30, 20, 40, 10, 25, 35, 50, 5}依次插入构建AVL树。插入30, 20, 40后树平衡。插入1030的左子树高度为220-10右子树高度为140不平衡属于LL型。右旋以20为轴30变成20的右孩子20原来的右孩子空变成30的左孩子。旋转后根为20左孩子10右孩子3030的右孩子40。插入25找到位置作为20的右孩子30的左孩子。此时20的左子树高度110右子树高度230-25不平衡属于RL型。先右旋再左旋先对30右旋25变成30的左孩子再对20左旋25变成根20变成25的左孩子30变成25的右孩子。旋转后根为25左孩子20左孩子10右孩子30右孩子40。插入35作为40的左孩子。插入50作为40的右孩子。此时25的右子树高度330-40-35/50左子树高度220-10不平衡属于RR型。左旋以30为轴25变成30的左孩子30原来的左孩子空变成25的右孩子。旋转后根为30左孩子25左孩子2020的左孩子10右孩子40左孩子35右孩子50。插入5作为10的左孩子。此时30的左子树高度425-20-10-5右子树高度240-35/50不平衡属于LL型。右旋以25为轴30变成25的右孩子25原来的右孩子空变成30的左孩子。旋转后根为25左孩子20左孩子1010的左孩子5右孩子30左孩子25原来的右孩子空右孩子40-35/50。这个过程看起来复杂但只要你每一步都检查平衡因子找到第一个不平衡的结点判断旋转类型然后画图推演就不会错。我建议考试时用铅笔在草稿纸上画每插入一个就检查一次不要等全部插完再调整。4.4 散列表线性探测法的完整插入与查找设散列表长m11哈希函数H(key) key mod 11用线性探测法处理冲突。关键字序列{22, 41, 53, 46, 30, 13, 1, 67}。插入22H(22)0位置0空放入。插入41H(41)8位置8空放入。插入53H(53)9位置9空放入。插入46H(46)2位置2空放入。插入30H(30)8位置8被41占探测9被53占探测10空放入位置10。插入13H(13)2位置2被46占探测3空放入位置3。插入1H(1)1位置1空放入。插入67H(67)1位置1被1占探测2被46占探测3被13占探测4空放入位置4。最终散列表位置0:22, 1:1, 2:46, 3:13, 4:67, 5:空, 6:空, 7:空, 8:41, 9:53, 10:30。查找67的过程H(67)1位置1是1不是67探测2是46探测3是13探测4是67找到比较4次。查找30的过程H(30)8位置8是41探测9是53探测10是30找到比较3次。成功ASL (11113214)/8 14/8 1.75。这个计算就是数每个元素插入时比较了几次插入时的比较次数就是查找时的比较次数。注意线性探测法插入时如果探测到空位置就放入比较次数包括最后一次发现空位置的比较。但查找成功时比较次数不包括发现空位置的那次因为已经找到了。这个细节在计算ASL时容易搞混。5. 常见问题与排查技巧实录5.1 折半查找判定树画错的排查最常见的错误是mid取整方式不一致。比如有的地方用(lowhigh)/2向下取整有的地方用(lowhigh1)/2向上取整。排查方法是先确认题目或教材用的是哪种然后从头到尾统一。如果题目没说默认向下取整。另一个错误是区间边界处理。折半查找的循环条件是low high不是low high。当low high时mid low还要再比较一次。很多人写成low high导致最后一个元素找不到。排查技巧画完判定树后检查每个结点的左子树所有关键字是否都小于它右子树是否都大于它。如果满足BST性质说明判定树画对了。5.2 AVL树旋转类型判断错误的排查旋转类型判断错误通常是因为找错了“第一个不平衡的结点”。正确的做法是从插入点往上回溯找到第一个平衡因子绝对值大于1的结点。这个结点就是旋转的轴。另一个常见错误是LR和RL型搞反。LR型是左子树的右边插入需要先左旋再右旋RL型是右子树的左边插入需要先右旋再左旋。记忆口诀是“那边重就往那边转先转孩子再转自己”。排查技巧旋转完成后检查每个结点的平衡因子是否都在-1到1之间。如果还有不平衡的说明旋转类型判断错了或者旋转操作执行错了。5.3 B树分裂与合并的排查B树分裂时中间关键字上移到父结点左右两部分分别成为两个新结点。常见错误是把中间关键字留在原结点或者上移的不是中间位置。排查方法是分裂后检查每个结点的关键字数是否在⌈m/2⌉-1到m-1之间。B树合并时如果兄弟结点关键字数足够就借一个如果不够就和兄弟合并同时把父结点中的分隔关键字拉下来。常见错误是忘记把父结点的分隔关键字拉下来导致合并后关键字数不对。排查技巧B树的插入删除每一步操作后都检查所有结点的关键字数是否满足B树定义。如果不满足就继续调整。5.4 散列表ASL计算错误的排查散列表ASL计算错误通常是因为忘记算失败ASL或者把成功和失败的比较次数搞混。成功ASL是找到元素的比较次数失败ASL是确定元素不存在的比较次数。另一个常见错误是线性探测法删除元素后没有标记“已删除”导致后续查找断链。排查方法是删除后检查探测链是否完整如果某个位置被删空后面的同义词就找不到了。排查技巧散列表的ASL计算最稳妥的方法是画出最终的散列表然后逐个元素数比较次数。不要试图套公式除非你非常确定公式的适用条件。5.5 常见问题速查表问题现象可能原因排查方法解决方案折半查找找不到最后一个元素循环条件写成low high检查循环条件改为low highAVL树旋转后仍不平衡旋转类型判断错误检查第一个不平衡结点的位置重新判断LL/RR/LR/RLB树分裂后关键字数不对中间关键字位置找错检查分裂后各结点关键字数确保中间关键字上移散列表查找断链删除时直接删空检查探测链是否完整标记为“已删除”ASL计算结果与答案不符成功/失败混淆检查是否分别计算成功除以n失败除以n1二叉排序树退化成链插入序列有序检查树的高度改用AVL树或随机插入5.6 独家避坑技巧第一个技巧做ASL计算题先画图再算数。不管是折半查找的判定树还是二叉排序树的查找树画出来之后层数和结点数一目了然比套公式靠谱得多。第二个技巧B树和B树的区别记住“B树所有关键字都在叶子且叶子之间有链表”这一句话就够了。考试里90%的B树B树选择题都围绕这个区别出题。第三个技巧散列表的装填因子α n/mα越大冲突越多。如果题目问“什么时候ASL最小”答案是α越小越好但α太小浪费空间。通常α取0.5到0.75之间比较合适。第四个技巧AVL树的旋转不要背口诀画图推。我见过太多人背“左左右右”背混了画一次图就再也不会忘。第五个技巧折半查找的ASL成功大约是log₂(n1)-1失败大约是log₂(n1)。这个近似公式可以用来快速验证你的计算结果是否合理。6. 查找结构选型的工程视角6.1 内存查找 vs 磁盘查找如果你在内存里查找数据数据量不大比如几万条折半查找或者散列表就足够了。折半查找实现简单散列表查找最快。如果数据动态变化用AVL树或者红黑树很多语言的标准库就是这么实现的。如果数据在磁盘上数据量大到内存装不下就必须用B树或B树。因为磁盘I/O是按块读取的一次读一个结点结点越大树高越低I/O次数越少。数据库索引默认用B树就是因为这个原因。6.2 有序查找 vs 无序查找有序查找折半、BST、AVL、B树的优点是支持范围查询和有序遍历缺点是维护有序性有代价。无序查找散列表的优点是查找最快缺点是不支持范围查询且需要处理冲突。如果你的场景是“点查”只查某个具体值散列表最合适。如果你的场景是“范围查”查某个区间内的所有值必须用有序结构。6.3 静态 vs 动态的选型决策静态场景数据建好就不动了用折半查找或者分块查找。折半查找要求全局有序分块查找只要求块间有序更灵活。动态场景数据频繁插入删除用AVL树、红黑树或者B树。AVL树平衡严格查找快但插入删除旋转多红黑树平衡宽松插入删除旋转少但查找略慢。实际工程中红黑树用得更多因为综合性能更好。6.4 我个人的选型经验我在实际项目里遇到查找需求时第一反应是问自己三个问题数据量多大数据动态变化吗需要范围查询吗数据量小几千条以内直接顺序查找或者折半查找别整那些花里胡哨的。数据量大且动态变化用标准库里的有序容器通常是红黑树或者散列表。需要范围查询用有序容器。不需要范围查询用散列表。如果数据量大到内存装不下那就上B树或者直接用数据库的索引。不要自己手写B树除非你在做数据库内核开发。7. 复习与刷题的个人建议7.1 知识点串联的复习方法我复习这一章的方法是拿一张白纸从顺序查找开始每学一个结构就问自己“上一个结构有什么问题这个结构怎么解决的”。比如顺序查找的问题是O(n)折半查找用有序性把复杂度降到O(log₂n)折半查找的问题是插入删除代价大BST用链式结构解决了动态性问题BST的问题是可能退化AVL用旋转强制平衡AVL的问题是树太高B树用多路降低高度所有比较查找的问题是O(log₂n)下不去了散列表用哈希函数直接跳到O(1)。这样串一遍整章的逻辑就通了不用死记硬背。7.2 刷题的正确姿势这一章的刷题重点不是数量而是类型。每种结构刷3到5道典型题就够了。折半查找刷判定树和ASL计算BST刷插入删除和ASLAVL刷旋转B树刷分裂合并和高度计算散列表刷冲突处理和ASL计算。刷题时不要只看答案要自己动手画图、算数。尤其是ASL计算看答案觉得懂了自己一算就错。我当初就是吃了这个亏后来强迫自己每道题都手算一遍正确率才上来。7.3 考试中的时间分配如果考试里查找这一章占20分左右我建议分配25分钟。选择题快速过计算题留足时间画图。ASL计算题不要急画完图再算比心算靠谱。B树分裂合并题一步一步来不要跳步。最后再分享一个小技巧考试前把这一章的所有公式抄在一张纸上考前过一遍。公式不多但容易混。抄一遍比看十遍管用。