C实现哈希表
摘要: 使用C语言、数组与链表的组合实现哈希表数据结构。 可以学习链表操作及C文件组织。
难度: 初级。
哈希表是数组、链表及数学算法的一个综合应用, 无论在理论上还是实践上都是非常有价值的。 废话不多说了, 贴上自己的实现吧,以供参考,有错误之处,恳请指出。 还需要做的一个工作是, 为实现中用到的链表和哈希表设计详尽苛刻的测试。
该哈希表包括四个文件: common.h , LinkList.c, Hashtable.c , main.c . 将它们放在同一个文件夹中,按照下面的方式编译运行
$ gcc -Wall *.c -o main
$ main
或者 一个简单的 makefile 文件:
OBJS = Hashtable.o main.o LinkList.o mhash: $(OBJS) cc -o mhash $(OBJS) HashSearch.o: Hashtable.c common.h cc -c Hashtable.c main.o: main.c common.h cc -c main.c LinkList.o: LinkList.c common.h cc -c LinkList.c .PHONY: clean clean: -rm $(OBJS) mhash
$ make
$ mhash
下面是各个文件的具体实现:
common.h
/* * common.h * 关于哈希查找和链表结构的声明和定义。 * * 哈希查找法: * 采用字符串散列函数的哈希函数和链地址法的冲突解决方案 * */ enum { OK = 1, ERROR = 0, TRUE = 1, FALSE = 0, FAILURE = 1, LEN_OF_TABLE = 13, RECORD_NUM = 95 }; typedef struct { /* 记录类型 */ char *key; int value; } Record; typedef struct node *LLNptr; typedef struct node { /* 链表结点类型 */ Record data; LLNptr next; } LLNode; typedef int Status; /* 单链表的函数声明,主要依照严蔚敏老师《数据结构C语言版》中的描述 */ Status InitList(LLNptr *list); /* 创建带头结点的单链表 */ int IsEmpty(LLNptr list); /* 判断单链表是否为空 */ int ListLength(LLNptr list); /* 返回链表长度,即记录的数目(不含头结点) */ Status GetRecord(LLNptr list, int i, Record *e); /* 返回链表中第 i 个位置上的结点,并保存在 e 中 */ Status ListInsert(LLNptr list, int i, Record e); /* 将记录 e 插入到链表 list 中的第 i 个位置上 */ Status ListDelete(LLNptr list, int i, Record *e); /* 将链表 list 中的第 i 个位置上的记录删除,并用 e 返回 */ Status ListTraverse(LLNptr list, void (*visit)(Record *e)); /* 使用 visit 遍历链表 */ Status ListPrint(LLNptr list); /* 打印链表内容 */ void printRecord(Record *e); /* 打印记录内容 */ int Equal(Record *e1, Record *e2); /* 判断记录的相等性 */ /* 返回链表 list 中与 e 满足 compare 关系的记录的指针; 如果不存在,返回 NULL */ Record* PLocateRecord(LLNptr list, Record e, int (*compare)(Record *e1, Record *e2)); /* 哈希查找的函数声明 */ int hash(char* key); /* 计算关键字的哈希值 */ Status InitTable(LLNptr hashtable[]); /* 初始化哈希表 hashtable */ Status HashInsert(LLNptr hashtable[], Record e); /* 将记录插入哈希表 hashtable 中 */ Record* HashSearch(LLNptr hashtable[], Record e); /* 根据记录关键字查找:若有则返回指向该记录的指针,否则返回 NULL */ Status HashTraverse(LLNptr hashtable[]); /* 遍历哈希表 */
LinkList.c
/* LinkList.c */ /* 带头结点的单链表实现,头结点存储表长信息 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include "common.h" Status InitList(LLNptr* list) { LLNptr head = (LLNptr)malloc(sizeof(LLNode)); if (!head) { fprintf(stderr, "Error: fail to allocate memory!\n"); return ERROR; } head->data.value = 0; /* 头结点的 value 域存储表长 */ head->data.key = NULL; /* 头结点的 key 域 未用 */ head->next = NULL; *list = head; return OK; } int IsEmpty(LLNptr list) { return list->data.value == 0; } int ListLength(LLNptr list) { return list->data.value; } Status GetRecord(LLNptr list, int i, Record *e) { /* 取得表list中的第i个元素, 并用e返回 */ LLNptr p = list->next; /* p 指向第一个记录结点 */ if (IsEmpty(list)) { fprintf(stderr, "Error: list empty!\n"); return ERROR; } if (i < 1 || i > ListLength(list)) { fprintf(stderr, "Error: position parameter %d out of range!\n", i); return ERROR; } while(--i) p = p->next; /* p 指向第i个元素 */ *e = p->data; return OK; } Record* PLocateRecord(LLNptr list, Record e, int (*compare)(Record *e1, Record *e2)) { /* 返回表list中与给定元素e满足compare关系的记录指针 */ LLNptr p = list->next; while (p) { if (compare(&p->data, &e)) return &p->data; p = p->next; } return NULL; } Status ListInsert(LLNptr list, int i, Record e) { /* 将记录 e 插入表list中的第 i 个位置上 */ LLNptr s, p = list; int j = 0; /* j 作计数器 */ if (i < 1 || i > ListLength(list)+1) { fprintf(stderr, "Error: position parameter %d out of range!\n", i); return ERROR; } while (p && j < i-1) { p = p->next; j++; } s = (LLNptr)malloc(sizeof(LLNode)); if (!s) { fprintf(stderr, "Error: fail to allocate memory!\n"); return ERROR; } s->data = e; s->next = p->next; p->next = s; list->data.value++; return OK; } Status ListDelete(LLNptr list, int i, Record *e) { /* 删除表list中的第i个位置上的记录,并用 e 返回 */ LLNptr p1 = list; LLNptr p2; int j = 0; /* j 作计数器 */ if (IsEmpty(list)) { printf("Error: list empty!\n"); return ERROR; } if (i < 1 || i > ListLength(list)) { printf("Error: invalid index!\n"); return ERROR; } while (p1->next && j < i-1) { /* p1 指向第 i-1 个元素 */ p1 = p1->next; j++; } p2 = p1->next; /* p2 指向第 i 个元素 */ *e = p2->data; p1->next = p2->next; free(p2); list->data.value--; return OK; } Status ListTraverse(LLNptr list, void (*visit)(Record *e)) { /* 用visit函数遍历表list中的元素有且仅有一次 */ LLNptr p = list->next; if (IsEmpty(list)) { printf("list empty!\n"); return ERROR; } while (p) { visit(&p->data); p = p->next; } return OK; } Status ListPrint(LLNptr list) { return ListTraverse(list, printRecord); } void printRecord(Record *e) { printf("(%s, %d)\n", e->key, e->value); } int Equal(Record *e1, Record *e2) { return strcmp(e1->key,e2->key)==0; }
Hashtable.c
// Hashtable.c // 哈希函数的实现 #include <stdio.h> #include "common.h" int hash(char* key) { char *p = key; int hash = 17; while (*p) { hash = hash * 37 + (*p); p++; } printf("key = %s\thash = %d , hash %% %d = %d\n" , key, hash, LEN_OF_TABLE, hash % LEN_OF_TABLE); return hash % LEN_OF_TABLE; } Status InitTable(LLNptr table[]) { int i; for (i = 0; i < LEN_OF_TABLE; i++) { if (!InitList(&table[i])) { return ERROR; } } return OK; } Status HashInsert(LLNptr table[], Record e) { int t_index = hash(e.key); if (PLocateRecord(table[t_index], e, Equal)) { /* 哈希表中已存在该记录 */ printf("Record exists, nothing to do."); return OK; } int ins_pos = ListLength(table[t_index]) + 1; if (!ListInsert(table[t_index], ins_pos, e)) { return ERROR; } return OK; } Record* HashSearch(LLNptr table[], Record e) { int t_index = hash(e.key); return PLocateRecord(table[t_index], e, Equal); } Status HashTraverse(LLNptr table[]) { int i; printf("The hash table:\n"); for (i = 0; i < LEN_OF_TABLE; i++) { printf("List[%d]:\n", i); ListPrint(table[i]); printf("\n"); } return OK; }
main.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "common.h" char* toString(int i); int main() { printf("Program start up ...\n"); /* 表长为LEN_OF_TABLE的哈希表,每个元素为一个单链表指针 */ LLNptr hashtable[LEN_OF_TABLE]; Record rds[RECORD_NUM]; Record* pe; Record* qe; int i; if (!InitTable(hashtable)) { fprintf(stderr, "Error: fail to initialize the hashtable!\n"); exit(FAILURE); } printf("initialize the hashtable successfully!\n"); HashTraverse(hashtable); for (i = 0; i < RECORD_NUM; i++) { rds[i].key = toString(i); rds[i].value = i; printf("Record: (%s %d) \n", rds[i].key, rds[i].value); printf("prepare to insert ..."); if (!HashInsert(hashtable, rds[i])) { printf("failed to insert record!"); } } HashTraverse(hashtable); pe = (Record* ) malloc(sizeof(Record)); pe->key = "11111"; qe = HashSearch(hashtable, *pe); if (qe != NULL) { printRecord(qe); } else { printf("Record Not Found.\n"); } pe->key = "11112"; qe = HashSearch(hashtable, *pe); if (qe != NULL) { printRecord(qe); } else { printf("Record Not Found.\n"); } return 0; } char* toString(int i) { char *p = (char *)malloc(5*sizeof(char) + 1); char *q; if (!p) { fprintf(stderr, "Error, fail to allocate memory."); exit(FAILURE); } q = p; while (q < p+5) { *q = i + ' '; q++; } *q = '\0'; return p; }