当键是两个整数时,C 地图或unordered_map

C++ map or unordered_map when key is two integers?

本文关键字:地图 unordered map 整数 两个      更新时间:2023-10-16

考虑一个情况,例如表示稀疏矩阵。例如,矩阵可能是1,000,000行x 1,000,000 col(或其他大尺寸(,在任何特定时间内,可能是50、100或几千个单元格为非零值。

我试图辨别最佳的C 数据结构来表示这一点。蛮力和非常糟糕的答案将是(例如,仅在1个单元格中为1个单元格中一个值,想象几百或几千个单元格(:

int numRows = 1000000;
int numCols = 1000000;
std::vector<std::vector<int>> sparseMatrix(numRows, std::vector<int>(numCols, 0));
int currentRow = 12345;
int currentCol = 98765;
sparseMatrix[currentRow][currentCol] = 10;
std::cout << "n" << "sparseMatrix[currentRow][currentCol] = " << sparseMatrix[currentRow][currentCol] << "nn";

显然,这是一场灾难,这是由于专用于未使用数据结构的99 %的内存。

下一个直观的选项(至少对我来说(是:

std::unordered_map<std::pair<int, int>, int> sparseMatrix;
int currentRow = 12345;
int currentCol = 98765;
std::pair<int, int> rowCol = std::make_pair(currentRow, currentCol);
sparseMatrix[rowCol] = 10;
std::cout << "n" << "sparseMatrix[rowCol] = " << sparseMatrix[rowCol] << "nn";

不幸的是,这无法与错误编译:

attempting to reference a deleted function

在谷歌搜索此主题之后,unordered_map似乎没有设置为使用一对作为钥匙。

据我所知,还有4个合法选择:

1(使用map,它确实接受一对整数作为键,而不是unordered_map,ex(此编译和运行(:

std::map<std::pair<int, int>, int> sparseMatrix;
int currentRow = 12345;
int currentCol = 98765;
std::pair<int, int> rowCol = std::make_pair(currentRow, currentCol);
sparseMatrix[rowCol] = 10;
std::cout << "n" << "sparseMatrix[rowCol] = " << sparseMatrix[rowCol] << "nn";

2(使用 unordered_map s的 unordered_map,ex(这也编译并运行(:

std::unordered_map<int, std::unordered_map<int, int>> sparseMatrix;
int currentRow = 12345;
int currentCol = 98765;
sparseMatrix[currentRow][currentCol] = 10;
std::cout << "n" << "sparseMatrix[currentRow][currentCol] = " << sparseMatrix[currentRow][currentCol] << "nn";

3(为行和Col整数制作自己的哈希功能,并将其馈入更典型的std::unordered_map<int, int>。这似乎是一个非常糟糕的选择,因为如果两个整数对映射到同一哈希键,这很难处理。

4(使用Boost :: Hash,我收集的看起来像:

std::unordered_map<std::pair<int, int>, int, boost::hash<pair<int, int>>> sparseMatrix;

我倾向于不喜欢此选项b/c 1(数据结构看起来很尴尬,2(我不确定如何执行其余的实现,3(在某些情况下可能不会提升可用。

因此,为了澄清我的问题,它们是:

1(上面哪种选项最适合大多数情况?(如果可能的话,我真的更喜欢坚持#1或#2(。

2(我对map S(红色树木(vs unordered_map S(哈希表((hash tables(我的印象是,我的印象是#1是1最好的内存,但#2会更快,在这种情况下,我的理解是正确的吗?

3(如果我对#1的正确正确,并且#2更快,那么我上面提到的一般情况下是否有明显的赢家(1,000,000 x 1,000,000矩阵,通常大约有1,000个值(还是大约是洗涤的差异?

4(#3和#4的实现将有多困难?如果#3和/或#4的实现非常好,则性能益处足以超过编码复杂性成本与#1或#2?

有人将这篇文章标记为重复之前,我已经阅读了这篇文章,为什么我不能将unordered_map用一对键编译为键?哪个涉及上面的选项,但没有为我在这里提出的问题提供答案。

有人说"使用内置启动稀疏矩阵"之前,是的,我知道Boost和其他一些库有一个稀疏的矩阵类。但是,我仍在问这个问题,但是,b/c是一张无序的地图,在其他情况下,密钥是2个整数我很有用,而且有些人也可能无法访问Boost或可能希望做自己更具体的实现一个特定目的。

显然,这是一场灾难,这是由于专用于未使用数据结构的99 %的内存。

这根本不清楚。现代OS倾向于为应用程序提供虚拟内存,这些虚拟内存只有在访问时才能用物理RAM备份,因此只有您将元素写入需要的备份RAM的内存页面。如果您的数组中最多有成千上万的条目,并且每个内存页面都为4K,那么您将使用数十兆字节的命令 - 几乎不会对典型的现代机器造成压力。因此,这很浪费,但不一定在问题上浪费。这不是缓存友好的 - 其性能含义可能会证明更加关注。

4(使用Boost :: Hash,我收集的看起来像:

std::unordered_map<std::pair<int, int>, int, boost::hash<pair<int, int>>> sparseMatrix;

我倾向于不喜欢此选项b/c 1(数据结构看起来很尴尬,2(我不确定如何执行其余的实现,3(在某些情况下可能不会提升可用。

1(看起来很尴尬吗?来吧... 2(无事可做 - 您只是像其他任何unordered_map 3(一样使用它,然后根据Boost创建自己的创建(请参阅此Q(:

template <class T>
inline void hash_combine(std::size_t& seed, const T& v)
{
    std::hash<T> hasher;
    seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
struct hash_pair
{
    std::size_t operator()(const std::pair<int, int>& p) const
    {
        std::size_t h = 0;
        hash_combine(h, p.first);
        hash_combine(h, p.second);
        return h;
    }
};

1(以上哪种选项最适合大多数情况?(如果可能的话,我真的更喜欢坚持#1或#2(。

对于大多数情况,您的编号选项都不是最好的选择:根据您对Boost的陈述担忧,根据BOOST的实现来创建自己的hash_combine是基于标准库容器的最佳通用解决方案。

2(我对地图(红色树木(vs unordered_maps(哈希表(的了解,我的印象是,#1在内存上是最好的,但#2会更快,我的理解是正确的这种情况?

内存使用情况不会大不相同。GCC的哈希表使用链接列表来存储值,其中每个值都需要带有链接指针的动态内存分配,再加上一个连续的存储阵列(每个是列表迭代器;阵列将是(re(,以维持合理的负载,以维持合理的负载因素,所以不会特别大(。map也使用每个值的动态内存分配 - 但为左/右指针分配一些额外的位置。大部分。

3(如果我在#1上正确的记忆更好,并且#2更快,那么我上面提到的一般情况下有明显的赢家(1,000,000 x 1,000,000矩阵,通常大约有1,000个值(还是差异大约洗涤?

如前所述,对于一个人来说,内存使用情况不应明显更好(尽管实现可能会有所不同(。至于更快的速度,当填充的值很少时,只需实现它们并测量即可。当填充元素的数量较大时,哈希表的优点更加始终如一。

4(#3和#4的实现将有多困难?如果#3和/或#4的实现非常好,则性能益处足以超过编码复杂性成本与#1或#2?

如前所述,您应该将#1与#4的撕裂进行比较。忘记#3-从您意识到自己"非常糟糕的选项"中,这是从根本上有缺陷

对于编码复杂性 - 几乎没有。只需复制上面的哈希实现,在实例化unordered_map时指定哈希策略,然后继续使用它。

如果您遇到实际问题在实现选项时,请提出一个新问题以获得帮助。

它可能无法解决您的问题,但是您的假设之一是错误的:

3(为行和col整数制作自己的哈希功能,然后馈送 这是一个更典型的std :: unordered_map。似乎 一个非常糟糕的选择,因为如果两个整数对映射到同一哈希 很难处理的钥匙。

处理哈希碰撞不是您必须做的事情,而是unordered_map为您做的事情。即使所有值的哈希都映射到同一整数,它也可以正确确保将不同的值视为不同的键,即使性能会降低。

也就是说,地图(mapunordered_map(的地图既可以正常工作并提供合理的性能,假设您只有几个元素。