繁体   English   中英

用C读/写多数组数据结构

[英]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_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因为您从不希望以这种方式遍历数据,而保持真实的指针可能会导致错误,或者对insert或删除。

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

  • num_list.next_numnum_list.next_alpha列表的num_list.next_alpha列表。
  • aplha_list.next_alpha是负责人的名单alpha_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或分配第二个内存块,并记住哪个索引位于哪个块中。

处理您要查询的多个索引的一种很好的通用方法是使用成对的哈希表和可交换哈希函数,其中字母和数字键的顺序无关紧要:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM