
(分块查找)

大家好,很高兴又和大家见面啦!!!
在前面的学习中,我们系统学习了两种基础而重要的查找算法:
在实际应用中,我们常常面临这样的困境:数据动态变化导致难以维持有序性,但又希望获得较高的查找效率。正是为了解决这一矛盾,分块查找应运而生!
本篇博客将深入探讨分块查找算法,它巧妙地将顺序查找和折半查找的优点相结合:
文章将从分块查找的基本思想入手,详细讲解两种分块方式(逻辑分配与物理分配)的实现细节,通过具体实例演示查找过程,并深入分析算法的平均查找长度。无论你是正在学习数据结构的学生,还是希望优化实际项目中查找效率的开发者,这篇文章都将为你提供宝贵的 insights。
让我们一起探索这种兼具灵活性与效率的查找算法吧!
分块查找也称索引顺序查找,它吸取了顺序查找和折半查找各自的优点,即有动态结构,有适合快速查找。
其基本思想为:
下面我们还是通过实例来理解其基本思想:
例如在关键码集合:{88, 24, 72, 61, 21, 6, 32, 11, 8, 31, 22, 83, 78, 54} 中,我们按照某种规则对该集合进行分块,如以跨度 30 为单位进行分块,我们就可以将该集合分为三块:
接下来我们可以根据该分块从小到大的顺序创建一个索引表
graph TB
a[0, 29]
b[30, 59]
c[60, 89]之后,我们只需要将各元素依次分配到各个分块中即可,这里有两种方式:
两种方式各自的优缺点分别为:
下面我们分别来说明一下这两种分配方式的具体过程:
在逻辑分配中,我们需要完成两件事:
对于数组:{88, 24, 72, 61, 21, 6, 32, 11, 8, 31, 22, 83, 78, 54} 各元素下标分别为:
graph TB
A[88<br>0]
B[24<br>1]
C[72<br>2]
D[61<br>3]
E[21<br>4]
F[6<br>5]
G[32<br>6]
H[11<br>7]
I[8<br>8]
J[31<br>9]
K[22<br>10]
L[83<br>11]
M[78<br>12]
N[54<br>13]接下来,我们可以通过对数组进行遍历,并将各元素及其下标记录到各分块中:
graph LR
a[0, 29]
b[30, 59]
c[60, 89]
A[88<br>0<br>max]
B[24<br>1<br>max]
C[72<br>2]
D[61<br>3]
E[21<br>4]
F[6<br>5]
G[32<br>6]
H[11<br>7]
I[8<br>8]
J[31<br>9]
K[22<br>10]
L[83<br>11]
M[78<br>12]
N[54<br>13<br>max]
a--->B--->E--->F--->H--->I--->K
b--->G--->J--->N
c--->A--->C--->D--->L--->M按照上图的分配,对应的索引表需要更改为:最大值 + 元素下标的形式
graph TB
a[24<br>1, 4, 5, 7, 8, 10]
b[54<br>6, 8, 13]
c[88<br>0, 2, 3, 11, 12]现在我们就可以在不改变原有数组的情况下,通过索引表来查找各个元素了;
在物理分配中,我们需要完成三件事:
与逻辑分配相同的是,我们同样需要通过数组遍历来查找各分块的元素,查找的结果前面有介绍,这里我就不再赘述。
接下来我们就来看一下完成物理分配后得到的新数组:
graph TB
B[24<br>0<br>max<br>begin]
E[21<br>1]
F[6<br>2]
H[11<br>3]
I[8<br>4]
K[22<br>5]
G[32<br>6<br>begin]
J[31<br>7]
N[54<br>8<br>max]
A[88<br>9<br>max<br>begin]
C[72<br>10]
D[61<br>11]
L[83<br>12]
M[78<br>13]按照上图的分配,对应的索引表需要更改为:最大值 + 分块起始元素下标的形式:
graph TB
a[24<br>0]
b[54<br>6]
c[88<br>9]可以看到,不管是逻辑分配,还是物理分配,每一个分块内的元素均为无序:
但是分块之间有序:
在分块查找中,分为两部分:
下面我们以物理分配为例来说明查找过程:
graph TB
B[24<br>0<br>max<br>begin]
E[21<br>1]
F[6<br>2]
H[11<br>3]
I[8<br>4]
K[22<br>5]
G[32<br>6<br>begin]
J[31<br>7]
N[54<br>8<br>max]
A[88<br>9<br>max<br>begin]
C[72<br>10]
D[61<br>11]
L[83<br>12]
M[78<br>13]上图所示数组对应的分块索引表为:
graph TB
a[24<br>0]
b[54<br>6]
c[88<br>9]下面我们分别以顺序查找和折半查找为例,来说明查找元素33与21的整个过程;
在索引表中,我们按照从左到右的顺序完成顺序查找:
在索引表中,我们通过指针 low 指向索引表最左侧下标,即 low = 0,指针 high 指向索引表最右侧下标,即 high = 2,之后完成折半查找:
在确定了分区后,接下来我们需要对确定好的分区进行顺序查找!!!
从索引表中我们可以确定元素 33 位于分区2中:
graph TB
G[32]
J[31]
N[54]通过顺序查找,经过 3 次关键字比较后,我们并不能在分区中找到该元素,这说明原数组中并不存在该元素,因此本次查找失败;
从索引表中我们可以确定元素 21 位于分区1中:
graph TB
B[24]
E[21]
F[6]
H[11]
I[8]
K[22]通过顺序查找,经过 2 次关键字比较,我们便找到了该元素,这时需要返回该元素的存储位置;
在分块查找中,其平均查找长度由索引表和分区共同组成,即:
其中:L_l 表示索引表内的查找长度,L_s 为分区块内的查找长度
若有一个长度为 n 的查找表,我们将其均匀地分为 b 块,每块中有 s 个记录,在等概率情况下,其平均查找长度为:
$$ \begin{align*} ASL_{成功} &= L_l + L_s \ &= \frac{b + 1}{2} + \frac{s + 1}{2} \ &= \frac{b + s + 2}{2} \ &= \frac{\frac{n}{s} + s + 2}{2} \ &= \frac{s^2 + 2 * s + n}{2 * s} \ ASL_{失败1} &= L_l + L_s \ &= \frac{b + 1}{2} + s \ &= \frac{\frac{n}{s} + 1}{2} + s \ &= \frac{2 * s ^ 2 + s + n}{2s} \ ASL_{失败2} &= L_l \ &= \frac{b + 1}{2} \ &= \frac{\frac{n}{s} + 1}{2} \ &= \frac{n + s}{2 * s} \end{align} $$
注:这里计算的失败1指的是索引表查找成功,分块查找失败;失败2指的是索引表查找失败
$$ \begin{align*} ASL_{成功} &= L_l + L_s \ &= \sum\limits^b_{i = 1}P_iC_i + \sum\limits^s_{j = 1}P_jC_j \ &= 1 * 1 * \frac{1}{b} + 2 * 2 * \frac{1}{b} + \cdots + 2^{h - 1} * h * \frac{1}{b} + \frac{s + 1}{2} \ &= \frac{1 * 1 + 2 * 2 + \cdots + 2 ^{h - 1} * h}{b} + \frac{s + 1}{2}\ &= \frac{(h - 1) * 2 ^ h + 1}{b} + \frac{s + 1}{2} \ &= \frac{(\log_{2}{(b + 1)} - 1) * 2 ^ {\log_{2}{(b + 1)}}+1}{b} + \frac{s + 1}{2} \ &= \frac{(b + 1) * \log_{2}{(b + 1)} - b - 1 + 1}{b} + \frac{s + 1}{2} \ &= \frac{(\frac{n}{s} + 1) * \log_{2}{(\frac{n}{s} + 1)} - \frac{n}{s}}{\frac{n}{s}} + \frac{s + 1}{2} \ &= \frac{\frac{(n + s)* \log_{2}{(\frac{n + s}{s})} - n}{s}}{\frac{n}{s}} + \frac{s + 1}{2} \ &= \frac{(n + s)* \log_{2}{(\frac{n + s}{s})} - n}{n} + \frac{s + 1}{2} \ ASL_{失败} &= L_l + L_s \ &= \sum\limits^{b+1}{i = 1}P_iC_i + s \ &= \frac{1}{b + 1} * (h + 1) + \frac{1}{b + 1} * (h + 1) + \cdots + \frac{1}{b + 1} * (h + 1) + s \ &= h + 1 + s \ &= \log{2}{(b + 1)} + 1 + s \ &= \log_{2}{(\frac{n}{s} + 1)} + 1 + s \end{align*} $$
现在我们以顺序查找成功为例,其平均比较长度为:
通过对该函数求导可得,当 s = \sqrt{n} 时,其平均查找长度取最小值:\sqrt{n} + 1
在分块查找中,虽然索引表占用了额外的存储空间,索引查找也增加了一定的系统开销,但由于其分块结构,使得在块内查找时的范围较小,与顺序查找相比,分块查找的总体效率提升了不少。
从上述的平均查找长度分析来看,分块查找的整体平均查找长度分析还是比较复杂的。
这主要体现在当我们对索引表采用折半查找时,它并不能像我们直接对有序查找表使用折半查找一样,能够直接确定查找表中是否存在该元素。
我们只能够通过索引表确定查找值的所在分区,而具体的查找结果还需要通过对分块内的元素进行更进一步的顺序查找。
通过本文的学习,相信大家对分块查找这一高效的查找算法有了全面的理解。让我们回顾一下本文的核心要点:
📚 核心知识点回顾
分块查找的精髓在于巧妙结合了顺序查找和折半查找的优点:
关键收获:
💡 实际应用价值
分块查找不仅在理论学习中具有重要意义,在实际开发中也有着广泛应用:
🤝 期待与您互动
如果本文对您有帮助,欢迎:
您还希望了解哪些查找算法或数据结构的深度解析?在评论区告诉我,我会根据大家的需求安排后续的创作计划!
学习之路,你我同行。 让我们在算法的世界里继续探索,下期再见!✨