从整数范围映射到任意单个整数

时间:2010-02-04 18:46:53

标签: c++ algorithm

在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 ++库可以做到这一点?

10 个答案:

答案 0 :(得分:13)

这里最好的方法确实是二元搜索,但任何有效的基于订单的搜索都能做得非常好。您实际上不必显式实现搜索和数据结构。您可以通过使用标准关联容器来间接使用它。

由于您的范围不重叠,因此解决方案非常简单。您可以立即使用std::map解决此问题,只需几行代码即可解决问题。

例如,这是一种可能的方法。假设我们将[ int, int ]范围映射到int值。让我们将范围表示为封闭开放范围,即如果原始范围是[0, 70],我们考虑使用[0, 71)范围。另外,让我们使用0的值作为“保留”值,这意味着“没有映射”(正如您在问题中所要求的那样)

const int EMPTY = 0;

您需要做的就是声明从intint的地图:

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)

一个简单的数组不足够吗?你不是说你有多少项目,但到目前为止最快的数据结构是一个简单的数组。

如果范围是:

  • 0..9 - &gt; 25
  • 10..19 - &gt; 42

然后数组就像这样:

[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,则创建一个新元素,您必须稍后删除。在第一步之后使用迭代器并查看您找到的内容。

http://www.cplusplus.com/reference/stl/set/insert/请参阅返回值

答案 8 :(得分:0)

这是一维空间索引。例如,四叉树风格的二叉树就可以了 - 还有其他几种广泛使用的方法。

答案 9 :(得分:0)

你可能会发现Minimal Perfect Hashing Function很有用,http://cmph.sourceforge.net/