热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

哈希表(HashTable)的开放定址法和链地址法的实现

散列表(Hashtable,也叫哈希表),是根据关键码值(Keyvalue)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速


散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。引用(百度)


算法时间复杂度分析:采用哈希表作为数据结构的存储系统中,根据关键字的值可快速定位到相应的地址上,在采用开放定址法时仅需O(1)复杂度就可找到,在采用链地址法时,需要O(N)复杂度,主要在链表中搜索,相对于搜索树的O(lg(N))的复杂度,开放定址法显然来得快,但是哈希表的长度会变得非常长,采用链地址法时快速定位到相应的头结点中,只需在链表中循环遍历即可,编程难度比树降低了不少,还可以将链地址法中哈希表数组中的指针指向一个树,这样在搜索时,快速定位到搜索树的根节点,根据树的对数搜索复杂度,更可快速的找到元素,比如说,红黑树,B树..

关于哈希表中的元素指针只想为树的结点时,相应的结构如下:

                                                   

下面采用开放定址法和链地址法实现哈希表:


1. 开放定址法:

include
#include
#pragma warning(disable:4996)
typedef int KeyType;

int hashsize[] = { 11, 19, 29, 37 };//哈希表容量递增表,一个合适的素数序列
int m;//哈希表的表长,全局变量

struct ElemType{
KeyType key;
int order;
struct ElemType*next;//方便在采用链地址法时用的
};
typedef struct HashTable1{
ElemType *elem;//数据元素的基址
int count;
int hashindex;
}HashTable;

/*
初始化哈希表
*/
void InitHashTable(HashTable&H){
H.count = 0;
H.hashindex = 0;
m = hashsize[H.hashindex];//初始化哈希表表长为hashsize[H.hashindex]
if ((H.elem = (ElemType*)malloc(sizeof(ElemType)*m)) == NULL){
printf("初始化HashTable基址失败\n");
exit(-1);
}
for (int i = 0; i H.elem[i].key = 0;//未填充的记录
}
}

/*
Hash函数
采用取余法,用关键字的值余上表的长度,作为哈希存储的地址
*/
unsigned Hash(KeyType K){
return K%m;
}
int d(int i){//增量序列是冲突次数i的函数
return i;//线性探测再散列
//return rand() 随机探测再散列,一班用线性探测再散列就够了
}


/*
开放定址法处理冲突
*/
void collision(KeyType K, int&p, int i){
p = (Hash(K) + d(i)) % m;//最后得到的结果一定在0~m-1之间
}

/*
在哈希表中查找关键字为K的记录,若找到了则返回success,p表示数据在表中的位置
否则以p指示插入位置,并返回Unsuccess.i为冲突次数,传出参数
*/
int SearchHash(HashTable&H, KeyType K, int &p, int &i){
p = Hash(K);//根据关键字计算哈希地址
while (H.elem[p].key != 0 && K != H.elem[p].key){//在所得的地址上关键字不为空,且不等于该位置上的关键字,则产生冲突
++i;//冲突次数自增一次
if (i collision(K, p, i);//计算下一个位置/根据开放定址法
}
else break;//否则找不到位置了s
}
if (K == H.elem[p].key){//找到了位置
return 1;
}
else return 0;
}
int InsertHashTable(HashTable&H, ElemType e);
void RecreateHashTable(HashTable&H){
int i, count = H.count;
ElemType *p, *elem = (ElemType*)malloc(sizeof(ElemType)*count);
p = elem;
for (i = 0; i if (H.elem[i].key != 0){//H在该单元有数据
*p++ = H.elem[i];
}
}//将H中的数据临时存放到elem中对应的位置上去
H.count = 0;
++H.hashindex;

m = hashsize[H.hashindex];//新的存储容量
H.elem = (ElemType*)realloc(H.elem, m*sizeof(ElemType));//利用H.elem重新分配表长大小为m哈希表长空间
for (i = 0; i H.elem[i].key = 0;//赋初值
}
for (p = elem; p InsertHashTable(H, *p);//将临时的数据再次插入到新构造的HashTable中
free(elem);
}
/*
在哈希表中插入数据项为e的元素
*/
int InsertHashTable(HashTable&H, ElemType e){
int p, c = 0;//c为冲突的次数
if (SearchHash(H, e.key, p, c)){//如果找到了要插入的元素
return -1;
}
else if (c H.elem[p] = e;
H.count++;
return 1;//插入成功
}
else {
RecreateHashTable(H);//需重新建表
return 0;
}
}
/*
遍历哈希
*/
void TraverseHashTable(HashTable H){
for (int i = 0; i if (H.elem[i].key != 0)//第i个单元有数据
printf("%d ,(%d,%d)\n", i, H.elem[i].key, H.elem[i].order);
}
}

2. 接下来为链地址法处理冲突:


typedef int KeyType;

int hashsize[] = { 11, 19, 29, 37 };//哈希表容量递增表,一个合适的素数序列
int m;//哈希表的表长,全局变量
struct ElemType{
KeyType key;
int order;
struct ElemType*next;
};
typedef struct HashTable2{
ElemType **elem;//二级指针型向量,采用动态分配空间大小
int count;//当前的头指针向量的元素
int hashindex;//hashsize[H.hashindex]为当前容量
}HashTableLinkList;
//表的容量永远不会扩充,只是链地址的链表会很长,于是选择合适的表长变得 很重要了
/*
在ELemTYpe形成的链表中查找关键字等于K的元素,L指向头结点
动态查找的链表
*/
int SearchHashTableElemType(ElemType *L, KeyType K, ElemType *&v){
ElemType *s = NULL;
s = (ElemType*)malloc(sizeof(ElemType));
s->key = K;
if (!L->next){//一开始为空,插入一个元素
s->next = L->next;
L->next = s;
v = s;
return 0;
}
else{
// printf("\n进入了尾插法\n");
ElemType* p = L->next;
ElemType *q = L;
while (p&&p->key != K){
q = p;
p = p->next;
}
if (p&&p->key == K){
v = p;
return 1;//找到了数据元素
}

else{//插入一个数据,此时p为空,需要她的前驱指针q指向最后一个元素,采用尾插法插入元素
s->next = q->next;
q->next = s;
v = s;
return 0;
}
}

}
/*
链地址法解决哈希冲突
*/
void InitHashTableLinkListHash(HashTableLinkList &H){
H.count = 0;
m = hashsize[0];
H.elem = (ElemType**)malloc(m*sizeof(ElemType*));
for (int i = 0; i H.elem[i] = (ElemType*)malloc(sizeof(ElemType));
H.elem[i]->next = NULL;
}
}
/*
计算哈希地址
*/
int HashLinkList(KeyType K){
return K%m;
}
/*
在哈希表中查找元素,从哈希链地址中查找数据项值等于e的元素
,不需要冲突次数
*/
ElemType* SearchHashTableLinkList(HashTableLinkList H, KeyType K, int &p){
//ElemType *v = NULL;
p = Hash(K);//p为根据关键字计算的处的头结点所在的位置,关键
//printf("%d\n",p);
ElemType *head = H.elem[p];//记下头结点,关键字记录肯定在以head为头结点的单链表中
return head;
//SearchHashTableElemType(head, K, v);
//return v;
}

/*
遍历采用链地址法的哈希表
*/
void TraverseHashTableLinkList(HashTableLinkList H){
ElemType *p = NULL, *q = NULL;
for (int i = 0; i if ((p = H.elem[i])->next){//头结点不空
printf("\n进入了新的链表:\n");
q = p->next;//指向首节点
while (q){
printf("%d ", q->key);
q = q->next;
}
}
}
printf("\n");
}
/*
在哈希表中插入一个元素
*/
ElemType* InsertHashTableLinkList(HashTableLinkList&H, ElemType e){
int p = 0;//为插入的位置
ElemType*v = NULL;
ElemType*head = SearchHashTableLinkList(H, e.key, p);//找到数据项e应该插入的头结点所在的链表
//SearchHashTableLinkListElemType;
SearchHashTableElemType(head, e.key, v);//动态查找链表
return v;//返回这个结点,不管找没找到,找到了返回,没找到会自动插入这个元素也返回
//在以head为头结点的链表中查找e.key关键字的元素
}



推荐阅读
  • hashmap线程不安全允许有null的键和值效率高一点、方法不是Synchronize的要提供外同步有containsvalue和containsKey方法HashMap是Java1 ... [详细]
  • Map主要用于存储健值对,根据键得到值,因此不允许键重复(重复了覆盖了),但允许值重复hashMap是hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区 ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了logistic回归(线性和非线性)相关的知识,包括线性logistic回归的代码和数据集的分布情况。希望对你有一定的参考价值。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 006_Redis的List数据类型
    1.List类型是一个链表结构的集合,主要功能有push,pop,获取元素等。List类型是一个双端链表的结构,我们可以通过相关操作进行集合的头部或者尾部添加删除元素,List的设 ... [详细]
  • ALTERTABLE通过更改、添加、除去列和约束,或者通过启用或禁用约束和触发器来更改表的定义。语法ALTERTABLEtable{[ALTERCOLUMNcolu ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • Python SQLAlchemy库的使用方法详解
    本文详细介绍了Python中使用SQLAlchemy库的方法。首先对SQLAlchemy进行了简介,包括其定义、适用的数据库类型等。然后讨论了SQLAlchemy提供的两种主要使用模式,即SQL表达式语言和ORM。针对不同的需求,给出了选择哪种模式的建议。最后,介绍了连接数据库的方法,包括创建SQLAlchemy引擎和执行SQL语句的接口。 ... [详细]
author-avatar
-林之涵_396
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有