数组、链表和哈希表
数组、链表和哈希表关系
数组与链表的区别
(1)存储空间上
链表存放的内存空间可以是连续的,也可以是不连续的,数组则是连续的一段内存空间。一般情况下存放相同多的数据时,数组占用较小的内存,而链表还需要存放其前驱和后继的空间。
(2)长度的可变性
链表的长度是按实际需要可以伸缩的,而数组的长度是在定义时要给定的,如果存放的数据个数超过了数组的初始大小,则会出现溢出现象。
(3)查找效率:
按序号查找时,数组可以随机访问,时间复杂度为O(1),而链表不支持随机访问,平均需要O(n);按值查找时,若数组无序,数组和链表时间复杂度均为O(n),但是当数组有序时,可以采用折半查找将时间复杂度降为O(logn);
(4)插入删除时:
数组平均需要移动n/2个元素,而链表只需修改指针即可;
(5)空间分配方面:
(静态)数组从栈中分配空间, 对于程序员(某些编程语言如java对于堆内存的管理也交由程序自动控制,但性能肯定有差异)方便快速,但是自由度小。链表从堆中分配空间, 自由度大但是申请管理比较麻烦。
链表
那么有没有一种数据结构可以结合该两者的优点呢?那就是哈希表。
哈希表将需要查找的key值,通过hash函数的计算,换算为数组的位置值。这样在查找时就可以直接定位数据的位置。它结合了数组的快速查询的优点又能融合链表方便快捷的增加删除元素的优势
总结
数组------查找快,修改慢!
链表------查找慢,修改快!
哈希表:是数组和链表的结合体。
注意:
- 在程序变量中,数组和链表都只需要在栈上存储一个起始位置,占用一个变量位置即可。
- hash将需要定位的值通过hash函数换算为数据的位置值,以空间换时间。
链表开源库—utlist.h
介绍
utlist.h中包含了一组用于C结构体的通用链表宏。使用起来非常简单,只需要将utlist.h拷贝到你的项目,并包含进你的源码即可,utlist.h宏提供了基本的链表操作:添加、删除、排序、遍历。
源码获取
utlist.h的源码可以在GitHub上直接获取(src/utlist.h):
https://github.com/troydhanson/uthash
链表类型
utlist.h支持下面三种类型的链表:
- 单向链表
- 双向链表
- 环形双向链表
使用效率
- 头部添加:对所有的链表类型都是常量;
- 尾部添加:单向链表O(n);双向链表是常量;
- 删除:单向链表O(n);双向链表是常量;
- 排序:对所有链表类型都是O(n log(n));
- 有序链表遍历:对所有链表类型都是O(n);
- 无序链表遍历:对所有链表类型都是O(n);
哈希表开源C库—uthash
简介
C语言的标准库中没有哈希表的函数可以使用,但是可以通过第三方头文件uthash.h这个包来实现哈希表的操作。uthash 是C的比较优秀的开源代码,它实现了常见的hash操作函数,例如查找、插入、删除等待。该套开源代码采用宏的方式实现hash函数的相关功能,支持C语言的任意数据结构最为key值,甚至可以采用多个值作为key,无论是自定义的struct还是基本数据类型,需要注意的是不同类型的key其操作接口方式略有不通。
使用uthash代码时只需要包含头文件"uthash.h"即可。由于该代码采用宏的方式实现,所有的实现代码都在uthash.h文件中,因此只需要在自己的代码中包含该头文件即可。可以通过下面两种方式获取源代码。
- 通过官方下载链接:
官方GitHub地址
https://github.com/troydhanson/uthash
- 另外uthash的英文使用文档介绍可从下面网址获得:
uthash英文使用文档
http://troydhanson.github.io/uthash/
http://troydhanson.github.io/uthash/userguide.html
uthash能做什么
通过uthash软件,支持对hash表的item进行如下操作:
添加/替换(add/replace)
查找(find)
删除(delete)
统计(count)
迭代器(iterate)
排序(sort)
uthash包括的额外内容
uthash 附带了三个“附加功能”。这些提供列表、动态数组和字符串:
- utlist.h为 C 结构提供链表宏。
- utarray.h使用宏实现动态数组。
- utstring.h实现了一个基本的动态字符串。
uthash效率
uthash的插入、查找、删除的操作时间都是常量,当然这个常量的值受到key以及所选择的hash函数的影响。
uthash共提供了7种函数函数,一般情况下选择默认的即可。如果对效率要求特别高时,可以再根据自己的需求选择适合自己的hash函数。
简单使用
定义hash数据结构
在自己的数据结构中添加UT_hash_handle的支持:
#include "uthash.h"
struct my_struct {
int id; /* we'll use this field as the key */
char name[10];
UT_hash_handle hh; /* makes this structure hashable */
};
struct my_struct *g_users = NULL;
- id是键(key);
- name是值(value),即自己要保存的数据域,这里可以根据自己的需要让它变成结构体指针或者其他类型都可以;
- hh是内部使用的hash处理句柄,在使用过程中,只需要在结构体中定义一个UT_hash_handle类型的变量即可,不需要为该句柄变量赋值,但必须在该结构体中定义该变量;
- Uthash所实现的hash表中可以提供类似于双向链表的操作,可以通过结构体成员hh的hh.prev和hh.next获取当前节点的上一个节点或者下一个节点。
注意:在uthash中,当您的结构添加到hash表中时,并不会被移动或拷贝到其他位置。这意味着在程序运行过程中,无论是对hash表进行添加或删除操作,都可以保证您数据结构的安全性。
从hash表查找item
struct my_struct *find_user(int user_id)
{
struct my_struct *s = NULL;
HASH_FIND_INT(g_users, &user_id, s);
return s;
}
向hash表添加item
void add_user(struct my_struct *s)
{
HASH_ADD_INT(g_users, id, s );
}
从hash删除item
void delete_user(struct my_struct *user)
{
HASH_DEL(g_users, user);
}
在实际操作时,需要特别注意以下三点:
- 与任何hash表一样,每个item都必须有唯一的key,因此uthash也要求key具有唯一性;
- 插入时,先查找,当键不在当前的hash表中时再进行插入,以确保键的唯一性;
- 需调用插入接口函数时需要明确告诉接口函数,自己定义的键变量的名字是什么。
最后
更多请阅读英文使用文档:
uthash英文使用文档