C中的哈希值

时间:2018-10-22 10:15:25

标签: c hash

我是C语言的新手,因为6年后又回到了C语言! 想要实现一个代码来为这种数据存储树:

string1 = "foo.bar.foo.\*" string2 = "foo.baz.\*" string3 = "foo.\*.bar"

     foo
      |
  |   |   |
 bar baz  *
  |   |   |
 foo  *  bar
  |
  *

尝试使用HashTable做到这一点:

struct entry_s {
    char *key;
    struct entry_s *value;
    struct entry_s *next;
};

但是我认为它没有用,什么是最好的方法,即使Hash Map是可以在C语言中使用的最佳数据结构?

2 个答案:

答案 0 :(得分:1)

似乎您想实现一个具有两个级别的映射:第一级将字符串映射到第二级映射,第二级将另一个键映射到一个值。例如,使用Javascript语法:

data = {
    "London": {
        "Paris": 450
    },
    "Paris": {
       "Madrid": 600,
       "Algiers": 700
    }
}

有几种方法可以实现这一目标。

JavaScript变量带有它们的类型,因此yozu可以在两个级别上使用相同的Map实现。在C语言中,您可以实现两个具有不同值类型的哈希表,例如:

struct OItem {                  // Outer map item
    const char *key;                // string key
    struct IMap *value;             // inner map value
    struct OItem *next;         
};

struct OMap {                   // Outer map
    struct OItem *head[oSize];      // hash table
};

struct IItem {                  // Inner map item
    const char *key;                // string key
    int value;                      // integer value
    struct IItem *next;
};

struct IMap {                   // Inner map
    struct IItem *head[iSize];      // hash table
};

这将为您提供上面的两级结构。 (这些哈希表的大小是固定的,因此,例如当二级映射稀疏时,您可能会浪费大量空间。在这里仅使用单个列表或平衡树可能会更好。如果使用第二级映射仅模拟始终对相同或相似数据进行哈希处理的对象,请考虑在此处使用结构。)

例如,您可以使用此结构和lookup("London", "Paris")。如果您不需要访问内部地图,也可以使用两个键将两个级别打包到一个大哈希表中:

struct Item {
    const char *key1;
    const char *key2;
    int value;
    struct Item *next;
};

struct Map {
    struct Item *head[hSize];
};

计算哈希时,请同时使用两个键,例如:

static unsigned int hash(const char *s1, const char *s2)
{
    unsigned long hash = 5381u;

    while (*s1) hash = hash * 33 ^ *s1++;
    hash = hash *33;
    while (*s2) hash = hash * 33 ^ *s2++;

    return hash;
}

查找项目时,请确保两个键均匹配:

int map_find(const struct Map *map,
    const char *k1, const char *k2)
{
    unsigned int h = hash(k1, k2) % hSize;
    struct Item *item = map->head[h];

    while (item) {
        if (strcmp(item->key1, k1) == 0
         && strcmp(item->key2, k2) == 0) {
            return item->value;
        }

        item = item->next;
    }

    return 0;
}

这种方法可能更具限制性,但是它的优点是您没有很多潜在的超大哈希表,而只有一个数据结构。

最后,无论您做什么,都不要使用在GitHub上找到的哈希表实现。作者承认这更多是编码活动。它不处理使用后释放内存的问题,并且哈希功能很差。


在实际使用案例中进行了编辑之后,很明显,您需要使用trie。您可以按照建议实施trie。键和值可以是实现中的任何内容,因此也可以是字符串和trie节点。您可以适应现有的实现,以使用指向trie节点结构的指针作为值。 (幸运的是,所有比较的东西都保持不变。)

我看到的一个问题是,使用固定的哈希表大小,最终将浪费大量空间。如果您的特里稀疏,最好只使用链表或平衡二叉树作为映射。无论如何,您都必须找到合适的库或自己滚动。

答案 1 :(得分:0)

您的问题确实没有道理,我认为这是因为您并不真正了解哈希表如何工作,因此下面是一些(粗略且未经测试的)代码向您展示它们如何工作:

typedef struct entry_s {
    char *key;
    char *value;
    struct entry_s *next;
} entry_t;

#define MAX_HASH 1234;

entry_t *myHashTable[MAX_HASH];


void insert(char *key, char *value);
    entry_t *entry;

    hash = calculateHash(key) % MAX_HASH;

    // Create entry

    entry = malloc(sizeof(entry_t));
    if(entry == NULL) exit(EXIT_FAILURE);
    entry->key = key;
    entry->value = value;

    // Add entry to the singly linked list for this hash

    entry->next = myHashTable[hash];
    myHashTable[hash] = entry;
}


entry *find(char *key) {
    entry_t *entry;

    hash = calculateHash(key) % MAX_HASH;
    entry = myHashTable[hash];
    while(entry != NULL) {
        if(strcmp(key, entry->key) == 0) {
            return entry;
        }
        entry = entry->next;
    }
    return NULL;
}


void delete(char *key) {
    entry_t *previous = NULL;
    entry_t *entry;

    hash = calculateHash(key) % MAX_HASH;
    entry = myHashTable[hash];
    while(entry != NULL) {
        if(strcmp(key, entry->key) == 0) {

            // Remove entry from the singly linked list for this hash

            if(previous == NULL) {
                myHashTable[hash] = entry->next;
            } else {
                previous->next = entry->next;
            }

            // Free the memory and return

            free(entry);
            return;
        }
        previous = entry;
        entry = entry->next;
    }
}

注意:对单链列表的充分了解将有助于您弄清此示例的工作。