C++ 在不使用循环的情况下,查找给定数字所属的数字集(范围)
根据以下标准确定查找对象的算法或算法类型时,我感到困惑: 有两个类:“TileSets”和“Tile”。TileSet有两个int属性:firstTileId和lastTileId,而Tile有一个int属性:id,如下所示:C++ 在不使用循环的情况下,查找给定数字所属的数字集(范围),c++,algorithm,hash,C++,Algorithm,Hash,根据以下标准确定查找对象的算法或算法类型时,我感到困惑: 有两个类:“TileSets”和“Tile”。TileSet有两个int属性:firstTileId和lastTileId,而Tile有一个int属性:id,如下所示: struct TileSet { int firstTileId, lastTileId; } struct Tile { int id; } 该应用程序应具有不超过10个瓷砖集(通常为3-5个)和10.000多个瓷砖。速度对于确定具有给定id的磁贴属于哪个磁贴设置
struct TileSet { int firstTileId, lastTileId; }
struct Tile { int id; }
该应用程序应具有不超过10个瓷砖集(通常为3-5个)和10.000多个瓷砖。速度对于确定具有给定id的磁贴属于哪个磁贴设置至关重要。第一个和最后一个id属性在将tileset添加到向量后不会更改,并且它们不会相互重叠,例如:{{1,25},{26,125},{126,781},{782,789}…}。正如我们所见,瓷砖范围内也没有孔洞。Tiles向量没有排序,也不能排序。我当前的实现(伪短代码)是:
Vector t=10.000+瓷砖
向量ts=大小为2次幂的数字的瓷砖集,至少大于6
对于tileIndex=0;tileIndex 如果(ts[TileSiteIndex].firstTileId>=t[tileIndex].id&&t[tileIndex].id的磁贴集不变,那么最好的策略是进行一些预计算,以实现更快的查找。我可以看到一些很好的方法
查找表
如果平铺id是整数,并且不够大,您可以创建一个查找表。对于每个id,您只需记录该id所属的平铺集的数量。类似于
for set in tilesets
for id=set.first to set.last
setLookup[id] = set.number
现在,要找到一个由tile id组成的集合,只需查找
setLookup[tile.id]
二进制搜索
如果磁贴ID不是整数或太大以至于无法使用查找表,则第二种方法可以工作。然后,您可以提前对所有磁贴集进行排序,使它们的第一个增加(或最后一个增加,这相当于两个集不重叠),然后使用二进制搜索查找给定磁贴id的磁贴集。但是,如果您确实有几个磁贴集,这可能不会比顺序查找快,您必须对其进行测试
静态关联
最后,如果您的磁贴ID也没有更改,那么我不明白您为什么不能提前将磁贴与磁贴集完全关联。只需在磁贴
类中有一个额外的字段,用于存储磁贴集
编号(或引用或其他任何内容)
请注意,“不要改变”的意思是“不要经常改变”。如果允许更改,但非常罕见,则您可以实施任何假定不更改的解决方案,并在每次更改时进行完整的重新计算。首先,我会对磁贴集进行排序。例如,首先按firstTileId
排序,然后按lastTileId
排序。然后您可以使用二进制搜索(未经测试的代码,请注意):
自动查找文件集索引(常量向量和集合,
大小开始,大小结束,
常数(瓷砖和价值)
->符号整数{
if(start==end)返回-1;
尺寸=开始+(结束-开始)/2;
如果(设置[mid].firstTileId t[tileIndex].id)
中途返回;
if(设置[mid].firstTileId>t[tileIndex].id)
返回findTileSetIndex(集合、开始、中间、值);
返回findTileSetIndex(集合、中间、结束、值);
}
用于(自动和平铺:t){
自动tileSetIndex=findTileSetIndex(ts,0,ts.size(),t);
如果(TILESTENDEX>0){
//t属于ts[tilestendex]
}
}
您应该使用使用优化存储和算法的间隔容器
在这个使用Boost ICL的示例中,我做了一些“任意”选择来生成漂亮的间断TileSets
:
using TileSets = icl::split_interval_set<int>;
struct TileSet : TileSets::interval_type::type {
TileSet(int b, int e) : TileSets::interval_type(closed(b, e)) {}
};
struct Tile : TileSets::interval_type::type {
Tile(int id) : TileSets::interval_type(closed(id, id)) {}
};
演出
使用默认大小运行时(2^8+1个瓷砖集中的100000个瓷砖),我的盒子上需要0.034个瓷砖
$ time ./test | tee >(echo "total lines: $(wc -l)") | tail
9987 hits in tileset (9984,9990]
9988 hits in tileset (9984,9990]
9989 hits in tileset (9984,9990]
9990 hits in tileset (9984,9990]
9991 hits in tileset (9990,9995]
9992 hits in tileset (9990,9995]
9993 hits in tileset (9990,9995]
9994 hits in tileset (9990,9995]
9995 hits in tileset (9990,9995]
total lines: 9988
real 0m0.034s
user 0m0.029s
sys 0m0.008s
以0.064s运行的。这包括进行冗余查找(ts.find(hit)
)的输出所花费的时间
更新-更高的卷
使用更高的卷和更具体的定时输出进行更多性能测试:
对于这个问题,我将使用优化的二叉树搜索,并考虑区间的大小。
如果磁贴ID具有均匀分布,则有必要尽量减少确定磁贴ID所需的比较次数
TileSet是指间隔较大的TileSet。这个想法提醒了哈夫曼编码算法,二叉树是在哪里建立的
将树中路径中更频繁符号的编码最小化的方法
考虑以下示例
给定瓷砖集:
[0,2), [2,9), [9,34), [34,39), [39,48), [48,148), [148,153), [153,154)
那么间隔的大小是:
2,7,25,5,9,100,5,1
总间隔长度(间隔总和)为:
让我们估计以下方法的比较次数
一个接一个的比较(如在您的问题中实现的)
如果瓷砖属于第一个瓷砖集,则要找到第一个瓷砖集需要一次比较;
如果磁贴属于第二个磁贴集,则需要进行两次比较,
如果磁贴属于第三个磁贴集,则需要进行三次比较,依此类推:
C1 = (2*1 + 7*2 + 25*3 + 5*4 + 9*5 + 100*6 + 5*7 + 1*8)/length = 799/154 = 4.84
二叉树
/ \
/ \
/ \
/ \ / \
/ \ / \
/\ /\ /\ /\
2 7 25 5 9 100 5 1
每条路径进行3次比较,因此:
C2 = 3
加权树
/ \
/ \
/ \ \
/\ \ / \
/\ \ /\ / /\
2 7 25 5 9 100 5 1
比较估计:
C3 = (2*4+7*4+25*3+5*3+9*3+100*2+5*3+1*3)/154 = 2.41
正如所见,第三种方法比其他方法需要更少的比较
树的构建方式如下:将平铺集分成两部分,这样左部分和右部分的权重之和之间的差异就最小化了。
例如:
[2,7,25,5,9,100,5,1] => [2,7,25,5,9],[100,5,1]
在左、右部件上执行拆分,直到生成树为止
当某些tileSet比其他tileSet宽得多时,这种方法是有益的。快10倍?以下是如何使代码运行速度快10倍(或更多)。我们希望删除分支,并借助gcc对内部循环进行矢量化
我们要删除循环中的条件:
for (int i=0; i<10000; ++i) {
for (int j=0; j<8; j++) {
if ((tiles[i] >= lowerBounds[j]) &&
(tiles[i] <= upperBounds[j])) {
ids[i] = j;
}
}
}
10000个磁贴和8个磁贴范围的计时,同时cpu速度固定在其基本频率:
Trivial: 0.147607 ms
Optimized: 0.014068 ms
Trivial: 0.043876 ms
Optimized: 0.004328 ms
允许cpu节流至其最高频率时的计时:
Trivial: 0.147607 ms
Optimized: 0.014068 ms
Trivial: 0.043876 ms
Optimized: 0.004328 ms
H
C3 = (2*4+7*4+25*3+5*3+9*3+100*2+5*3+1*3)/154 = 2.41
[2,7,25,5,9,100,5,1] => [2,7,25,5,9],[100,5,1]
for (int i=0; i<10000; ++i) {
for (int j=0; j<8; j++) {
if ((tiles[i] >= lowerBounds[j]) &&
(tiles[i] <= upperBounds[j])) {
ids[i] = j;
}
}
}
for (int i=0; i<10000; ++i) {
for (int j=0; j<8; ++j) {
short int ld = range[j] - tiles[i] + lowerBounds2[j];
ld = ld<0?0:ld;
ld = ld>(range[j]-1)?0:ld;
ld = ld>1?1:ld;
ids2[i] += j*ld;
}
}
g++ -std=c++11 -O3 -march=native
Trivial: 0.147607 ms
Optimized: 0.014068 ms
Trivial: 0.043876 ms
Optimized: 0.004328 ms
#include <iostream>
#include <random>
#include <chrono>
#include <cstring>
using namespace std;
using namespace std::chrono;
int main() {
short int lowerBounds [8] = {0, 2, 9, 34, 39, 48, 148, 153};
short int upperBounds [8] = {1, 8, 33, 38, 47, 147, 152, 154};
short int range [8] = {3, 8, 26, 6, 10, 101, 6, 3};
short int lowerBounds2[8] = {-1, 1, 8, 33, 38, 47, 147, 152};
short int tiles [10000];
short int ids [10000] = {0};
short int ids2 [10000] = {0};
// 10,000 random tiles
default_random_engine gen;
uniform_int_distribution<short int> dist(0, 154);
for (int i=0; i<10000; ++i) {
tiles[i] = dist(gen);
}
// *** trivial solution
double bestTime = 1.0;
for (int r=0; r<100; r++) {
auto t1 = high_resolution_clock::now();
for (int i=0; i<10000; ++i) {
for (int j=0; j<8; j++) {
if ((tiles[i] >= lowerBounds[j]) &&
(tiles[i] <= upperBounds[j])) {
ids[i] = j;
}
}
}
auto t2 = high_resolution_clock::now();
auto elapsed = duration_cast<duration<double>>(t2 - t1).count();
if (elapsed < bestTime)
bestTime = elapsed;
}
cout<<"Trivial: "<<bestTime*1000<<" ms"<<endl;
// *** optimized solution
bestTime = 1.0;
for (int r=0; r<100; r++) {
// ids should be zero for this method
memset(ids2, 0, 10000*sizeof(short int));
auto t1 = high_resolution_clock::now();
for (int i=0; i<10000; ++i) {
for (int j=0; j<8; ++j) {
short int ld = range[j] - tiles[i] + lowerBounds2[j];
ld = ld<0?0:ld;
ld = ld>(range[j]-1)?0:ld;
ld = ld>1?1:ld;
ids2[i] += j*ld;
}
}
auto t2 = high_resolution_clock::now();
auto elapsed = duration_cast<duration<double>>(t2 - t1).count();
if (elapsed < bestTime)
bestTime = elapsed;
}
cout<<"Optimized: "<<bestTime*1000<<" ms"<<endl;
// validate
for (int i=0; i<10000; i++)
if ((ids[i] - ids2[i]) != 0) {
cout<<"The results didn't match!"<<endl;
break;
}
}