【问题标题】:Find the lowest unused number找到最低的未使用号码
【发布时间】:2009-06-11 10:26:39
【问题描述】:

我已经设置了一个标准映射来映射一些数字,此时我知道我正在从一个映射到哪些数字,例如:

std::map<int, int> myMap;

map[1] = 2;
map[2] = 4;
map[3] = 6;

但是,稍后,我想将一些数字映射到不在地图中的最低数字,例如:

map[4] = getLowestFreeNumberToMapTo(map); // I'd like this to return 1
map[5] = getLowestFreeNumberToMapTo(map); // I'd like this to return 3

有什么简单的方法吗?

当我将它们添加到地图时,我考虑构建一个有序的数字列表,这样我就可以只查找 1,而不是找到它,使用它,添加它等等。

【问题讨论】:

  • 我希望最低值是 INT_MIN 和 INT_MIN+1 ?如果它是一张地图

标签: c++ algorithm stl


【解决方案1】:

类似

typedef std::set<int> SetType;
SetType used;           // The already used numbers
int freeCounter = 1;    // The first available free number

void AddToMap(int i)
{
    used.insert(i);
    // Actually add the value to map
}

void GetNewNumber()
{
    SetType::iterator iter = used.lower_bound(freeCounter);
    while (iter != used.end() && *iter == freeCounter)
    {
        ++iter;
        ++freeCounter;
    }
    return freeCounter++;
}

如果您的地图很大但很稀疏,这将像 o(log(N)) 一样工作,其中 N 是地图中的项目数 - 在大多数情况下,您不必遍历集合,或者只需几步。 否则,如果地图中的空白很少,那么您最好在 [1..maxValueInTheMap] 范围内拥有一组免费项目。

【讨论】:

  • 这是一个比初看起来更好的算法。请注意,虽然 some 调用 GetNewNumber() 可能需要很多步骤,但 freeCounter 递增的 total 次数(因此 GetNewNumber() 的总运行时间除以集合操作的对数因子)受映射中分配的项目数的限制(因为如果您分配了 N 个数字,则 freeCounter ≤ N+1)。因此,如果您已分配给 map[N],那么到目前为止对 GetNewNumber() 的所有调用将只花费 O(N log N) 时间:GetNewNumber 是 O(log N) 摊销的。 en.wikipedia.org/wiki/Amortized_analysis
  • (换句话说,即使地图不是稀疏的,算法也能很好地工作;它总是 O(log N) 摊销。)
  • GetNewNumber()是常见的基于index-value coordinataion的缺失项搜索,但我还是能理解为什么你们说这是一个o(logN)函数。
【解决方案2】:

在 UNIX 内核中查找最小的未使用数字是一个非常常见的操作,就像每个 open/socket/etc 一样。 syscall 应该绑定到最低的未使用 FD 编号。

在 Linux 上,fs/file.c­#alloc_fd 中的算法是:

  • 跟踪next_fd,这是一个低水位线——不一定是 100% 准确
  • 每当释放 FD 时,next_fd = min(fd, next_fd)
  • 要分配一个FD,从next_fd开始搜索位图——lib/find_next_bit.c­#find_next_zero_bit是线性的但仍然非常快,因为它需要BITS_PER_LONG一次步幅
  • 分配FD后,next_fd = fd + 1

FreeBSD 的sys/kern/kern_descrip.c­#fdalloc 遵循同样的思路:从int fd_freefile; /* approx. next free file */ 开始,向上搜索位图。


然而,这些都是在大多数进程打开的FD很少,而很少有数千的假设下运行的。如果数字会变得更高,并且孔洞稀疏,那么常见的解决方案(据我所知)是

#include <algorithm>
#include <functional>
#include <vector>

using namespace std;

int high_water_mark = 0;
vector<int> unused_numbers = vector<int>();

int get_new_number() {
    if (used_numbers.empty())
        return high_water_mark++;
    pop_heap(unused_numbers.begin(), unused_numbers.end(), greater<int>());
    return unused_numbers.pop_back();
}

void recycle_number(int number) {
    unused_numbers.push_back(number);
    push_heap(unused_numbers.begin(), unused_numbers.end(), greater<int>());
}

(未经测试的代码......想法是:保持高水位线;尝试从低于高水位线的unused窃取,否则向上高水位线;返回释放到unused

如果您的假设是 使用 数字将是稀疏的,那么 Dmitry 的解决方案更有意义。

【讨论】:

  • 正如我在对另一个答案的评论中指出的那样,另一个解决方案实际上并不要求使用的数字是稀疏的(至少要符合对数因子。)这个答案很有趣:这个问题似乎只有插入,没有删除,并且可能分配了任意高的数字,但您的问题是插入和删除,并且总“范围”足够小,可以保留未使用数字的列表。跨度>
  • 我提到“稀疏”是因为如果你一次只有 5 个未使用的数字,O(5) 很好而且很小,但如果其中一半没有使用,O(log n) 是(相对) 要大得多。
【解决方案3】:

我会使用双向地图类来解决这个问题。这样你就可以简单地检查值 1 是否存在等等。

编辑

使用 bimap 的好处是它已经存在健壮的实现,即使搜索下一个空闲数字是 O(n),如果 n 很大(或者如果 n 适中并且这被非常频繁地调用)。总体上实现了一个不太容易出错且易于维护的简单实现。

如果 n 很大或此操作非常频繁地执行,则值得投入精力实施更高级的解决方案。恕我直言。

【讨论】:

  • 这隐藏了算法......这是 Q。哪个算法用于搜索该类的键项?
  • 检查值 n 是否存在以找到最低可用声音,如 O(n) 练习
猜你喜欢
  • 2023-01-13
  • 2023-02-03
  • 2014-07-11
  • 1970-01-01
  • 1970-01-01
  • 2011-03-27
  • 1970-01-01
  • 1970-01-01
  • 2012-10-29
相关资源
最近更新 更多