文章目录
很久之前的一个课程作业,用C语言实现HashSet,体会HashSet的内部工作机制,同时模仿Java的机制和语法
01 - HashSet
HashSet(哈希集合)是一种容器,Java.util包中提供了HashSet类,任何语言都可以自己实现HashSet类,HashSet具有以下特点:
非线性容器,数据的存放位置不固定
数据不可重复,数据在集合中是唯一不可重复的
增删改查(特别是查找)效率非常高,HashSet是以空间换时间
HashSet的结构如下,左边的List称为桶,其实是一个数组,数组的每一项都是一个链表,数据的增删查改都围绕着一种HashCode(哈希密码算法)进行,本例程中使用了最基本的求余运算
02 - HashCode
HashCode(哈希密码)是一种映射算法,作用是把目标数据映射为容易处理的数据,可以理解为压缩,本例程是把目标数据映射为数组下标,例如有一些字符串的映射如下:
建立映射(加密)后,目标数据就可以直接存放在数组中而且使用下标去访问,下标每次都需要计算(解密),如果忽略计算过程同时不考虑碰撞域,那么无论数据容量有多大,查找的速度都是O(1),常数级别,如果考虑碰撞域,情况要复杂一点,但是与O(log2n)接近
02 - 模仿Java机制和语法
C语言并没有给出HashSet的API,但是Java有,在Java.util工具包中,本例程需要模仿Java的机制和语法,在Java中有泛型的机制,代码表现如下:
HashSet<Integer> ih = new HashSet<Integer>();
ih.add( 3 );
HashSet<String> sh = new HashSet<String>();
sh.add("abc");
集合的类型在创建的时候指定,同时HashSet添加数据的时候会自动扩容,而且因为HashSet是非线性容器,集合的访问需要用迭代器:
Iterator<Integer> ite = h.iterator();
while( ite.hasNext() )
{
if( ite.next() == date )
{/*funtion*/}
}
因此,模仿Java的核心内容是:泛型、自动扩容、迭代器
2.1 - 模仿泛型
HashSet类需要支持所有的数据类型,不只是int
、double
等基本数据类型,还需要支持自定义数据类型struct
,C语言中的void*
能够容纳所有的指针类型,也就是所有的指针类型都可以直接赋值给void*
,不过一旦赋值,再也没有途径知道原来的类型
所以用户必须提供equal()
函数实现泛型,同时hash_code()
加密函数也需要用户提供,HashSet的结构如下
/*哈希表*/
typedef struct HashSet_Table{
List **date_list; //指向链表头结点数组的指针
int size; //哈希表的大小
int Capacity; //容量
float load_factor; //加载因子
int bucket_depth; //桶的深度
int (*hash_code)(void* date , int depth); //哈希密码函数
int (*equal)(void *date_one , void *date_two); //用于判断两组数据是否相等
}HashSet;
只要提供equal()
和hash_code()
之后,就可以用C语言的一个参数宏模仿Java的语法,在参数宏中,##
用于连接两个字符串
#define HashSet(T) HashSet*
#define new_HashSet(T) CreateHashSet(hash_code_##T , equal_##T)
如以下代码:
int hash_code_string(void *date, int depth);
int equal_string(void *a , void *b)
HashSet(string) h = new_HashSet(string);
#实际上被展开HashSet* h = CreateHashSet(hash_code_string,equal_string)
2.2 - 模仿自动扩容
自动扩展发生在add()
添加数据的时候,使用到C语言的一个库函数realloc
进行,伪代码如下
void add(……)
{
if( "没有相同的元素 " )
{
if("超出范围")
{
realloc("扩容");
}
push_top("添加数据") ;
}
return;
}
2.3 - 模仿迭代器
迭代器的好处在于,遍历不需要程序员指定长度和递增方式,这些操作由迭代器自己完成,核心是一个current
指针,每次调用getNext()
的时候,就让指针自动记录下一个位置,伪代码
void* getNext(ite)
{
if("当前节点是当前链表最后一个元素 ")
{
{"寻找下一个元素"}
}
else
{
ite->current = ite->next; //若下一个节点不为空,则作为当前节点
}
return ite->current->data;
}
03 - 结果测试
测试字符串类型,首先提供equal()
和hash_code()
/*实现数据是字符型指针的函数*/
int equal_string(void *a , void *b)
{
char *ap = (char*)a;
char *bp = (char*)b;
if( strcmp(ap,bp) == 0 )
return True;
return False;
}
/*实现字符型指针哈希密码函数*/
int hash_code_string(void *date, int depth)
{
char *t = (char*)date;
int result = 0;
for(int i = 0;*t;i++,t++)
{
result += i*(*t);
}
return (result % depth);
}
然后main()函数中可以测试
/*create*/
HashClass *fun = Hash_Initialize();
HashSet(string) h = new_HashSet(string);
……
/*add*/
for(int i = 0;i<len;i++)
{
fun->add(h , str[i]);
……
}
/*iterator*/
Iterator(string) ite = fun->CreateIterator(h);
while(fun->hasNext(ite))
{
char *k = (char*)fun->getNext(ite);
……
}
04 - 源码下载
链接:百度网盘
提取码:vbef
05 - 总结
- C语言实现了求余加密算法的HashSet
- 暂时提供int、double和string类型的equal()和hash_code()
- 本例程把HashSet数据结构和操作分开,操作在HashClass内部,可以合并在HashSet中