我抽出了一个我需要保留在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 ]
是否有人认识到在内存中表示此数据结构的最有效方法是什么?
答案 0 :(得分:3)
如果我理解正确:
我也会假设数据键是稀疏的,所以直接的“[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
,因为您永远不想以这种方式遍历数据,并且维护一个真正的指针会导致错误或导致额外的插入或删除时的维护。
您可以将其视为两个列表列表:
num_list.next_num
是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
或分配第二个内存块并记住哪个索引位于哪个块中。
答案 1 :(得分:1)
处理您要问的多重索引的一般方法是使用对的哈希表和交换哈希函数,其中字母和数字键的顺序无关紧要:
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
调用。在所有其他情况下,代码将保持不变。有了这个,存储开销就是一个整数和每个数据项的指针。