在C中读/写多阵列数据结构

时间:2013-02-02 17:23:16

标签: c arrays

我抽出了一个我需要保留在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 ]

是否有人认识到在内存中表示此数据结构的最有效方法是什么?

2 个答案:

答案 0 :(得分:3)

如果我理解正确:

  • 您的数据有一个复合键,由一个数字和某种字母组成(您不会说它是字符还是字符串)。
  • 有时候你有alpha键,需要搜索数字,有时反之亦然(它恰好是读取和(over)写入,但这可能就在这一点上。)
  • 插入和删除很少见,但需要支持。

我也会假设数据键是稀疏的,所以直接的“[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_numNULL
  • 2B num_list.next_alpha指向2C
  • 2B num_alpha.next_alphaNULL
  • 2B num_alpha.next_num指向3B

因此,在单词中,num_list.next_num始终指向具有下一个数字的内容,但第一个字母可用。同样,alpha_list.next_alpha始终指向具有下一个字母的内容,但第一个号码可用。如果您没有查看辅助列表的头部,那么主列表的指针是NULL,因为您永远不想以这种方式遍历数据,并且维护一个真正的指针会导致错误或导致额外的插入或删除时的维护。

您可以将其视为两个列表列表:

  • num_list.next_numnum_list.next_alpha列表的头部列表。
  • aplha_list.next_alphaalpha_list.next_num列表的头部列表。

要查找项目,您首先要移动其中一个主要列表num_list.next_numaplha_list.next_alpha,然后选择其中一个辅助列表num_list.next_alphanum_alpha.next_num


所以,显然这有一些效率问题:

  • 所有这些小数据块的malloc效率低下。
  • 列表是O(n)访问。

如果您正在处理大量数据,我会做两件事:

  1. 使用某种平衡树而不是平面列表。然后,“名单的首领”成为“树的根”。

  2. 分配一个固定大小的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_strdupfree调用。在所有其他情况下,代码将保持不变。有了这个,存储开销就是一个整数和每个数据项的指针。