我有大量的值,范围从0到5463458053.对于每个值,我希望映射一个包含字符串的集合,以便操作查找,即。即查找该集合中是否存在字符串所花费的时间最少。请注意,这组值可能不包含(0 - 5463458053)中的所有值,但是可以包含大量值。
我目前的解决方案是散列这些值(介于0 - 5463458053之间),并且对于每个值,都有一个与该值对应的字符串的链接列表。每次,我想检查一个给定集合中的字符串,我散列该值(在0 - 5463458053之间),获取链接列表,并遍历它以查明它是否包含上述字符串。
虽然这可能看起来更容易,但这需要花费一些时间。你能想到更快的解决方案吗?此外,碰撞将是可怕的。它们会导致错误的结果。
另一部分是关于在C中实现这一点。我将如何做到这一点?
注意:有人建议使用数据库。我想知道这是否有用。
我有点担心自然会耗尽RAM。 : - )
答案 0 :(得分:3)
您可以拥有哈希表的哈希表。第一个哈希表包含整数键。其中的值是散列集,即其键是字符串的散列表。
你也可以有一个散列集,其中键是整数和字符串对。
有许多库实现了这样的数据结构(在C ++中,标准库正在实现它们,如std::map
& std::set
)。对于C,我在想GTK的Glib。
使用散列技术,内存使用与所考虑的集合(或关系)的大小成比例。例如,你可以接受30%的空虚率。
答案 1 :(得分:1)
如果条目从0到N且连续:使用数组。 (索引速度是否足够快?)
编辑:这些数字似乎不是连续的。有一个大数的{key,value}对,其中密钥是一个大数字(> 32位但<64位),值是一串字符串。
如果内存可用,哈希表很容易,如果一串字符串不是太大,你可以按顺序检查它们。如果相同的字符串多次出现(很多次),你可以枚举字符串(在char * array []中放入指针并使用索引到该数组中。找到给定字符串的索引可能涉及另一个哈希表)
对于“主”哈希表,条目可能是:
struct entry {
struct entry *next; /* for overflow chain */
unsigned long long key; /* the 33bits number */
struct list *payload;
} entries[big_enough_for_all] ; /* if size is known in advance
, preallocation avoids a lot of malloc overhead */
如果你有足够的内存来存储磁头阵列,你肯定会这样做:
struct entry *heads[SOME_SIZE] = {NULL, };
,否则您可以将头数组与条目数组合并。 (就像我在这里做Lookups on known set of integer keys)
处理冲突很容易:当您走过溢出链时,只需将您的密钥与条目中的密钥进行比较即可。如果他们不平等:继续前进。如果他们是平等的:找到;现在去走吧。
答案 2 :(得分:1)
具有实现它的C库的Judy Array可能正是您所需要的基础。这是一个描述它的引用:
Judy是一个提供最先进核心技术的C库 实现稀疏动态数组。宣布Judy数组 只需使用空指针。 Judy数组仅在消耗内存时才消耗内存 已填充,但可以增长以利用所有可用内存 如果需要的话Judy的主要优势是可扩展性,高性能和高性能 记忆效率。 Judy数组是可扩展的,可以扩展到a 非常大量的元素,仅受机器内存的限制。以来 Judy被设计成一个无界数组,Judy数组的大小是 没有预先分配但随着数组动态增长和收缩 人口。 Judy将可扩展性与易用性相结合。 Judy API 可以通过简单的插入,检索和删除调用来访问 需要大量的编程。不需要调整和配置 (实际上甚至不可能)。另外,排序,搜索,计数和 顺序访问功能内置于Judy的设计中。
只要开发人员需要动态大小的数组,就可以使用Judy, 关联数组或简单易用的接口,不需要 为扩张或收缩做的返工。Judy可以替换许多常见的数据结构,例如数组,稀疏 数组,哈希表,B树,二叉树,线性列表,跳过列表, 其他排序和搜索算法,以及计数功能。
答案 3 :(得分:1)
大量字符串+快速查找+有限内存----&gt;你想要一个前缀trie,暴击位树或该家族的任何东西(非常类似的东西有许多不同的名字,例如PATRICIA ...... Judy也是这样的一个东西)。请参阅示例this。
这些数据结构允许前缀压缩,因此它们能够非常有效地存储大量字符串(在某种程度上必然会有共同的前缀)。此外,查找速度非常快。由于常见的大O符号没有考虑的缓存和分页效应,它们可以比哈希快,甚至更快,只占内存的一小部分(即使根据big-O,除了数组之外什么都没有)可以打败哈希)。
答案 4 :(得分:1)
您可以使用单个二叉搜索树(AVL / Red-black / ...)从所有集合中包含所有字符串,方法是按字典顺序键入(set_number,string)。您无需在任何位置显式存储集。例如,定义树节点顺序的比较器可能如下所示:
function compare_nodes (node1, node2) {
if (node1.set_number < node2.set_number) return LESS;
if (node1.set_number > node2.set_number) return GREATER;
if (node1.string < node2.string) return LESS;
if (node1.string > node2.string) return GREATER;
return EQUAL;
}
有了这样的结构,一些常见的操作是可能的(但可能不是直截了当的)。
要查找集合s
中是否存在字符串set_number
,只需在树中查找(set_number
,s
),即可获得完全匹配。
查找集合set_number
中的所有字符串:
function iterate_all_strings_in_set (set_number) {
// Traverse the tree from root downwards, looking for the given key. Return
// wherever the search ends up, whether it found the value or not.
node = lookup_tree_weak(set_number, "");
// tree empty?
if (node == null) {
return;
}
// We may have gotten the greatest node from the previous set,
// instead of the first node from the set we're interested in.
if (node.set_number != set_number) {
node = successor(node);
}
while (node != null && node.set_number == set_number) {
do_something_with(node.string);
node = successor(node);
}
}
以上内容需要O((k+1)*log(n))
时间,其中k
是set_number
中的字符串数量,n
是所有字符串的数量。
查找至少有一个相关字符串的所有集合编号:
function iterate_all_sets ()
{
node = first_node_in_tree();
while (node != null) {
current_set = node.set_number;
do_something_with(current_set);
if (cannot increment current_set) {
return;
}
node = lookup_tree_weak(current_set + 1, "");
if (node.set_number == current_set) {
node = successor(node);
}
}
}
以上内容需要O((k+1)*log(n))
时间,其中k
是至少包含一个字符串的集合数,n
是所有字符串的数量。
请注意,上面的代码假定在“do_something”调用中未修改树;如果移除节点,它可能会崩溃。
Addidionally,这是一些使用my own generic AVL tree implemetation来演示这一点的真实C代码。要编译它,只需从BadVPN源中的某处复制misc/
和structure/
文件夹,然后在那里添加包含路径。
注意我的AVL树在其节点中不包含任何“数据”,以及它如何不执行任何自己的内存分配。当您有大量数据可用时,这很方便。为了说清楚:下面的程序只有一个malloc()
,它是分配节点数组的那个。
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <assert.h>
#include <structure/BAVL.h>
#include <misc/offset.h>
struct value {
uint32_t set_no;
char str[3];
};
struct node {
uint8_t is_used;
struct value val;
BAVLNode tree_node;
};
BAVL tree;
static int value_comparator (void *unused, void *vv1, void *vv2)
{
struct value *v1 = vv1;
struct value *v2 = vv2;
if (v1->set_no < v2->set_no) {
return -1;
}
if (v1->set_no > v2->set_no) {
return 1;
}
int c = strcmp(v1->str, v2->str);
if (c < 0) {
return -1;
}
if (c > 0) {
return 1;
}
return 0;
}
static void random_bytes (unsigned char *out, size_t n)
{
while (n > 0) {
*out = rand();
out++;
n--;
}
}
static void random_value (struct value *out)
{
random_bytes((unsigned char *)&out->set_no, sizeof(out->set_no));
for (size_t i = 0; i < sizeof(out->str) - 1; i++) {
out->str[i] = (uint8_t)32 + (rand() % 94);
}
out->str[sizeof(out->str) - 1] = '\0';
}
static struct node * find_node (const struct value *val)
{
// find AVL tree node with an equal value
BAVLNode *tn = BAVL_LookupExact(&tree, (void *)val);
if (!tn) {
return NULL;
}
// get node pointer from pointer to its value (same as container_of() in Linux kernel)
struct node *n = UPPER_OBJECT(tn, struct node, tree_node);
assert(n->val.set_no == val->set_no);
assert(!strcmp(n->val.str, val->str));
return n;
}
static struct node * lookup_weak (const struct value *v)
{
BAVLNode *tn = BAVL_Lookup(&tree, (void *)v);
if (!tn) {
return NULL;
}
return UPPER_OBJECT(tn, struct node, tree_node);
}
static struct node * first_node (void)
{
BAVLNode *tn = BAVL_GetFirst(&tree);
if (!tn) {
return NULL;
}
return UPPER_OBJECT(tn, struct node, tree_node);
}
static struct node * next_node (struct node *node)
{
BAVLNode *tn = BAVL_GetNext(&tree, &node->tree_node);
if (!tn) {
return NULL;
}
return UPPER_OBJECT(tn, struct node, tree_node);
}
size_t num_found;
static void iterate_all_strings_in_set (uint32_t set_no)
{
struct value v;
v.set_no = set_no;
v.str[0] = '\0';
struct node *n = lookup_weak(&v);
if (!n) {
return;
}
if (n->val.set_no != set_no) {
n = next_node(n);
}
while (n && n->val.set_no == set_no) {
num_found++; // "do_something_with_string"
n = next_node(n);
}
}
static void iterate_all_sets (void)
{
struct node *node = first_node();
while (node) {
uint32_t current_set = node->val.set_no;
iterate_all_strings_in_set(current_set); // "do_something_with_set"
if (current_set == UINT32_MAX) {
return;
}
struct value v;
v.set_no = current_set + 1;
v.str[0] = '\0';
node = lookup_weak(&v);
if (node->val.set_no == current_set) {
node = next_node(node);
}
}
}
int main (int argc, char *argv[])
{
size_t num_nodes = 10000000;
// init AVL tree, using:
// key=(struct node).val,
// comparator=value_comparator
BAVL_Init(&tree, OFFSET_DIFF(struct node, val, tree_node), value_comparator, NULL);
printf("Allocating...\n");
// allocate nodes (missing overflow check...)
struct node *nodes = malloc(num_nodes * sizeof(nodes[0]));
if (!nodes) {
printf("malloc failed!\n");
return 1;
}
printf("Inserting %zu nodes...\n", num_nodes);
size_t num_inserted = 0;
// insert nodes, giving them random values
for (size_t i = 0; i < num_nodes; i++) {
struct node *n = &nodes[i];
// choose random set number and string
random_value(&n->val);
// try inserting into AVL tree
if (!BAVL_Insert(&tree, &n->tree_node, NULL)) {
printf("Insert collision: (%"PRIu32", '%s') already exists!\n", n->val.set_no, n->val.str);
n->is_used = 0;
continue;
}
n->is_used = 1;
num_inserted++;
}
printf("Looking up...\n");
// lookup all those values
for (size_t i = 0; i < num_nodes; i++) {
struct node *n = &nodes[i];
struct node *lookup_n = find_node(&n->val);
if (n->is_used) { // this node is the only one with this value
ASSERT(lookup_n == n)
} else { // this node was an insert collision; some other
// node must have this value
ASSERT(lookup_n != NULL)
ASSERT(lookup_n != n)
}
}
printf("Iterating by sets...\n");
num_found = 0;
iterate_all_sets();
ASSERT(num_found == num_inserted)
printf("Removing all strings...\n");
for (size_t i = 0; i < num_nodes; i++) {
struct node *n = &nodes[i];
if (!n->is_used) { // must not remove it it wasn't inserted
continue;
}
BAVL_Remove(&tree, &n->tree_node);
}
return 0;
}