[英]Read/Write multi array data structure in C
我提取出需要保留在C中的排序列表。一种方式最适合阅读,另一种方式最适合编写。
WRITE: search KeyNumeric then KeyAlpha and write *Data
Key1 : [ KeyA, *Data1A, KeyB, *Data1B, KeyC, *Data1C ]
Key2 : [ KeyA, *Data2A, KeyB, *Data2B, KeyC, *Data2C ]
Key3 : [ KeyA, *Data3A, KeyB, *Data3B, KeyC, *Data3C ]
READ: search KeyAlpha then KeyNumeric and read *Data
KeyA : [ Key1, *Data1A, Key2, *Data2A, Key3, *Data3A ]
KeyB : [ Key1, *Data1B, Key2, *Data2B, Key3, *Data3B ]
KeyC : [ Key1, *Data1C, Key2, *Data2C, Key3, *Data3C ]
有谁知道在内存中表示此数据结构的最有效方法是什么?
如果我正确理解:
我还要假设数据键是稀疏的,所以直接的“ [N] [A]”数组对您不起作用。
由于您希望对数据进行双索引,因此建议您需要某种链接结构:列表或树。
要使用链表进行操作,您的C结构可能如下所示:
struct stuff {
int num_key;
char alpha_key;
/* The number-first lists. */
struct {
struct stuff *next_num;
struct stuff *next_alpha;
} num_list;
/* The alpha-first links. */
struct {
struct stuff *next_alpha;
struct stuff *next_num;
} alpha_list;
struct data Data;
};
因此,如果您有数据项1A, 1B, 1C, 2A, 2B, 2C, 3A, 3B, 3C
这些链接将像这样工作:
1A num_list.next_num
指向2A
。 1A num_list.next_alpha
指向1B
。 1A num_alpha.next_alpha
指向1B
。 1A num_alpha.next_num
指向2A
。 2B num_list.next_num
为NULL
。 2B num_list.next_alpha
指向2C
。 2B num_alpha.next_alpha
为NULL
。 2B num_alpha.next_num
指向3B
。 因此,换句话说, num_list.next_num
始终指向带有下一个数字的东西,但第一个字母可用。 同样, alpha_list.next_alpha
始终指向带有下一个字母的东西,但第一个数字可用。 如果您不查看辅助列表的开头,则主列表的指针为NULL
因为您从不希望以这种方式遍历数据,而保持真实的指针可能会导致错误,或者对insert或删除。
您可以将其视为两个列表列表:
num_list.next_num
是num_list.next_alpha
列表的num_list.next_alpha
列表。 aplha_list.next_alpha
是负责人的名单alpha_list.next_num
名单。 要查找项目,请首先在主要列表之一num_list.next_num
或aplha_list.next_alpha
,然后在次要列表之一num_list.next_alpha
或num_alpha.next_num
。
因此,显然存在一些效率问题:
如果您要处理大量数据,我将做两件事:
使用某种平衡树而不是平面列表。 然后,“列表的头”成为“树的根”。
分配了一个固定大小的struct stuff
数组,并使用数组索引作为链接,而不是指针。 然后只需维护未使用插槽的“空闲列表”。 如果数据超出数组,则使用realloc
或分配第二个内存块,并记住哪个索引位于哪个块中。
处理您要查询的多个索引的一种很好的通用方法是使用成对的哈希表和可交换哈希函数,其中字母和数字键的顺序无关紧要:
typedef struct hash_node_s {
struct hash_node_s *next;
char *keyAlpha;
unsigned keyNumeric;
void *data
} HASH_NODE, *HASH_NODE_PTR;
#define HASH_TABLE_SIZE 997
typedef HASH_NODE_PTR HASH_TABLE[HASH_TABLE_SIZE];
// Hash a string and integer in one value.
unsigned hash(char *keyAlpha, unsigned keyNumeric) {
unsigned h = 0;
for (int i = 0; keyAlpha[i]; i++) {
h = h * 31 ^ keyAlpha[i] ^ keyNumeric;
keyNumeric *= 31;
}
return h;
}
static HASH_NODE *find_or_insert(HASH_TABLE tbl, char *keyAlpha, unsigned keyNumeric) {
unsigned h = hash(keyAlpha, keyNumeric) % HASH_TABLE_SIZE;
for (HASH_NODE *p = tbl[h]; p; p = p->next)
if (strcmp(keyAlpha, p->keyAlpha) == 0 && keyNumeric == p->keyNumeric)
return p;
HASH_NODE *n = safe_malloc(sizeof *n);
n->next = tbl[h];
n->keyAlpha = safe_strdup(keyAlpha);
n->keyNumeric = keyNumeric;
n->data = NULL;
tbl[h] = n;
return n;
}
void insert(HASH_TABLE tbl, char *keyAlpha, unsigned keyNumeric, void *data) {
find_or_insert(keyAlpha, keyNumeric)->data = data;
}
void write(HASH_TABLE tbl, unsigned keyNumeric, char *keyAlpha, void *data) {
find_or_insert(keyAlpha, keyNumeric)->data = data;
}
void *read(HASH_TABLE tbl, char *keyAlpha, unsigned keyNumeric) {
return find_or_insert(keyAlpha, keyNumeric)->data;
}
void delete(HASH_TABLE tbl, char *keyAlpha, unsigned keyNumeric)
{
unsigned h = hash(keyAlpha, keyNumeric) % HASH_TABLE_SIZE;
for (HASH_NODE *q = NULL, *p = tbl[h];
p;
q = p, p = p->next)
if (strcmp(keyAlpha, p->keyAlpha) == 0 && keyNumeric == p->keyNumeric) {
if (q)
q->next = p->next;
else
tbl[h] = p->next;
safe_free(p->keyAlpha);
safe_free(p);
return;
}
}
该代码未经测试,但除了轻微的错字外,它应该是可靠的。
所有操作的成本大致相同。 计算哈希函数取决于密钥长度。 除此之外,所有操作都是概率O(1),这意味着除非您遇到哈希函数不会产生伪随机结果或让表负载过高的糟糕情况,否则这确实会非常快。
该代码的弱点在于,每个元素存储两个键,并且字符串键可能会很大。 但这可以通过使用单独的字符串表(字符串的哈希表)来解决,以使重复的字符串由同一指针表示。 字符串表的插入和删除(当引用计数达到零时)将替换safe_strdup
和free
调用。 在所有其他情况下,代码将保持不变。 这样,存储开销就是一个整数和每个数据项的指针。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.