【发布时间】:2019-01-08 12:46:06
【问题描述】:
我最近编写了一个程序,处理大量 if/else 语句以返回特定值。有人建议改用查找表。我的问题是,
- 它们是如何工作的以及您如何实施它们?
- 映射、哈希表和查找表之间有什么区别。
【问题讨论】:
标签: c++ dictionary hashtable lookup
我最近编写了一个程序,处理大量 if/else 语句以返回特定值。有人建议改用查找表。我的问题是,
【问题讨论】:
标签: c++ dictionary hashtable lookup
我的问题是,它们是如何工作的,你又是如何实现的?什么是 stl map、hash表、lookup表的区别。
您正在寻找的是一种有效的机制,您可以通过该机制查找与给定键对应的值。
您当前的机制(一长串 if/else-if 命令)效率很低,因为如果您有 N 个可能的值可供选择,则(平均而言)您必须将候选键与 (N/ 2)在找到匹配的那个之前的其他键,您可以停止查找。 (这称为 O(N) 复杂度)
那么其他的选择是什么?
最简单的实际上只是一个值数组,例如
const char myLookupTable[1000] = {
"zero",
"one",
"two",
[...]
"nine hundred and ninety-nine"
};
... 使用这样的查找表,您获取一个键(在本例中是一个介于 0 和 999 之间的数字,包括 0 到 999),然后使用单个数组查找来查找相应的值:
const char * val = myLookupTable[500];
这是超高效的(O(1) 复杂度——它总是在恒定时间内完成,不管数组有多大!),但它只适用于你的键是连续无符号整数的情况(并且相对小)值范围。例如,如果您的键是字符串,则此方法不适用。
为了更灵活,下一个选项是 STL 的std::map。 std::map 为您提供从任何键类型到任何值类型的快速键-> 值查找。在内部,它被实现为tree:每个键值对都以这样的方式插入到树中,即树保持排序,最小的键在树的左侧,最大的键在右侧。因此,在 std::map 中查找键(及其关联值)只需从树的根节点开始并将该节点处的键与您正在查找的键进行比较:它是否小于您的钥匙?然后移动到右手边的孩子。或者它比你的钥匙更大?然后移动到左边的孩子。重复此操作,直到您到达树的底部,此时您将找到您正在寻找的键值对,或者您会发现它不存在。这是一个 O(log(N)) 复杂度的算法,因为对于其中包含 N 个值的树,它需要 log(N) 比较才能完成查找。 O(log(N)) 被认为是相当不错的效率。
您提到的最终数据结构是hash table(见std::unordered_map)。哈希表做的事情有点不同——在内部它是一个数组,但为了避免查找表方法的局限性,它还带有一种算法,用于确定给定键/值对在其数组中的位置要存储。它通过为您传入的键对象计算 哈希码 来实现这一点——然后使用该代码计算数组的偏移量(例如 int array_offset = hash_code % array_size)并查看数组以查看请求的键值对是否存在。如果是,那么它就完成了(再次执行 O(1) !);或者如果插槽为空,则它知道您的密钥不在表中,并且可以立即返回失败(再次为 O(1))。如果该槽被其他键/值对占用,则哈希表将需要回退到另一种算法来整理hash collision;不同的哈希表处理不同的方式,但通常仍然相当有效。
【讨论】:
由于 StackOverflow 不是一个教程网站,您的问题实在是太宽泛了,但今天早上我感觉很亲切...
“查找表”只是一个容器(任何种容器),其中包含您查找的值,并且通常映射到其他值。
以最简单的形式,考虑以下几点:
struct MapIntToString
{
int value;
char* string;
};
MapIntToString my_map[] = {
{ 1, "one" },
{ 2, "two" },
{ 3, "three" },
// ...
};
上面可以被认为是一个查找表。您可以遍历(循环)my_map 以查找(查找)整数 2,然后从中选择字符串 "two"。
根据您的需要和用例,上述示例可能还不够。上面的代码基本上是用普通 C 语言完成的,而不是 C++。对于 C++,有更好的映射值容器,例如 std::map 和 std::unordered_map。
但有时标准类型可能还不够,还有许多其他数据结构可以用于查找。
【讨论】:
switch 语句有时是最佳选择;对于足够数量的选项,它通常会编译为范围检查,然后使用查找表来确定跳转目标(对于少量选项,它会编译为类似于if/else 链,因为这是最有效的解决方案)。