返回分类:全部文章 >> 基础知识
返回上级:算法 - 查找与排序 (Searching and Sorting)
本文将用C++实现通用模板分块查找算法,复制代码直接可使用。
在查看本文之前,需要一些程序语言的基础。
还需要熟悉多种查找方法,在分块查找中最常用的:
算法 - 查找 - 顺序查找 (Sequential Search)
算法 - 查找 - 二分查找 (Binary Search)
分块查找,也叫索引顺序查找,其时间复杂度与查找块和查找元素所用查找方法有关。
分块查找的输入数据包含:
需要查找的表;
块索引表,即将表分成几个子表,每一个子表最大关键字的元素对应子表第一个元素在整个表中的位置。
其需要查找的表必须整体有序或分块有序。
分块有序,即除首个子表外,其余每一个子表中所有关键字都必须大于它上一个子表的最大关键字。
如果表整体有序,不一定需要传入块索引表,可以在算法中动态建立,而只需传入块数量或每个块的元素数量。
假设表中有 n 个元素,分了 m 个块:
(1)对 m 个块进行查找(任意查找方式),查找元素是否在块中;
(2)如果找到了块,再对块顺序查找(如果块内有序,可以使用其它查找方法);
(3)没有找到块,或没有找到元素返回-1。
通常情况下:
返回值,代表下标;
返回-1,代表没有找到关键字;
之后的程序,我们以数组列表形式描述。
注意:代码全部使用std::vector
作为数组列表,如果你用指针数组MyType*
,还需传入数组大小size
。
一般举例中,查找最基本的元素本身就是整型。
// Author: https://blog.csdn.net/DarkRabbit
// Block Search
// 块映射结构
struct BlockSearchPair
{
int maxKey;
int startIndex;
BlockSearchPair()
{
maxKey = -1;
startIndex = -1;
}
BlockSearchPair(int fst, int snd)
{
maxKey = fst;
startIndex = snd;
}
};
// 整型无序表,块有序 - 分块查找
// params:
// list: 查找的无序表
// keyMap: 索引表,{子表最大关键字, 子表第一个元素下标}
// element: 查找的元素
// return:
// int: 找到的下标,-1为表中没有
int BlockSearch(const std::vector<int>& list,
std::vector<BlockSearchPair>& keyMap,
const int& element)
{
if (list.empty())
{
return -1;
}
std::sort(keyMap.begin(), keyMap.end(),
[](const BlockSearchPair& x, const BlockSearchPair& y)->bool
{
return x.maxKey < y.maxKey;
}); // 先对映射表排序,以防映射表没排序(这其实应该在函数外部判断)
int size = list.size(); // 元素数量
int keyMapSize = keyMap.size(); // 索引表映射数量
int key = element; // 需要查找的关键字
// 如果索引表为空,整表代表一个块,则从0开始顺序查找
int beginIndex = keyMapSize == 0 ? 0 : -1; // 块的起始下标
int endIndex = size - 1; // 块的结束下标
// 二分查找
int left = 0;
int right = keyMapSize - 1;
int mid;
while (left <= right)
{
mid = (left + right) / 2;
if (key > keyMap[mid].maxKey) // 右查找
{
left = mid + 1;
}
else if (key <= keyMap[mid].maxKey) // 左查找
{
beginIndex = keyMap[mid].startIndex;
if (mid + 1 != keyMapSize)
{
endIndex = keyMap[mid + 1].startIndex - 1;
}
right = mid - 1;
}
}
if (beginIndex != -1) // 在块内
{
// 在块内顺序查找
for (int i = beginIndex; i <= endIndex; i++)
{
if (key == list[i])
{
return i;
}
}
}
return -1;
}
在实际应用中,通常情况下,列表存储的都是一些数据(结构体或类),它们都包含唯一标识(即关键字Key)。
我们一般不会将它们的关键字重新建立一个列表,再去查找。
这在C++中通常用模板 (template) 来解决,其它语言多数用泛型 (Genericity) 来解决。
我们的要求仅仅是用结构中的关键字进行比较,即我们只关心关键字而不关心这个数据的类型。这样使用自定义类型也不怕了。
由于分块查找会进行不同类型的比较,所以可以使用一个函数指针传入获取关键字的函数,在函数中自定义获取关键字。
我们规定此函数指针的结果:关键字。
我们接下来改造成模板函数:
// Author: https://blog.csdn.net/DarkRabbit
// Block Search
// 模板无序表,块有序 - 分块查找
// params:
// list: 查找的无序表
// keyMap: 索引表,{子表最大关键字, 子表第一个元素下标}
// element: 查找的元素
// pKey: 获取关键字
// return:
// int: 找到的下标,-1为表中没有
template<typename T>
int BlockSearch(const std::vector<T>& list,
std::vector<BlockSearchPair>& keyMap,
const T& element,
int(*pKey)(const T&))
{
if (pKey == nullptr || list.empty())
{
return -1;
}
// TODO 省略代码,其它代码和之前没有任何区别
int key = (*pKey)(element); // 需要查找的关键字
// TODO 省略代码,其它代码和之前没有任何区别
if (beginIndex != -1) // 在块内
{
// 在块内顺序查找
for (int i = beginIndex; i <= endIndex; i++)
{
if (key == (*pKey)(list[i]))
{
return i;
}
}
}
return -1;
}
有了模板函数后,我们之前的整型函数可以进行修改,直接调用模板函数即可。
我们在这里直接传入 Lambda 表达式:
// Author: https://blog.csdn.net/DarkRabbit
// Block Search
// 整型无序表,块有序 - 分块查找
// params:
// list: 查找的无序表
// keyMap: 索引表
// element: 查找的元素
// return:
// int: 找到的下标,-1为表中没有
int BlockSearch(const std::vector<int>& list,
std::vector<BlockSearchPair>& keyMap,
const int& element)
{
return BlockSearch<int>(list, keyMap, element,
[](const int& x)->int
{
return x;
});
}
类似的,自定义类型调用:
// Author: https://blog.csdn.net/DarkRabbit
// Interpolation Search
#include
#include
#include
using namespace std;
struct MyElement
{
int key;
string data;
};
int MyBlockKey(const MyElement& x)
{
return x.key;
}
int main()
{
vector<MyElement> list; // 列表
MyElement tofind; // 需要查找的元素
std::vector<BlockSearchPair> keyMap; // 索引表
// TODO 省略初始化列表、元素和索引表的过程
int index = BlockSearch<MyElement>(list, keyMap, tofind,
[](const MyElement& x)->int
{
return x.key;
});
// 以上调用方法等同于
// int index = BlockSearch(list, keyMap, tofind, MyBlockKey);
if (index != -1)
{
// do something
cout << "找到了下标:" << index << endl;
}
return 0;
}