在Linux环境中使用C ++,我的情况是定义了多个整数范围,整数输入根据它们所属的范围映射到不同的任意整数。没有范围重叠,并且它们并不总是连续的。
解决此问题的“最简单”方法是使用每个范围的一堆if语句,但范围的数量,它们的边界和目标值都可以变化,因此if语句不可维护。
例如,范围可能是[0,70],称为r_a,[101,150],称之为r_b,[201,400],称之为r_c。 r_a中的输入映射为1,r_b映射为2,r_c映射为3.任何不在r_a,r_b,r_c中的映射都为0.
我可以想出一个数据结构&存储(边界,地图目标)的元组并迭代它们的算法,因此找到目标值需要在边界对的数量上采用线性时间。我还可以想象一个方案,保持对的顺序,并使用二进制sort-ish算法对所有下限(或上限),找到最接近输入,然后比较相对的边界。
有没有比基于二进制搜索的算法更好的方法来完成映射?更好的是,有没有一些C ++库可以做到这一点?
答案 0 :(得分:13)
这里最好的方法确实是二元搜索,但任何有效的基于订单的搜索都能做得非常好。您实际上不必显式实现搜索和数据结构。您可以通过使用标准关联容器来间接使用它。
由于您的范围不重叠,因此解决方案非常简单。您可以立即使用std::map
解决此问题,只需几行代码即可解决问题。
例如,这是一种可能的方法。假设我们将[ int, int ]
范围映射到int
值。让我们将范围表示为封闭开放范围,即如果原始范围是[0, 70]
,我们考虑使用[0, 71)
范围。另外,让我们使用0
的值作为“保留”值,这意味着“没有映射”(正如您在问题中所要求的那样)
const int EMPTY = 0;
您需要做的就是声明从int
到int
的地图:
typedef std::map<int, int> Map;
Map map;
并在封闭开放范围的每一端填充它。左(闭)端应映射到整个范围映射到的所需值,而右(开)端应映射到我们的EMPTY
值。对于您的示例,它将如下所示
map[0] = r_a;
map[71] = EMPTY;
map[101] = r_b;
map[251] = EMPTY;
map[260] = r_c; // 260 adjusted from 201
map[401] = EMPTY;
(我调整了你的最后一个范围,因为在原始示例中它与之前的范围重叠,并且你说你的范围不重叠。)
这是初始化。
现在,为了确定给定值i
映射到您需要做的所有事情的位置
Map::iterator it = map.upper_bound(i);
如果it == map.begin()
,则i
不在任何范围内。否则,请执行
--it;
如果it->second
(针对递减的it
)为EMPTY
,则i
不在任何范围内。
组合的“未命中”检查可能如下所示
Map::iterator it = map.upper_bound(i);
if (it == map.begin() || (--it)->second == EMPTY)
/* Missed all ranges */;
否则,it->second
(对于递减的it
)是您的映射值
int mapped_to = it->second;
请注意,如果原始范围是“触摸”,如[40, 60]
和[61, 100]
,则封闭开放范围将显示为[40, 61)
和[61, 101)
,表示在映射初始化期间,61
的值将被映射两次。在这种情况下,务必确保61
的值映射到正确的目标值而不是EMPTY
的值。如果您按照从左到右(即增加)的顺序映射上面显示的范围,它将自行正常工作。
请注意,只有范围的端点插入到地图中,这意味着内存消耗和搜索性能仅取决于范围总数,并且完全独立于其总长度。
如果您愿意,可以在初始化期间向地图添加“guard”元素
map[INT_MIN] = EMPTY;
(它对应于“负无穷大”)并且“未命中”检查将变得更简单
Map::iterator it = map.upper_bound(i);
assert(it != map.begin());
if ((--it)->second == EMPTY)
/* Missed all ranges */;
但这只是个人偏好的问题。
当然,如果您只想为非映射值返回0
,则根本不需要执行任何检查。只需从递减的迭代器中取it->second
即可。
答案 1 :(得分:8)
我会使用一个非常简单的事情:std::map
。
class Range
{
public:
explicit Range(int item); // [item,item]
Range(int low, int high); // [low,high]
bool operator<(const Range& rhs) const
{
if (mLow < rhs.mLow)
{
assert(mHigh < rhs.mLow); // sanity check
return true;
}
return false;
} // operator<
int low() const { return mLow; }
int high() const { return mHigh; }
private:
int mLow;
int mHigh;
}; // class Range
然后,让我们有一张地图:
typedef std::map<Range, int> ranges_type;
编写一个在此地图中搜索的函数:
int find(int item, const ranges_type& ranges)
{
ranges_type::const_iterator it = ranges.lower_bound(Range(item));
if (it != ranges.end() && it->first.low() <= item)
return it->second;
else
return 0; // No mapping ?
}
主要好处:
如果范围被冻结(即使它们的值不是),您可能希望使用Loki::AssocVector
来减少内存开销并稍微提高性能(基本上,它是带有地图接口的有序向量) )。
答案 2 :(得分:2)
一个简单的数组不足够吗?你不是说你有多少项目,但到目前为止最快的数据结构是一个简单的数组。
如果范围是:
然后数组就像这样:
[25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42]
答案 3 :(得分:1)
您可以有两个排序的数组:一个用于下限,一个用于上限。使用std::lower_bound(lower_bound_array, value)
和std::upper_bound(upper_bound_array, value)
。如果两个结果的索引相同,return index + 1
。否则,return 0
。
如果返回的索引匹配,则表示值为>=
下限和<
上限。如果他们不这样做,那么你就在范围之间。
答案 4 :(得分:1)
理想情况是interval tree(专用二叉树)。维基百科完全描述了该方法。比我好。在不牺牲性能空间的情况下,你不会比这更好。
答案 5 :(得分:0)
包含范围条目的简单链接列表应该足够快,即使是50-100范围。此外,您可以实现Skip List,例如上限,以加速这些范围查询。另一种可能性是Interval Tree。
最终我会选择最简单的:二分搜索。
答案 6 :(得分:0)
您的示例范围重叠,但问题是他们不会。我假设范围是一个错字。您可以,可以将目标存储在数组中,并使用索引作为范围。它很简单,但很难看,而且不易维护。您需要将数组初始化为0,然后对于每个范围,迭代这些索引并将每个索引设置为目标值。非常难看,但是查询时间不断,所以如果数字不会太高而且范围不会经常变化,那么可能很有用。
答案 7 :(得分:0)
将限制记录到set
(或map
)。当您致电insert
时,您将获得一对返回值。迭代器和布尔值。如果布尔值为true,则创建一个新元素,您必须稍后删除。在第一步之后使用迭代器并查看您找到的内容。
答案 8 :(得分:0)
这是一维空间索引。例如,四叉树风格的二叉树就可以了 - 还有其他几种广泛使用的方法。
答案 9 :(得分:0)
你可能会发现Minimal Perfect Hashing Function很有用,http://cmph.sourceforge.net/。