C语言哈希表uthash的使用方法详解附下载链接

uthash简介

  由于C语言本身不存在哈希,但是当需要使用哈希表的时候自己构建哈希会异常复杂。因此,我们可以调用开源的第三方头文件,这只是一个头文件:uthash.h。我们需要做的就是将头文件复制到您的项目中,然后:#include "uthash.h"。由于uthash仅是头文件,因此没有可链接的库代码。

  使用uthash添加,查找和删除通常是常数时间的操作,此哈希的目标是简约高效。它大约有1000行C。它会自动内联,因为它是作为宏实现的。
  uthash还包括三个额外的头文件,主要提供链表,动态数组和字符串。utlist.h为C结构提供了链接列表宏。utarray.h使用宏实现动态数组。utstring.h实现基本的动态字符串。
  github下载链接:https://github.com/troydhanson/uthash

uthash的使用

定义结构体

  这里我们将id作为一个索引值,也就是键值,将name作为value。

#include "uthash.h"
struct my_struct {
    int id;                    /* key */
    char name[10];
    UT_hash_handle hh;         /* makes this structure hashable */
};
/*声明哈希为NULL指针*/
struct my_struct *users = NULL;    /* important! initialize to NULL */

  注意:一定要包含UT_hash_handle hh;hh不需要初始化。它可以命名为任何名称,但是我们一般都命名为hh。

添加

  HASH_ADD_INT表示添加的键值为int类型
  HASH_ADD_STR表示添加的键值为字符串类型
  HASH_ADD_PTR表示添加的键值为指针类型
  HASH_ADD表示添加的键值可以是任意类型

void add_user(int user_id,char *name) {
    struct my_struct *s;
    /*重复性检查,当把两个相同key值的结构体添加到哈希表中时会报错*/
    HASH_FIND_INT(users,&user_id,s);  /* id already in the hash? */
    /*只有在哈希中不存在ID的情况下,我们才创建该项目并将其添加。否则,我们只修改已经存在的结构。*/
    if (s==NULL) {
      s = (struct my_struct *)malloc(sizeof *s);
      s->id = user_id;
      HASH_ADD_INT( users,id,s );  /* id: name of key field */
    }
    strcpy(s->name,name);
}

  HASH_ADD_INT函数中,第一个参数users是哈希表,第二个参数id是键字段的名称。最后一个参数s是指向要添加的结构的指针。

查找

struct my_struct *find_user(int user_id) {
    struct my_struct *s;
    HASH_FIND_INT( users,s );  /* s: output pointer */
    return s;
}

  在上述代码中,第一个参数users是哈希表,第二个参数是user_id的地址一定要传递地址)。最后s是输出变量。当可以在哈希表中找到相应键值时,s返回给定键的结构,当找不到时s返回NULL。

替换

  HASH_REPLACE宏等效于HASH_ADD宏,HASH_REPLACE会尝试查找和删除项目外。如果找到并删除了一个项目,它还将返回该项目的指针作为输出参数。

void replace_user(HashHead *head,HashNode *newNode) {
    HashNode *oldNode = find_user(*head,newNode->id);
    if (oldNode)
        HASH_REPLACE_INT(*head,newNode,oldNode);
}

删除

  要从哈希表中删除结构,必须具有指向它的指针。(如果只有键,请先执行HASH_FIND以获取结构指针)。

void delete_user(struct my_struct *user) {
    HASH_DEL(users,user);  /* user: pointer to deletee */
    free(user);             /* optional; it's up to you! */
}

  同样,这里users是哈希表,user是指向我们要从哈希中删除的结构的指针。

  删除结构只是将其从哈希表中删除,并非free 。何时释放结构的选择完全取决于自己;uthash永远不会释放您的结构

循环删除

  HASH_ITER是一个宏定义,程序执行时被替换为一个循环。

void delete_all() {
  struct my_struct *current_user,*tmp;

  HASH_ITER(hh,users,current_user,tmp) {
    HASH_DEL(users,current_user);  /* delete; users advances to next */
    free(current_user);            /* optional- if you want to free  */
  }
}

删除哈希表所有元素

  如果您只想删除所有项目,但不释放它们或进行每个元素的清理,则可以通过一次操作更有效地做到这一点:

HASH_CLEAR(hh,users);

  之后,列表头(此处为users)将设置为NULL。

计算哈希表元素个数

unsigned int num_users;
num_users = HASH_COUNT(users);
printf("there are %u users\n",num_users);

  当users为NULL时,HASH_COUNT会返回0.

遍历哈希表中的所有项目

void print_users() {
    struct my_struct *s;

    for(s=users; s != NULL; s=s->hh.next) {
        printf("user id %d: name %s\n",s->id,s->name);
    }
}

  还有一个hh.prev指针,可用于从任何已知项开始向后迭代哈希。
  由于hh.prev和hh.next字段的缘故,可以在哈希中向前和向后迭代。可以通过重复跟随这些指针来访问哈希中的所有项目,因此哈希也是双链表

排序哈希表

HASH_SORT( users,name_sort );

  第二个参数是指向比较函数的指针。它必须接受两个指针参数(要比较的项目),并且如果第一个项目分别在第二个项目之前,等于或之后排序,则必须返回小于零,零或大于零的int。 (这与标准C库中的strcmp或qsort使用的约定相同)。

int sort_function(void *a,void *b) {
  /* compare a to b (cast a and b appropriately)
   * return (int) -1 if (a < b)
   * return (int)  0 if (a == b)
   * return (int)  1 if (a > b)
   */
}

  name_sort和id_sort的两个排序函数示例。

int name_sort(struct my_struct *a,struct my_struct *b) {
    return strcmp(a->name,b->name);
}

int id_sort(struct my_struct *a,struct my_struct *b) {
    return (a->id - b->id);
}

void sort_by_name() {
    HASH_SORT(users,name_sort);
}

void sort_by_id() {
    HASH_SORT(users,id_sort);
}

完整代码

#include <stdio.h>   /* gets */
#include <stdlib.h>  /* atoi,malloc */
#include <string.h>  /* strcpy */
#include "uthash.h"

struct my_struct {
    int id;                    /* key */
    char name[10];
    UT_hash_handle hh;         /* makes this structure hashable */
};

struct my_struct *users = NULL;

void add_user(int user_id,char *name) {
    struct my_struct *s;

    HASH_FIND_INT(users,s);  /* id already in the hash? */
    if (s==NULL) {
      s = (struct my_struct *)malloc(sizeof *s);
      s->id = user_id;
      HASH_ADD_INT( users,name);
}

struct my_struct *find_user(int user_id) {
    struct my_struct *s;

    HASH_FIND_INT( users,s );  /* s: output pointer */
    return s;
}

void delete_user(struct my_struct *user) {
    HASH_DEL(users,user);  /* user: pointer to deletee */
    free(user);
}

void delete_all() {
  struct my_struct *current_user,current_user);  /* delete it (users advances to next) */
    free(current_user);             /* free it */
  }
}

void print_users() {
    struct my_struct *s;

    for(s=users; s != NULL; s=(struct my_struct*)(s->hh.next)) {
        printf("user id %d: name %s\n",s->name);
    }
}

int name_sort(struct my_struct *a,id_sort);
}

int main(int argc,char *argv[]) {
    char in[10];
    int id=1,running=1;
    struct my_struct *s;
    unsigned num_users;

    while (running) {
        printf(" 1. add user\n");
        printf(" 2. add/rename user by id\n");
        printf(" 3. find user\n");
        printf(" 4. delete user\n");
        printf(" 5. delete all users\n");
        printf(" 6. sort items by name\n");
        printf(" 7. sort items by id\n");
        printf(" 8. print users\n");
        printf(" 9. count users\n");
        printf("10. quit\n");
        gets(in);
        switch(atoi(in)) {
            case 1:
                printf("name?\n");
                add_user(id++,gets(in));
                break;
            case 2:
                printf("id?\n");
                gets(in); id = atoi(in);
                printf("name?\n");
                add_user(id,gets(in));
                break;
            case 3:
                printf("id?\n");
                s = find_user(atoi(gets(in)));
                printf("user: %s\n",s ? s->name : "unknown");
                break;
            case 4:
                printf("id?\n");
                s = find_user(atoi(gets(in)));
                if (s) delete_user(s);
                else printf("id unknown\n");
                break;
            case 5:
                delete_all();
                break;
            case 6:
                sort_by_name();
                break;
            case 7:
                sort_by_id();
                break;
            case 8:
                print_users();
                break;
            case 9:
                num_users=HASH_COUNT(users);
                printf("there are %u users\n",num_users);
                break;
            case 10:
                running=0;
                break;
        }
    }

    delete_all();  /* free any structures */
    return 0;
}

键值的各种类型举例

整型键值

  当键值为整型时,可以使用HASH_ADD_INT和HASH_FIND_INT。(对于所有类型的键,其他操作(例如HASH_DELETE和)HASH_SORT都是相同的)。

字符串键值

  当键值为字符串时,具体要使用那个函数取决于结构体中的键值为字符串数组还是字符串指针。 这一点很重要。当结构体中的键值为字符串数组时,使用HASH_ADD_STR。键值为字符串指针时使用HASH_ADD_KEYPTR。接下来给出两个例子参考。

  当结构体中的键值为字符串数组时

#include <string.h>  /* strcpy */
#include <stdlib.h>  /* malloc */
#include <stdio.h>   /* printf */
#include "uthash.h"

struct my_struct {
    char name[10];             /* key (string is WITHIN the structure) */
    int id;
    UT_hash_handle hh;         /* makes this structure hashable */
};


int main(int argc,char *argv[]) {
    const char *names[] = { "joe","bob","betty",NULL };
    struct my_struct *s,*tmp,*users = NULL;

    for (int i = 0; names[i]; ++i) {
        s = (struct my_struct *)malloc(sizeof *s);
        strcpy(s->name,names[i]);
        s->id = i;
        HASH_ADD_STR( users,name,s );
    }

    HASH_FIND_STR( users,s);
    if (s) printf("betty's id is %d\n",s->id);

    /* free the hash table contents */
    HASH_ITER(hh,s,tmp) {
      HASH_DEL(users,s);
      free(s);
    }
    return 0;
}

  当结构体中的键值为字符串指针时

#include <string.h>  /* strcpy */
#include <stdlib.h>  /* malloc */
#include <stdio.h>   /* printf */
#include "uthash.h"

struct my_struct {
    const char *name;          /* key */
    int id;
    UT_hash_handle hh;         /* makes this structure hashable */
};


int main(int argc,*users = NULL;

    for (int i = 0; names[i]; ++i) {
        s = (struct my_struct *)malloc(sizeof *s);
        s->name = names[i];
        s->id = i;
        HASH_ADD_KEYPTR( hh,s->name,strlen(s->name),s);
      free(s);
    }
    return 0;
}

指针键值

#include <stdio.h>
#include <stdlib.h>
#include "uthash.h"

typedef struct {
  void *key;
  int i;
  UT_hash_handle hh;
} el_t;

el_t *hash = NULL;
char *someaddr = NULL;

int main() {
  el_t *d;
  el_t *e = (el_t *)malloc(sizeof *e);
  if (!e) return -1;
  e->key = (void*)someaddr;
  e->i = 1;
  HASH_ADD_PTR(hash,key,e);
  HASH_FIND_PTR(hash,&someaddr,d);
  if (d) printf("found\n");

  /* release memory */
  HASH_DEL(hash,e);
  free(e);
  return 0;
}

结构体键值

  在将项目添加到哈希或查找项目之前,必须将结构体键值中的元素清零。

#include <stdlib.h>
#include <stdio.h>
#include "uthash.h"

typedef struct {
  char a;
  int b;
} record_key_t;

typedef struct {
    record_key_t key;
    /* ... other data ... */
    UT_hash_handle hh;
} record_t;

int main(int argc,char *argv[]) {
    record_t l,*p,*r,*records = NULL;

    r = (record_t *)malloc(sizeof *r);
    /*结构体键值清零*/
    memset(r,sizeof *r);
    r->key.a = 'a';
    r->key.b = 1;
    HASH_ADD(hh,records,sizeof(record_key_t),r);

    memset(&l,sizeof(record_t));
    l.key.a = 'a';
    l.key.b = 1;
    HASH_FIND(hh,&l.key,p);

    if (p) printf("found %c %d\n",p->key.a,p->key.b);

    HASH_ITER(hh,p,tmp) {
      HASH_DEL(records,p);
      free(p);
    }
    return 0;
}

常用宏参考

类型宏

HASH_ADD_INT(head,keyfield_name,item_ptr)

HASH_REPLACE_INT(head,keyfiled_name,item_ptr,replaced_item_ptr)

HASH_FIND_INT(head,key_ptr,item_ptr)

HASH_ADD_STR(head,item_ptr)

HASH_REPLACE_STR(head,replaced_item_ptr)

HASH_FIND_STR(head,item_ptr)

HASH_ADD_PTR(head,item_ptr)

HASH_REPLACE_PTR(head,replaced_item_ptr)

HASH_FIND_PTR(head,item_ptr)

HASH_DEL(head,item_ptr)

HASH_SORT(head,cmp)

HASH_COUNT(head)

通用宏

HASH_ADD(hh_name,head,key_len,item_ptr)

HASH_ADD_BYHASHVALUE(hh_name,hashv,item_ptr)

HASH_ADD_KEYPTR(hh_name,item_ptr)

HASH_ADD_KEYPTR_BYHASHVALUE(hh_name,item_ptr)

HASH_ADD_INORDER(hh_name,cmp)

HASH_ADD_BYHASHVALUE_INORDER(hh_name,cmp)

HASH_ADD_KEYPTR_INORDER(hh_name,cmp)

HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh_name,cmp)

HASH_REPLACE(hh_name,replaced_item_ptr)

HASH_REPLACE_BYHASHVALUE(hh_name,replaced_item_ptr)

HASH_REPLACE_INORDER(hh_name,replaced_item_ptr,cmp)

HASH_REPLACE_BYHASHVALUE_INORDER(hh_name,cmp)

HASH_FIND(hh_name,item_ptr)

HASH_FIND_BYHASHVALUE(hh_name,item_ptr)

HASH_DELETE(hh_name,item_ptr)

HASH_VALUE(key_ptr,hashv)

HASH_SRT(hh_name,cmp)

HASH_CNT(hh_name,head)

HASH_CLEAR(hh_name,head)

HASH_SELECT(dst_hh_name,dst_head,src_hh_name,src_head,condition)

HASH_ITER(hh_name,tmp_item_ptr)

HASH_OVERHEAD(hh_name,head)

参数说明

  参数说明
  hh_name
  UT_hash_handle结构中字段的 名称。俗称 hh。

  head
  结构指针变量,用作哈希的“头”。如此命名是因为它最初指向添加到哈希中的第一项。

  keyfield_name
  结构中键字段的名称。(对于多字段键,这是键的第一个字段)。如果您不熟悉宏,则将字段名称作为参数传递似乎很奇怪。请参阅 注释。

  key_len
  键字段的长度(以字节为单位)。例如,对于整数键,它是 sizeof(int),而对于字符串键,它是strlen(key)。(有关多字段键,请参阅此注释。)

  key_ptr
  对于HASH_FIND,这是指向要在哈希中查找的键的指针(由于它是指针,因此您不能在此处直接传递文字值)。对于 HASH_ADD_KEYPTR,这是要添加的项的键的地址。

  hashv
  提供的键的哈希值。这是..._BYHASHVALUE宏的输入参数,是 的输出参数HASH_VALUE。如果您要重复查找相同的键,则重用缓存的哈希值可以优化性能。

  item_ptr
  指向要添加,删除,替换或查找的结构的指针,或迭代期间的当前指针。这是一个输入参数HASH_ADD, HASH_DELETE和HASH_REPLACE宏,和用于输出参数HASH_FIND 和HASH_ITER。(当HASH_ITER用于迭代时,tmp_item_ptr 是与item_ptr内部使用的类型相同的另一个变量)。

  replace_item_ptr
  用于HASH_REPLACE宏。这是一个输出参数,设置为指向替换的项目(如果没有替换的项目,则设置为NULL)。

  cmp
  指向比较函数的指针,该函数接受两个参数(指向要比较的项目的指针),并返回一个int值,该值指定第一个项目应在第二个项目之前,等于还是之后排序(如strcmp)。

  condition
  接受单个参数的函数或宏(指向结构的空指针,需要将其强制转换为适当的结构类型)。如果应“选择”结构以将其添加到目标哈希中,则函数或宏的值应为非零值。

有任何问题,均可通过公告中的二维码联系我

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


【啊哈!算法】算法3:最常用的排序——快速排序       上一节的冒泡排序可以说是我们学习第一个真正的排序算法,并且解决了桶排序浪费空间的问题,但在算法的执行效率上却牺牲了很多,它的时间复杂度达到了O(N2)。假如我们的计算机每秒钟可以运行10亿次,那么对1亿个数进行排序,桶排序则只需要0.1秒,而冒泡排序则需要1千万秒,达到115天之久,是不是很吓人。那有没有既不浪费空间又可以快一点的排序算法
匿名组 这里可能用到几个不同的分组构造。通过括号内围绕的正则表达式就可以组成第一个构造。正如稍后要介绍的一样,既然也可以命名组,大家就有考虑把这个构造作为匿名组。作为一个实例,请看看下列字符串: “08/14/57 46 02/25/59 45 06/05/85 18 03/12/88 16 09/09/90 13“ 这个字符串就是由生日和年龄组成的。如果需要匹配年两而不要生日,就可以把正则
选择排序:从数组的起始位置处开始,把第一个元素与数组中其他元素进行比较。然后,将最小的元素方式在第0个位置上,接着再从第1个位置开始再次进行排序操作。这种操作一直到除最后一个元素外的每一个元素都作为新循环的起始点操作过后才终止。 public void SelectionSort() { int min, temp;
public struct Pqitem { public int priority; public string name; } class CQueue { private ArrayList pqueue; public CQueue() { pqueue
在编写正则表达式的时候,经常会向要向正则表达式添加数量型数据,诸如”精确匹配两次”或者”匹配一次或多次”。利用数量词就可以把这些数据添加到正则表达式里面了。 数量词(+):这个数量词说明正则表达式应该匹配一个或多个紧紧接其前的字符。 string[] words = new string[] { "bad", "boy", "baad", "baaad" ,"bear", "b
来自:http://blog.csdn.net/morewindows/article/details/6678165/归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列
插入排序算法有两层循环。外层循环会啄个遍历数组元素,而内存循环则会把外层循环所选择的元素与该元素在数组内的下一个元素进行比较。如果外层循环选择的元素小于内存循环选择的元素,那么瘦元素都想右移动以便为内存循环元素留出位置。 public void InsertionSort() { int inner, temp;
public int binSearch(int value) { int upperBround, lowerBound, mid; upperBround = arr.Length - 1; lowerBound = 0; while (lowerBound <= upper
虽然从表内第一个节点到最后一个节点的遍历操作是非常简单的,但是反向遍历链表却不是一件容易的事情。如果为Node类添加一个字段来存储指向前一个节点的连接,那么久会使得这个反向操作过程变得容易许多。当向链表插入节点的时候,为了吧数据复制给新的字段会需要执行更多的操作,但是当腰吧节点从表移除的时候就能看到他的改进效果了。 首先需要修改Node类来为累增加一个额外的链接。为了区别两个连接,这个把指
八、树(Tree)树,顾名思义,长得像一棵树,不过通常我们画成一棵倒过来的树,根在上,叶在下。不说那么多了,图一看就懂:当然了,引入了树之后,就不得不引入树的一些概念,这些概念我照样尽量用图,谁会记那么多文字?树这种结构还可以表示成下面这种方式,可见树用来描述包含关系是很不错的,但这种包含关系不得出现交叉重叠区域,否则就不能用树描述了,看图:面试的时候我们经常被考到的是一种叫“二叉树”的结构,二叉
Queue的实现: 就像Stack类的实现所做的一样,Queue类的实现用ArrayList简直是毋庸置疑的。对于这些数据结构类型而言,由于他们都是动态内置的结构,所以ArrayList是极好的实现选择。当需要往队列中插入数据项时,ArrayList会在表中把每一个保留的数据项向前移动一个元素。 class CQueue { private ArrayLis
来自:http://yingyingol.iteye.com/blog/13348911 快速排序介绍:快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地
Stack的实现必须采用一种基本结构来保存数据。因为再新数据项进栈的时候不需要担心调整表的大小,所以选择用arrayList.using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Collecti
数组类测试环境与排序算法using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace Data_structure_and_algorithm{ class CArray { pr
一、构造二叉树 二叉树查找树由节点组成,所以需要有个Node类,这个类类似于链表实现中用到的Node类。首先一起来看看Node类的代码。 public class Node { public int Data; public Node Left; public Node Right; public v
二叉树是一种特殊的树。二叉树的特点是每个结点最多有两个儿子,左边的叫做左儿子,右边的叫做右儿子,或者说每个结点最多有两棵子树。更加严格的递归定义是:二叉树要么为空,要么由根结点、左子树和右子树组成,而左子树和右子树分别是一棵二叉树。 下面这棵树就是一棵二叉树。         二叉树的使用范围最广,一棵多叉树也可以转化为二叉树,因此我们将着重讲解二叉树。二叉树中还有连两种特殊的二叉树叫做满二叉树和
上一节中我们学习了队列,它是一种先进先出的数据结构。还有一种是后进先出的数据结构它叫做栈。栈限定只能在一端进行插入和删除操作。比如说有一个小桶,小桶的直径只能放一个小球,我们现在向小桶内依次放入2号、1号、3号小球。假如你现在需要拿出2号小球,那就必须先将3号小球拿出,再拿出1号小球,最后才能将2号小球拿出来。在刚才取小球的过程中,我们最先放进去的小球最后才能拿出来,而最后放进去的小球却可以最先拿
msdn中的描述如下:(?= 子表达式)(零宽度正预测先行断言。) 仅当子表达式在此位置的右侧匹配时才继续匹配。例如,w+(?=d) 与后跟数字的单词匹配,而不与该数字匹配。此构造不会回溯。(?(零宽度正回顾后发断言。) 仅当子表达式在此位置的左侧匹配时才继续匹配。例如,(?此构造不会回溯。msdn描述的比较清楚,如:w+(?=ing) 可以匹配以ing结尾的单词(匹配结果不包括ing),(
1.引入线索二叉树 二叉树的遍历实质上是对一个非线性结构实现线性化的过程,使每一个节点(除第一个和最后一个外)在这些线性序列中有且仅有一个直接前驱和直接后继。但在二叉链表存储结构中,只能找到一个节点的左、右孩子信息,而不能直接得到节点在任一遍历序列中的前驱和后继信息。这些信息只有在遍历的动态过程中才能得到,因此,引入线索二叉树来保存这些从动态过程中得到的信息。 2.建立线索二叉树 为了保
排序与我们日常生活中息息相关,比如,我们要从电话簿中找到某个联系人首先会按照姓氏排序、买火车票会按照出发时间或者时长排序、买东西会按照销量或者好评度排序、查找文件会按照修改时间排序等等。在计算机程序设计中,排序和查找也是最基本的算法,很多其他的算法都是以排序算法为基础,在一般的数据处理或分析中,通常第一步就是进行排序,比如说二分查找,首先要对数据进行排序。在Donald Knuth 的计算机程序设