第十四周项目2----分块查找算法

本文介绍了分块查找算法的详细过程,通过实例展示了如何在给定数据中使用该算法进行查找。当输入值为85时,程序成功执行并返回相应结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

/*烟台大学计算机与控制工程学院
作者:孙潇
时间:2015年12月19日
问题描述:认真阅读并验证分块查找算法。请用22,4,23,11,20,2,15,13,30,45,26,34,29,35,26,36,55,98,56, 74,61,90,80,96,127,158,116,114,128,113,115,102,184,211,243,188,187,218,195,210,279,307,492,452,408,361,421,399,856,523,704,703,697,535,534,739(共n=56个数据,每块数据个数s=8)作为数据表,自行构造索引表,分别对查找61、739、200进行测试。 
输入描述:无
输出描述:若干数据
*/

程序:

#include <stdio.h>
#define MAXL 100    //数据表的最大长度
#define MAXI 20     //索引表的最大长度
typedef int KeyType;
typedef char InfoType[10];
typedef struct
{
    KeyType key;                //KeyType为关键字的数据类型
    InfoType data;              //其他数据
} NodeType;
typedef NodeType SeqList[MAXL]; //顺序表类型

typedef struct
{
    KeyType key;            //KeyType为关键字的类型
    int link;               //指向对应块的起始下标
} IdxType;
typedef IdxType IDX[MAXI];  //索引表类型

int IdxSearch(IDX I,int m,SeqList R,int n,KeyType k)
{
    int low=0,high=m-1,mid,i;
    int b=n/m;              //b为每块的记录个数
    while (low<=high)       //在索引表中进行二分查找,找到的位置存放在low中
    {
        mid=(low+high)/2;
        if (I[mid].key>=k)
            high=mid-1;
        else
            low=mid+1;
    }
    //应在索引表的high+1块中,再在线性表中进行顺序查找
    i=I[high+1].link;
    while (i<=I[high+1].link+b-1 && R[i].key!=k) i++;
    if (i<=I[high+1].link+b-1)
        return i+1;
    else
        return 0;
}

int main()
{
    int i,n=25,m=5,j;
    SeqList R;
    IDX I= {{14,0},{34,5},{66,10},{85,15},{100,20}};
    KeyType a[]= {8,14,6,9,10,22,34,18,19,31,40,38,54,66,46,71,78,68,80,85,100,94,88,96,87};
    KeyType x=85;
    for (i=0; i<n; i++)
        R[i].key=a[i];
    j=IdxSearch(I,m,R,n,x);
    if (j!=0)
        printf("%d是第%d个数据\n",x,j);
    else
        printf("未找到%d\n",x);
    return 0;
}

运行结果:

当x=85时



<think>我们已知分块查找算法的基本步骤:将数据分为若干块,块内无序,块间有序。建立索引表(存储每块的最大关键字和起始位置)。查找时先在索引表中确定目标所在的块,然后在该块内顺序查找。根据引用[2]和引用[4]提供的示例,我们可以这样实现:1.通常,分块查找需要两个步骤:a.构建索引表(可能需要在数据预处理时完成,或者假设数据已经分块并有序)b.查找过程:先确定块,再在块内查找。但是,引用[2]的代码实现中,并没有显式地构建索引表,而是直接根据块大小和数组本身来推断块的范围。这种方法适用于数组已经按块间有序(即前一块的最大值小于后一块的最小值)排列的情况。该实现假设每块的大小固定(最后一块可能较小),并且块内元素是任意顺序(但块间有序)。引用[4]提到:索引表递增有序,所以可以用二分查找来定位块,然后在块内顺序查找。但引用[2]的实现中采用的是顺序遍历索引块(因为块数不多,或者块数较少时顺序遍历和二分查找差异不大)。因此,我们可以提供两种实现:第一种(简单实现):固定块大小,不显式构建索引表,在查找时顺序遍历块(确定目标所在的块),然后在块内顺序查找。适用于块数不多的情况。第二种(优化实现):显式构建索引表(记录每块的最大值和起始位置),在索引表上使用二分查找定位块,然后在块内顺序查找。这里,我们将分别实现这两种方法,并给出示例。注意:分块查找的前提是数据满足“块间有序”,即第一块的所有元素都小于第二块的所有元素,第二块的所有元素都小于第三块的所有元素,以此类推。首先,我们实现第一种方法(固定块大小,顺序遍历块):步骤:1.计算块数(向上取整)2.遍历每个块:计算当前块的起始索引和结束索引(注意最后一块可能不满)如果目标值小于当前块的起始元素(即第一个元素),说明目标值不可能在当前块及之后的块中(因为块间有序,当前块的第一个元素大于前一块的最后一个元素,且都大于目标值),所以结束循环(没找到)。如果目标值大于当前块的最后一个元素,则跳过当前块,继续下一块。否则,目标值可能落在当前块内,然后在当前块内顺序查找。3.在块内找到则返回索引,否则返回-1。第二种方法(显式构建索引表,二分查找定位块):1.预处理:将数据分块,并构建一个索引表(可以用数组或vector),每个索引表项包含该块的最大值和起始地址(或也可以记录块内元素个数,但通常我们记录起始地址和块大小,或者起始地址和结束地址)。2.在索引表中二分查找:由于索引表按块最大值递增,所以可以查找一个最大值大于等于目标值的块(因为目标值可能落在该块内)。3.找到目标块后,在该块内顺序查找。下面我们分别用C++实现这两种方法,并给出示例。方法一:顺序遍历块(不显式构建索引表)代码参考引用[2]的实现,但注意引用[2]的实现中有一个小问题:它判断了如果目标值小于当前块的第一个元素就跳出循环,但是第一个块的第一个元素可能是整个数组的最小值,所以这个判断在第一个块时不会跳出。不过,这个判断是合理的,因为块间有序,当前块的第一个元素一定比前一块的所有元素大(除了第一块没有前一块),所以如果目标值小于当前块的第一个元素,那么它一定不在当前块及之后的块中。但是,这里要注意:如果目标值小于第一个块的第一个元素,那么就会在第一个块判断时进入break,从而返回-1。所以是正确的。方法一代码:```cpp#include<iostream>#include<vector>#include<algorithm>classBlockSearchSimple{public://分块查找算法实现,固定块大小staticintblockSearch(conststd::vector<int>&arr,intblockSize,inttarget){intn=arr.size();if(n==0)return-1;//计算块数,向上取整intblockNum=(n+blockSize-1)/blockSize;for(inti=0;i< blockNum;++i){intblockStart=i*blockSize;intblockEnd=std::min(blockStart+blockSize,n);//块结束位置(下一个元素的索引)//目标值比当前块的第一个元素还小,则不可能在后续块中if(target< arr[blockStart]){break;}//目标值比当前块的最后一个元素还大,则跳过当前块if(target>arr[blockEnd-1]){continue;}//在当前的块内进行顺序查找for(intj=blockStart;j< blockEnd;++j){if(arr[j]==target){returnj;}}}return-1;}};```方法二:构建索引表并使用二分查找定位块步骤:1.预处理:构建索引表,每个元素包括块的最大值和起始位置(也可以包括块结束位置,但我们可以用下一块的起始位置减1,或记录块大小)。注意:由于块间有序,所以索引表的最大值也是递增的。2.在索引表上进行二分查找,找到第一个最大值大于等于目标值的块(注意可能目标值小于所有块的最大值,所以找到的块可能是目标所在的块)。3.验证该块的最小值是否小于等于目标值(因为有可能目标值比当前块的最小值还要小?)但是根据块间有序的特性,目标值如果小于当前块的第一个元素,那么它应该在前一个块?不对,因为块间有序是指前一块的最大值小于后一块的最小值,所以目标值一定不会出现在最大值大于目标值的块之后,但是当前块的最小值可能比目标值大吗?考虑:第一块的最小值最小,然后依次增大。所以如果目标值大于前一块的最大值且小于等于当前块的最大值,那么它应该在当前块。但如果我们用二分查找找到第一个最大值大于等于目标值的块,那么该块应该包含目标值(因为前一块的最大值小于目标值,而当前块的最大值大于等于目标值,所以目标值应该落在当前块)。但是,有一个前提:前一块的最大值小于当前块的最小值(块间有序)。所以当前块的最小值应该大于前一块的最大值,因此,目标值只要大于前一块的最大值(自然大于前面所有块的值),又小于等于当前块的最大值,那么它就在当前块。然而,我们需要考虑边界:目标值比第一块的最小值还小?那么第一块的最大值就大于等于目标值(因为目标值小,所以第一块的所有值都比目标值大),此时我们找到第一块,然后在第一块内查找,即使找不到也是正确的。4.在找到的块内顺序查找。我们构建索引表的结构:这里我们用一个结构体来表示索引项,也可以直接用pair或tuple。为了方便,我们用vectorofstruct。代码示例:```cpp#include<iostream>#include<vector>#include<algorithm>structIndexBlock{intmaxValue;//当前块的最大值intstartIndex;//当前块的起始索引intendIndex;//当前块的结束索引(最后一个元素的索引+1,即左闭右开区间[startIndex,endIndex))};classBlockSearchWithIndex{public://构建索引表staticstd::vector<IndexBlock>buildIndex(conststd::vector<int>&arr,intblockSize){std::vector<IndexBlock>indexTable;intn=arr.size();intblockNum=(n+blockSize-1)/blockSize;for(inti=0;i< blockNum;++i){IndexBlockblock;block.startIndex=i*blockSize;block.endIndex=std::min(block.startIndex+blockSize,n);//计算当前块的最大值block.maxValue=*std::max_element(arr.begin()+block.startIndex,arr.begin()+block.endIndex);indexTable.push_back(block);}returnindexTable;}//在索引表上进行二分查找,找到第一个满足maxValue>=target的块staticintbinarySearchInIndexTable(conststd::vector<IndexBlock>&indexTable,inttarget){intleft=0;intright=indexTable.size()-1;intpos=-1;//记录找到的块索引while(left<=right){intmid=left+(right-left)/2;if(indexTable[mid].maxValue>=target){pos=mid;//可能是mid,但我们还要找第一个满足条件的,所以继续往左找right=mid-1;}else{left=mid+1;}}returnpos;//返回块在索引表中的索引}//分块查找(使用索引表+二分查找)staticintblockSearch(conststd::vector<int>&arr,conststd::vector<IndexBlock>&indexTable,inttarget){if(arr.empty()||indexTable.empty()){return-1;}//使用二分查找在索引表中定位块intblockIdx=binarySearchInIndexTable(indexTable,target);if(blockIdx==-1){//所有块的最大值都小于目标值,则目标值不存在return-1;}//获取目标块IndexBlockblock=indexTable[blockIdx];//在块内顺序查找for(inti=block.startIndex;i< block.endIndex;++i){if(arr[i]==target){returni;}}return-1;}//为了方便,可以写一个总的函数,包括构建索引表和查找staticintsearch(conststd::vector<int>&arr,intblockSize,inttarget){autoindexTable=buildIndex(arr,blockSize);returnblockSearch(arr,indexTable,target);}};```注意:方法二在构建索引表时,需要遍历每个块来求最大值,所以时间复杂度是O(n)(因为每个块都遍历一次,总共n个元素)。但是查找阶段,二分查找索引表需要O(logm)(m为块数),然后在块内顺序查找O(s)(s为块大小)。所以总查找时间复杂度为O(logm+s)。方法一因为没有显式构建索引表,在查找阶段顺序遍历块(O(m)),然后块内查找(O(s)),所以总的时间复杂度为O(m+s)。当块数m较大时,效率不如方法二。但是,在实际应用中,块的大小通常选择为√n,这样块数和块大小都约为√n,所以两种方法的时间复杂度都是O(√n),而方法二由于需要显式构建索引表,可能更适用于多次查找(因为索引表只需要构建一次)。下面我们给出一个使用示例:```cppintmain(){//示例数组,块间有序:每块的最大值小于下一块的最小值std::vector<int>data={3,4,1,2,8,9,6,7,13,14,11,12,18,19,16,17};//方法一:固定块大小为4(注意:块内无序,块间有序)intindex1=BlockSearchSimple::blockSearch(data,4,9);std::cout<< "Method1:index="<< index1<< "(expected5)"<< std::endl;//方法二:构建索引表,块大小4intindex2=BlockSearchWithIndex::search(data,4,9);std::cout<< "Method2:index="<< index2<< "(expected5)"<< std::endl;return0;}```注意:我们的数组是按照块大小为4来分组的,但是块内是无序的,而块间有序(第一块{3,4,1,2}最大值4小于第二块的最小值6,但实际上第二块的最小值是6,大于第一块的最大值4,所以块间有序)。但是,上面的数组并不完全满足块间有序,因为第一块的最大值4小于第二块的最小值6,第二块的最大值9小于第三块的最小值11,等等。但是,在数据录入时,我们可能已经按块间有序排列,即整个数组不一定全局有序,但分块后每块的最大值比下一块的最小值小。如果数组不满足块间有序,则分块查找可能失败。所以,在分块查找之前,需要确保数据满足分块要求。以上是两种实现方法。根据用户的问题,他可能需要简单的实现,所以我们也可以只提供一种实现。但为了全面,我们提供了两种。考虑到用户的问题:“如何用C++实现分块查找算法”,我们选择实现第二种更高效的方式(虽然构建索引表需要额外时间,但查找效率更高),并提供示例。另外,我们也可以支持不固定块大小的分块(根据具体数据情况),但这里我们仅处理固定块大小(除了最后一块)。最后,生成相关问题。相关问题:1.如何选择合适的分块大小以优化分块查找的性能?2.分块查找和二分查找、顺序查找的性能比较?3.如果数据需要频繁插入或删除,如何维护分块查找结构4.分块查找在数据库索引中的应用有哪些?我们将以上实现和解释整理给用户。</think> ### C++分块查找算法实现 分块查找算法通过将数据划分为有序块来提高查找效率,下面是详细实现步骤和代码: --- #### 一、算法原理 1. **数据分块** - 将数据集分为 $m$ 个块 - **块内无序**:元素在块内不要求排序 - **块间有序**:后一块所有元素大于前一块最大元素 2. **查找过程** - 定位目标值所在块(使用顺序查找或二分查找- 在目标块内进行顺序查找 数学表达式: 设数据量 $n$,块大小 $s$,块数 $m = \lceil n/s \rceil$ 平均查找长度 $ASL \approx \log_2 m + s/2$[^4] --- #### 二、C++实现(带详细注释) ```cpp #include <iostream> #include <vector> #include <algorithm> #include <cmath> // 用于 ceil() struct Block { int maxValue; // 块内最大值 int startIndex; // 块起始索引 }; int blockSearch(const std::vector<int>& arr, int blockSize, int target) { // 1. 预处理:创建索引表 const int n = arr.size(); const int blockCount = std::ceil(static_cast<float>(n) / blockSize); std::vector<Block> indexTable(blockCount); for (int i = 0; i < blockCount; ++i) { // 计算当前块范围 const int start = i * blockSize; const int end = std::min((i + 1) * blockSize, n); // 查找块内最大值 int maxVal = *std::max_element(arr.begin() + start, arr.begin() + end); // 存储块信息 indexTable[i] = {maxVal, start}; } // 2. 定位目标块(使用顺序查找) int targetBlock = -1; for (int i = 0; i < blockCount; ++i) { if (target <= indexTable[i].maxValue) { targetBlock = i; break; } } // 3. 处理找不到块的情况 if (targetBlock == -1) return -1; // 4. 在目标块内顺序查找 const int start = indexTable[targetBlock].startIndex; const int end = std::min(start + blockSize, n); for (int i = start; i < end; ++i) { if (arr[i] == target) return i; } return -1; // 未找到 } int main() { // 测试数据(满足块间有序) std::vector<int> data = { /* 块0 */ 3, 5, 2, 7, /* 块1 */ 12, 9, 15, 11, /* 块2 */ 20, 25, 18, 22, /* 块3 */ 30, 28, 35 }; const int blockSize = 4; // 块大小 const int target = 18; // 查找目标 // 执行分块查找 int result = blockSearch(data, blockSize, target); if (result != -1) { std::cout << "目标值 " << target << " 在位置: " << result << " (实际值: " << data[result] << ")\n"; } else { std::cout << "未找到目标值\n"; } return 0; } ``` [^2][^4] --- #### 三、关键实现点 1. **索引表构建** ```cpp // 使用std::max_element找块内最大值 int maxVal = *std::max_element(arr.begin() + start, arr.begin() + end); ``` 2. **块定位优化** - 可用二分查找替代顺序查找(块数多时效率更高): ```cpp // 二分查找定位块(要求索引表有序) auto it = std::lower_bound(indexTable.begin(), indexTable.end(), target, [](const Block& b, int val) { return b.maxValue < val; }); ``` [^4] 3. **数据约束** - 验证输入是否满足 $\text{块}_{k-1}\text{.max} < \text{块}_k\text{.min}$ - 可通过预处理确保数据合规性 --- #### 四、性能分析 | 操作 | 时间复杂度 | 空间复杂度 | |------|------------|------------| | 索引表构建 | $O(n)$ | $O(m)$ | | 块定位 | $O(m)$ 或 $O(\log m)$ | - | | 块内查找 | $O(s)$ | - | > **最佳实践**:当数据量 $n \geq 1000$ 时,设块大小 $s=\sqrt{n}$ 可平衡查找效率[^2][^4] --- #### 五、应用场景 1. 动态数据库系统(频繁插入/删除) 2. 大规模日志文件分析(无需全局排序) 3. 实时监控系统(按区域分块查询) 4. 内存受限环境(分块加载数据) --- ### 相关问题 1. 分块查找与二分查找在时间复杂度上有何区别? 2. 如何动态维护分块结构当数据频繁变更时? 3. 分块查找在大数据系统中的典型应用案例有哪些? 4. 分块大小如何影响查找效率的数学证明是什么?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值