【问题标题】:Quick Way to Implement Dictionary in C在 C 中实现字典的快速方法
【发布时间】:2011-05-22 00:37:19
【问题描述】:

在用 C 编写程序时,我想念的一件事是字典数据结构。在 C 中实现一个最方便的方法是什么?我不是在寻找性能,而是从头开始编码的容易程度。我也不希望它是通用的——像char*int 这样的东西就可以了。但我确实希望它能够存储任意数量的项目。

这更多是作为练习。我知道有可以使用的第 3 方库。但请考虑一下,它们不存在。在这种情况下,实现满足上述要求的字典的最快方法是什么。

【问题讨论】:

  • 如果您错过了为您提供的服务,那么您为什么要从头开始制作它,而不是使用第三方实现?
  • 是的,这种选择总是存在的。我提出这个问题更像是一个练习。
  • 用 C 编写哈希表是一项有趣的练习——每个认真的 C 程序员都应该至少做一次。
  • 我认为字典是一种数据类型而不是数据结构,因为它可以通过多种方式实现——列表、哈希表、树、自平衡树等。你是要字典还是哈希表?
  • 相关:如何用 C 表示类似 Python 的字典?[](stackoverflow.com/questions/3269881/…)

标签: c data-structures dictionary


【解决方案1】:

The C Programming Language 的第 6.6 节介绍了一个简单的字典(哈希表)数据结构。我认为没有比这更简单的字典实现了。为了您的方便,我在这里复制代码。

struct nlist { /* table entry: */
    struct nlist *next; /* next entry in chain */
    char *name; /* defined name */
    char *defn; /* replacement text */
};

#define HASHSIZE 101
static struct nlist *hashtab[HASHSIZE]; /* pointer table */

/* hash: form hash value for string s */
unsigned hash(char *s)
{
    unsigned hashval;
    for (hashval = 0; *s != '\0'; s++)
      hashval = *s + 31 * hashval;
    return hashval % HASHSIZE;
}

/* lookup: look for s in hashtab */
struct nlist *lookup(char *s)
{
    struct nlist *np;
    for (np = hashtab[hash(s)]; np != NULL; np = np->next)
        if (strcmp(s, np->name) == 0)
          return np; /* found */
    return NULL; /* not found */
}

char *strdup(char *);
/* install: put (name, defn) in hashtab */
struct nlist *install(char *name, char *defn)
{
    struct nlist *np;
    unsigned hashval;
    if ((np = lookup(name)) == NULL) { /* not found */
        np = (struct nlist *) malloc(sizeof(*np));
        if (np == NULL || (np->name = strdup(name)) == NULL)
          return NULL;
        hashval = hash(name);
        np->next = hashtab[hashval];
        hashtab[hashval] = np;
    } else /* already there */
        free((void *) np->defn); /*free previous defn */
    if ((np->defn = strdup(defn)) == NULL)
       return NULL;
    return np;
}

char *strdup(char *s) /* make a duplicate of s */
{
    char *p;
    p = (char *) malloc(strlen(s)+1); /* +1 for ’\0’ */
    if (p != NULL)
       strcpy(p, s);
    return p;
}

请注意,如果两个字符串的哈希值发生冲突,可能会导致O(n) 查找时间。您可以通过增加HASHSIZE 的值来降低发生冲突的可能性。有关数据结构的完整讨论,请参阅本书。

【讨论】:

  • 为什么hashval = *s + 31 * hashval; 正好是 31 而不是别的?
  • 31 是素数。素数通常用于散列函数中以减少冲突的概率。它与整数分解有关(即您不能分解素数)。
  • @アレックス 31 在测试数据上表现良好。选择素数有其好处,但如果哈希表本身的大小是素数,则没有必要。
  • 请注意,K&R C 散列算法是一种令人震惊的散列算法。请参阅:programmers.stackexchange.com/questions/49550/…,详细了解真正的可怕程度。别再用了!!
  • @Overdrivr:在这种情况下不需要。 hashtab 是静态的。具有静态持续时间的未初始化变量(即在函数之外声明的变量,以及使用存储类 static 声明的变量)保证以正确类型的零开始(即:0 或 NULL 或 0.0)
【解决方案2】:

最快的方法是使用已经存在的实现,例如uthash

而且,如果您真的想自己编写代码,可以检查和重用来自uthash 的算法。它是 BSD 许可的,所以除了传达版权声明的要求之外,你可以用它做很多事情。

【讨论】:

    【解决方案3】:

    为了易于实现,很难天真地搜索数组。除了一些错误检查之外,这是一个完整的实现(未经测试)。

    typedef struct dict_entry_s {
        const char *key;
        int value;
    } dict_entry_s;
    
    typedef struct dict_s {
        int len;
        int cap;
        dict_entry_s *entry;
    } dict_s, *dict_t;
    
    int dict_find_index(dict_t dict, const char *key) {
        for (int i = 0; i < dict->len; i++) {
            if (!strcmp(dict->entry[i], key)) {
                return i;
            }
        }
        return -1;
    }
    
    int dict_find(dict_t dict, const char *key, int def) {
        int idx = dict_find_index(dict, key);
        return idx == -1 ? def : dict->entry[idx].value;
    }
    
    void dict_add(dict_t dict, const char *key, int value) {
       int idx = dict_find_index(dict, key);
       if (idx != -1) {
           dict->entry[idx].value = value;
           return;
       }
       if (dict->len == dict->cap) {
           dict->cap *= 2;
           dict->entry = realloc(dict->entry, dict->cap * sizeof(dict_entry_s));
       }
       dict->entry[dict->len].key = strdup(key);
       dict->entry[dict->len].value = value;
       dict->len++;
    }
    
    dict_t dict_new(void) {
        dict_s proto = {0, 10, malloc(10 * sizeof(dict_entry_s))};
        dict_t d = malloc(sizeof(dict_s));
        *d = proto;
        return d;
    }
    
    void dict_free(dict_t dict) {
        for (int i = 0; i < dict->len; i++) {
            free(dict->entry[i].key);
        }
        free(dict->entry);
        free(dict);
    }
    

    【讨论】:

    • “为了便于实施”:你说得对:这是最简单的。此外,它还实现了 OP 的请求“我确实希望它能够存储任意数量的项目”——最高投票的答案并没有做到这一点(除非你认为选择一个 compile time 常量满足“任意”...)
    • 这可能是一种有效的方法,具体取决于用例,但 OP 明确要求字典,这绝对不是字典。
    【解决方案4】:

    我很惊讶没有人提到 hsearch/hcreate 一组库,虽然在 Windows 上不可用,但 POSIX 强制要求,因此在 Linux / GNU 系统中可用。

    该链接有一个简单而完整的基本示例,很好地解释了它的用法。

    它甚至具有线程安全的变体,易于使用且非常高效。

    【讨论】:

    • 值得注意的是这里的人说它有点不可用,虽然我自己没有尝试过:stackoverflow.com/a/6118591/895245
    • 很公平,但是,我已经在至少一个应用程序中尝试了 hcreate_r(用于多个哈希表)版本,它运行了相当长的时间来考虑它的真实世界。同意它是一个 GNU 扩展,但许多其他库也是如此。虽然我仍然认为您可能仍然可以将它用于在某些现实世界应用程序中运行的一个大键值对
    【解决方案5】:

    GLib 和 gnulib

    如果您没有更具体的要求,这些可能是您最好的选择,因为它们广泛可用、便携且可能高效。

    另见:Are there any open source C libraries with common data structures?

    【讨论】:

      【解决方案6】:

      创建一个简单的散列函数和一些结构的链表,根据散列,分配在哪个链表中插入值。也可以使用哈希来检索它。

      前段时间我做了一个简单的实现:

      ... #define K 16 // 链接系数 结构字典 { 字符*名称; /* 键名 */ 整数值; /* 价值 */ 结构字典*下一个; /* 链接字段 */ }; typedef struct dict 字典; 字典 *table[K]; int 初始化 = 0; 无效 putval ( char *,int); 无效的 init_dict() { 初始化 = 1; 诠释我; for(i=0;iname = (char *) malloc (strlen(key_name)+1); ptr->val = sval; strcpy (ptr->name,key_name); ptr->next = (struct dict *)table[hsh]; 表[hsh] = ptr; } int getval ( char *key_name ) { int hsh = hash(key_name); 字典 *ptr; for (ptr = table[hsh]; ptr != (dict *) 0; ptr = (dict *)ptr->next) if (strcmp (ptr->name,key_name) == 0) 返回 ptr->val; 返回-1; }

      【讨论】:

      • 你不是漏掉了一半的代码吗? “hash()”和“putval()”在哪里?
      【解决方案7】:

      这是一个快速实现,我用它从字符串中获取“矩阵”(结构)。 您可以拥有更大的数组并在运行时更改其值:

      typedef struct  { int** lines; int isDefined; }mat;
      mat matA, matB, matC, matD, matE, matF;
      
      /* an auxilary struct to be used in a dictionary */
      typedef struct  { char* str; mat *matrix; }stringToMat;
      
      /* creating a 'dictionary' for a mat name to its mat. lower case only! */
      stringToMat matCases [] =
      {
          { "mat_a", &matA },
          { "mat_b", &matB },
          { "mat_c", &matC },
          { "mat_d", &matD },
          { "mat_e", &matE },
          { "mat_f", &matF },
      };
      
      mat* getMat(char * str)
      {
          stringToMat* pCase;
          mat * selected = NULL;
          if (str != NULL)
          {
              /* runing on the dictionary to get the mat selected */
              for(pCase = matCases; pCase != matCases + sizeof(matCases) / sizeof(matCases[0]); pCase++ )
              {
                  if(!strcmp( pCase->str, str))
                      selected = (pCase->matrix);
              }
              if (selected == NULL)
                  printf("%s is not a valid matrix name\n", str);
          }
          else
              printf("expected matrix name, got NULL\n");
          return selected;
      }
      

      【讨论】:

        【解决方案8】:

        哈希表是简单“字典”的传统实现。如果您不关心速度或大小,只需search for it。有许多免费提供的实现。

        Here's the first one I saw -- 乍一看,我觉得没问题。 (这是非常基本的。如果您真的希望它保存无限量的数据,那么您需要在表内存增长时添加一些逻辑来“重新分配”表内存。)

        【讨论】:

          【解决方案9】:

          散列是关键。我认为为此使用查找表和散列键。你可以在网上找到很多哈希函数。

          【讨论】:

            【解决方案10】:

            最快的方法是使用二叉树。它最坏的情况也只有 O(logn)。

            【讨论】:

            • 这是不正确的。当二叉树不平衡时,最坏情况的查找是 O(n)(由于错误的插入顺序导致的退化情况,基本上会导致链接列表)。
            【解决方案11】:

            此外,您还可以使用 Google CityHash:

            #include <stdlib.h>
            #include <stddef.h>
            #include <stdio.h>
            #include <string.h>
            
            #include <byteswap.h>
            
            #include "city.h"
            
            void swap(uint32* a, uint32* b) {
                int temp = *a;
                *a = *b;
                *b = temp;
            }
            
            #define PERMUTE3(a, b, c) swap(&a, &b); swap(&a, &c);
            
            // Magic numbers for 32-bit hashing.  Copied from Murmur3.
            static const uint32 c1 = 0xcc9e2d51;
            static const uint32 c2 = 0x1b873593;
            
            static uint32 UNALIGNED_LOAD32(const char *p) {
              uint32 result;
              memcpy(&result, p, sizeof(result));
              return result;
            }
            
            static uint32 Fetch32(const char *p) {
              return UNALIGNED_LOAD32(p);
            }
            
            // A 32-bit to 32-bit integer hash copied from Murmur3.
            static uint32 fmix(uint32 h)
            {
              h ^= h >> 16;
              h *= 0x85ebca6b;
              h ^= h >> 13;
              h *= 0xc2b2ae35;
              h ^= h >> 16;
              return h;
            }
            
            static uint32 Rotate32(uint32 val, int shift) {
              // Avoid shifting by 32: doing so yields an undefined result.
              return shift == 0 ? val : ((val >> shift) | (val << (32 - shift)));
            }
            
            static uint32 Mur(uint32 a, uint32 h) {
              // Helper from Murmur3 for combining two 32-bit values.
              a *= c1;
              a = Rotate32(a, 17);
              a *= c2;
              h ^= a;
              h = Rotate32(h, 19);
              return h * 5 + 0xe6546b64;
            }
            
            static uint32 Hash32Len13to24(const char *s, size_t len) {
              uint32 a = Fetch32(s - 4 + (len >> 1));
              uint32 b = Fetch32(s + 4);
              uint32 c = Fetch32(s + len - 8);
              uint32 d = Fetch32(s + (len >> 1));
              uint32 e = Fetch32(s);
              uint32 f = Fetch32(s + len - 4);
              uint32 h = len;
            
              return fmix(Mur(f, Mur(e, Mur(d, Mur(c, Mur(b, Mur(a, h)))))));
            }
            
            static uint32 Hash32Len0to4(const char *s, size_t len) {
              uint32 b = 0;
              uint32 c = 9;
              for (size_t i = 0; i < len; i++) {
                signed char v = s[i];
                b = b * c1 + v;
                c ^= b;
              }
              return fmix(Mur(b, Mur(len, c)));
            }
            
            static uint32 Hash32Len5to12(const char *s, size_t len) {
              uint32 a = len, b = len * 5, c = 9, d = b;
              a += Fetch32(s);
              b += Fetch32(s + len - 4);
              c += Fetch32(s + ((len >> 1) & 4));
              return fmix(Mur(c, Mur(b, Mur(a, d))));
            }
            
            uint32 CityHash32(const char *s, size_t len) {
              if (len <= 24) {
                return len <= 12 ?
                    (len <= 4 ? Hash32Len0to4(s, len) : Hash32Len5to12(s, len)) :
                    Hash32Len13to24(s, len);
              }
            
              // len > 24
              uint32 h = len, g = c1 * len, f = g;
              uint32 a0 = Rotate32(Fetch32(s + len - 4) * c1, 17) * c2;
              uint32 a1 = Rotate32(Fetch32(s + len - 8) * c1, 17) * c2;
              uint32 a2 = Rotate32(Fetch32(s + len - 16) * c1, 17) * c2;
              uint32 a3 = Rotate32(Fetch32(s + len - 12) * c1, 17) * c2;
              uint32 a4 = Rotate32(Fetch32(s + len - 20) * c1, 17) * c2;
              h ^= a0;
              h = Rotate32(h, 19);
              h = h * 5 + 0xe6546b64;
              h ^= a2;
              h = Rotate32(h, 19);
              h = h * 5 + 0xe6546b64;
              g ^= a1;
              g = Rotate32(g, 19);
              g = g * 5 + 0xe6546b64;
              g ^= a3;
              g = Rotate32(g, 19);
              g = g * 5 + 0xe6546b64;
              f += a4;
              f = Rotate32(f, 19);
              f = f * 5 + 0xe6546b64;
              size_t iters = (len - 1) / 20;
              do {
                uint32 a0 = Rotate32(Fetch32(s) * c1, 17) * c2;
                uint32 a1 = Fetch32(s + 4);
                uint32 a2 = Rotate32(Fetch32(s + 8) * c1, 17) * c2;
                uint32 a3 = Rotate32(Fetch32(s + 12) * c1, 17) * c2;
                uint32 a4 = Fetch32(s + 16);
                h ^= a0;
                h = Rotate32(h, 18);
                h = h * 5 + 0xe6546b64;
                f += a1;
                f = Rotate32(f, 19);
                f = f * c1;
                g += a2;
                g = Rotate32(g, 18);
                g = g * 5 + 0xe6546b64;
                h ^= a3 + a1;
                h = Rotate32(h, 19);
                h = h * 5 + 0xe6546b64;
                g ^= a4;
                g = bswap_32(g) * 5;
                h += a4 * 5;
                h = bswap_32(h);
                f += a0;
                PERMUTE3(f, h, g);
                s += 20;
              } while (--iters != 0);
              g = Rotate32(g, 11) * c1;
              g = Rotate32(g, 17) * c1;
              f = Rotate32(f, 11) * c1;
              f = Rotate32(f, 17) * c1;
              h = Rotate32(h + g, 19);
              h = h * 5 + 0xe6546b64;
              h = Rotate32(h, 17) * c1;
              h = Rotate32(h + f, 19);
              h = h * 5 + 0xe6546b64;
              h = Rotate32(h, 17) * c1;
              return h;
            }
            

            【讨论】:

              猜你喜欢
              • 2011-08-17
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-01-01
              • 2019-12-03
              • 2023-04-07
              • 2014-09-19
              • 2011-03-29
              相关资源
              最近更新 更多