根据以下标准,我很难决定使用哪种算法或哪种算法来查找对象: 有两个类:' TileSets'和' Tile'。 TileSet有2个int属性:firstTileId和lastTileId,而Tile有一个int属性:id,就像那样:
struct TileSet { int firstTileId, lastTileId; }
struct Tile { int id; }
应用程序应该有不超过10个TileSet(通常为3-5)和10.000+ Tiles。速度对于确定具有给定id的TileSet哪个TileSet属于哪个非常重要。将tileset添加到向量后,第一个和最后一个id属性不会发生变化,并且它们不会相互重叠,例如:{{1,25},{26,125},{126, 781},{782,789} ...}。我们可以看到,瓷砖范围内没有任何孔。瓷砖矢量不是订购也不是。我目前的实现(伪短代码的种类)是:
Vector t = 10.000+ tiles
Vector ts = tilesets with a size of a number of a power of 2 number bigger than 6, at least
for tileIndex = 0; tileIndex < t.size; tileIndex++, do:
for tilesetIndex = 0; tilesetIndex < ts.size; tilesetIndex++, do:
if (ts[tilesetIndex].firstTileId >= t[tileIndex].id && t[tileIndex].id <= ts[tilesetIndex].lastTileId)
// tile t[tileIndex] belongs to the tileset ts[tilesetIndex]! Done!
我可以在这种情况下使用哪种算法?这有什么公式吗?
答案 0 :(得分:7)
您将使用使用优化存储和算法的间隔容器。
在这个使用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)) {}
};
美丽是高级编码:
<强> Live On Coliru 强>
TileSets gen_tiles (size_t n = 100000);
TileSets gen_tilesets(size_t n = (2ull << 8) + 1);
#include <iostream>
int main() {
auto const tiles = gen_tiles (10);
auto const ts = gen_tilesets(30);
std::cout << ts << "\n----\n";
for (auto hit : tiles & ts) {
std::cout << hit.lower() << " hits in tileset " << *ts.find(hit) << "\n";
}
}
打印
{[8,71)[71,133)[133,206)[206,231)[231,465)[465,467)[467,565](565,581)[581,651](651,907)[907,1000)[1000,1395](1395,1429)[1429,1560](1560,1706)[1706,1819)[1819,1835)[1835,1997)[1997,2124](2124,2328)[2328,2913)[2913,2922)[2922,3043)[3043,3338)[3338,3664](3664,3825](3825,3999)[3999,4320](4320,4506](4506,4561](4561,4593](4593,4668)[4668,5143)[5143,5248](5248,5633)[5633,5925](5925,6012](6012,6076)[6076,6117](6117,6119](6119,6175](6175,6184)[6184,6509)[6509,6804](6804,7081](7081,7220)[7220,7852](7852,8325)[8325,8600](8600,8662](8662,9386](9386,9423)[9423,9489](9489,9657](9657,9700](9700,9738](9738,9833](9833,9923]}
----
1561 hits in tileset (1560,1706)
1835 hits in tileset [1835,1997)
3746 hits in tileset (3664,3825]
4459 hits in tileset (4320,4506]
5969 hits in tileset (5925,6012]
5987 hits in tileset (5925,6012]
7320 hits in tileset [7220,7852]
7797 hits in tileset [7220,7852]
7966 hits in tileset (7852,8325)
9508 hits in tileset (9489,9657]
使用默认尺寸(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
Live On Coliru 以0.064秒运行。这包括输出所需的时间,它执行冗余查找(ts.find(hit)
)!
更高的性能测试和更高的特定时序输出:
#include <boost/icl/interval_set.hpp>
#include <boost/icl/split_interval_set.hpp>
namespace icl = boost::icl;
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(id) {}
};
TileSets gen_tiles (size_t n = (1ull << 22));
TileSets gen_tilesets(size_t n = (1ull << 12));
#include <iostream>
#include <iomanip>
#include <boost/chrono/chrono_io.hpp>
template <typename F>
auto timed(char const* task, F&& f) {
using namespace boost::chrono;
struct _ {
high_resolution_clock::time_point s;
const char* task;
~_() { std::cout << " -- (" << task << " completed in " << duration_cast<milliseconds>(high_resolution_clock::now() - s) << ")\n"; }
} timing { high_resolution_clock::now(), task };
return f();
}
int main() {
auto const tiles = timed("Generate tiles", [] { return gen_tiles(); });
auto const ts = timed("Generate tile sets", [] { return gen_tilesets(); });
//std::cout << ts << "\n----\n";
std::cout << "Random tiles generated: " << tiles.iterative_size() << " across a domain of " << std::setprecision(2) << static_cast<double>(tiles.size()) << "\n";
std::cout << "Tilesets to match against: " << ts.iterative_size() << " across a domain of " << std::setprecision(2) << static_cast<double>(tiles.size()) << "\n";
timed("Query intersection", [&] { std::cout << "Total number of hits: " << (tiles & ts).iterative_size() << "\n"; });
timed("Query difference", [&] { std::cout << "Total number of misses: " << (tiles - ts).iterative_size() << "\n"; });
//for (auto hit : tiles & ts) {
//std::cout << hit.lower() << " hits in tileset " << *ts.find(hit) << "\n";
//}
}
#include <random>
static auto gen_tile_id = [prng=std::mt19937{42}, dist=std::uniform_int_distribution<>()] () mutable
{ return dist(prng); };
TileSets gen_tiles(size_t n) {
TileSets r;
std::generate_n(icl::inserter(r, r.end()), n, [] () -> Tile { return gen_tile_id(); });
return r;
}
TileSets gen_tilesets(size_t n) {
TileSets r;
std::generate_n(icl::inserter(r, r.end()), n, [] () -> TileSet {
auto b = gen_tile_id(), e = gen_tile_id();
return { std::min(b,e), std::max(b,e) };
});
return r;
}
打印(在我的盒子上):
-- (Generate tiles completed in 3773 milliseconds)
-- (Generate tile sets completed in 152 milliseconds)
Random tiles generated: 4190133 across a domain of 4.2e+06
Tilesets to match against: 8191 across a domain of 4.2e+06
Total number of hits: 4187624
-- (Query intersection completed in 1068 milliseconds)
Total number of misses: 2509
-- (Query difference completed in 533 milliseconds)
答案 1 :(得分:2)
由于您的磁贴集不会更改,因此最佳策略是进行一些预先计算,以便更快地进行查找。我可以看到几种很好的方法。
如果tile id是整数且不够大,则可以创建一个查找表。对于每个id,您只需记录此ID所属的tileset数。像这样的东西
for set in tilesets
for id=set.first to set.last
setLookup[id] = set.number
现在按照图块ID查找设置,您只需查找
setLookup[tile.id]
如果您的tile id不是整数,或者可能太大而查找表变得不切实际,则第二种方法有效。然后事先对所有tileset进行排序,使其first
增加(或last
s增加,这相当于集合不重叠),然后使用二分搜索找到给定的tileset瓷砖ID。但是,如果你真的有一些tileset,这可能不比顺序查找快,你必须测试它。
最后,如果你的瓷砖ID也没有改变,那么我不明白为什么你不能提前完全将瓷砖与瓷砖集相关联。只需在Tile
类中添加一个存储TileSet
号码(或引用或其他)的字段。
请注意,通过说“不要改变”,我的意思是“不要经常改变”。如果允许更改,但很少,您可以实现任何假定它不会更改的解决方案,并在每次更改时执行完整的重新计算。
答案 2 :(得分:2)
对于这个问题,我会使用优化的二叉树搜索,考虑间隔的大小。 如果tile id具有均匀分布,则可能有意义地最小化确定所需的比较计数 TileSet的TileSet具有更大的间隔。这个想法提醒了哈夫曼编码算法,其中构建了二叉树 更频繁符号的编码方式,树中的路径被最小化
考虑以下示例。
鉴于TileSets:
[0,2), [2,9), [9,34), [34,39), [39,48), [48,148), [148,153), [153,154)
然后间隔的大小是:
2,7,25,5,9,100,5,1
总间隔长度(间隔总和)为:
length = 154
让我们估算以下方法的比较次数
逐一比较(如问题中所示) 如果Tile属于第一个TileSet,那么要找到第一个TileSet需要一个比较; 如果Tile属于第二个TileSet,则需要进行两次比较, 如果Tile属于第三个TileSet,则需要进行三次比较,依此类推:
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
正如所见,第三种方法需要的比较少于其他方法。
树按以下方式构建:将TileSets分成两部分,使左侧部分和右侧部分的权重之和之间的差异最小化。 举个例子:
[2,7,25,5,9,100,5,1] => [2,7,25,5,9],[100,5,1]
在左右部分执行拆分,直到构建树。
当某些TileSet比其他TileSets宽得多时,这种方法是有利可图的。
答案 3 :(得分:1)
快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;
}
}
}
这是一个可以改进的快速解决方案:
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 ++优化代码,第二个解决方案在i5-4200U上快10倍,因为我们没有时间使用AVX内在函数等:
g++ -std=c++11 -O3 -march=native
10,000个磁贴和8个磁贴范围的时序,而cpu速度固定为其基频:
Trivial: 0.147607 ms
Optimized: 0.014068 ms
允许cpu节流到最高频率的时间:
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;
}
}
您还可以使用多线程来获得更快的速度。我认为这很容易实现。
NB:如果您没有设置这些优化标记,我建议的方法会比简单的方法稍快或甚至更慢。
答案 4 :(得分:0)
首先,我对瓷砖集进行排序。例如。首先是firstTileId
,然后是lastTileId
。然后你可以使用二进制搜索(未经测试的代码,请注意):
auto findTileSetIndex(const Vector& sets,
size_t start, size_t end,
const Tile& value)
-> signed int {
if(start == end) return -1;
size_t mid = start + (end-start)/2;
if(sets[mid].firstTileId <= t[tileIndex].id &&
sets[mid].lastTileId > t[tileIndex].id)
return mid;
if(sets[mid].firstTileId > t[tileIndex].id)
return findTileSetIndex(sets, start, mid, value);
return findTileSetIndex(sets, mid, end, value);
}
for(auto& tile : t) {
auto tileSetIndex = findTileSetIndex(ts, 0, ts.size(), t);
if(tileSetIndex > 0) {
// t belongst to ts[tileSetIndex]
}
}