Hash Table(散列表)
这篇主要是基础的数据结构学习,写的时候才明白了书上说到的一些问题,由于该篇仅仅只是对这种数据结构进行一个理解,所以很基础,关于h(x)函数也只是简单的运用了除法散列,然后为了应对冲突,我用的是链接法。
下面说说散列表和我对散列表的理解:
1.什么是散列表?
散列表就是一个关键值映射的地址组成的表,这里的地址是数组的下标...至于映射,学过数学就应该要知道这个概念并不难理解,就是自己设计一个优秀的映射(函数),一个好的映射可以很优秀的避免空间的浪费和冲突的发生,由于我比较菜233,自己设计不出并且也暂时没时间研究一个这样的函数,所以,我用的就是简单的关键值取余,写成数学函数就是 h(key) = key mod N(其中key为关键值,N为散列表的大小),显然,可以看到这个函数设计得并不好,很多关键值都可能被映射到同一个位置上去,然后就发生了冲突,并且还有一些表的空间可能一直都没有值映射到,就造成了空间的浪费。但这也是一个方案,至少让我们有了踏出第一步的勇气去探索更好的方法,或者说还能让初学者更快的、更明了的理解什么是散列表和冲突,基本上这就是什么是散列表了。
2.什么是链接法?
上面多次提到了冲突(也叫碰撞,collision),按照我们的映射的设计,很大可能会出现不同的两个关键值映射到了同一个地址上去,这样的行为就被称为冲突或者碰撞,所以,为了保证数据都可以保存在表中,而且不会被覆盖、丢失等,解决这个问题的一个方法就是链接法(也称拉链法,Chaining),即,将散列到同一个槽(地址)的关键值用链表的方法保存,这样说可能还是想像不出是什么样的情形,那我更细节一点说就是,把所有具有相同地址的数据用链表来保存,并把该链表的头节点保存到散列表中的对应的地址中,这样就可以避免冲突,而且分好了同类,只要输入的关键值被映射到这个地址,那么就遍历这个地址中的链表,添加到尾部(我认为链表可以换成栈或者队列,然后就实现了一个其他的数据结构,比如散列栈、散列队列233...不过不知道用处?),从这里我们也就可以知道,查找、删除和添加的最坏时间复杂度为O(N)了,N是散列表的大小,这种情况就是所有的数据全被散列到同一个地址上了...然后就是一个链表...
好了,说得也差不多了,我再说一下昨晚一直烦恼的几个小问题,最大的问题就是开始写的时候,我的链表是没有头结点的...然后,大家知道没有头结点进行删除是有很大问题的,会删除的是删除对象的后一个节点,其实添加一个头结点就问题都解决了,但...我又感觉添加一个头结点改着麻烦,由于我太菜也没其他的解决办法,最后受不了了,睡觉。。。今天早上还是默默的添加了头结点,问题解决。。。另一个问题就是查找.....就是:假如我插入了几个具有相同地址的数据,也就是这个地址有了链表,然后我又删除其中一个以后,再查找删除的这个数据就会导致程序崩溃...因为,查找用的也是相同的散列函数,这样的话就因为这个位置保存的是地址相同的链表,删除一个,这里的链表还是存在,但没有了这个数据,我再查找删除的这个数据时,首先被映射到了这个链表头,然后会在里面找...结果已经被删除了,是不可能找得到的,但偏偏又要返回一个值...这个问题其实要解决也不难,把返回值改一下就好了,但我不想那么做...那么可能就需要再写个判断存在的函数,也感觉麻烦,于是就一直拖着了...然后就想着算了...嘛,之后再说吧,我有心情了再解决吧,还有个问题就是我本来想写一个输出散列表中的所有数据,结果接受不了一个地址一个地址的检查是否为NIL,但也没有其他好办法,嫌麻烦233。。。于是改成了输出某个地址的所有数据。。。可能还有其他地方也有些问题...暂时没发现,主要是测试得并不多~
-------------------update 16:06:37---------------
上面说的问题以及一些之前没注意到的BUG也都在午休以后全部解决了,应该没有问题了。
下面给出我的实现方法:
/**
* Hash table(散列表)
*
* 链接法(chaining)
*/
#include <stdio.h>
#include <stdlib.h>
#include <conio.h> #define bool int
#define true 1
#define false 0 #define ListSize 10 typedef struct LinkedList {
struct LinkedList* next; //当发生冲突时申请额外节点来存放冲突值
int key; //key-value
int count; //list个数
}HashChain; //链接法 struct HashList {
HashChain* Node;
}hashlist[ListSize]; //建立hashlist-LiseSize /* 定义全部函数 */
HashChain* initHashtable();
bool ListEmpty();
bool ListFull();
int GetListCount();
static int Hashmap();
static bool HashCollision();
bool InsertList();
HashChain* FindListKey();
bool DeleteList(); /**
* 初始化hashlist的每一个位置的节点与个数
*/
HashChain* initHashtable(void)
{
HashChain* table;
int i; for(i = 0; i < ListSize; i++)
{
hashlist[i].Node = NULL;
} table = (HashChain*)malloc(sizeof(HashChain));
table->count = 0;
table->next = NULL; return table;
} /**
* 判断hash表是否为空
*/
bool ListEmpty(HashChain* table)
{
return table->count == 0;
} /**
* 判断hash表是否满
*/
bool ListFull(HashChain* table)
{
return table->count == ListSize;
} /**
* 当前散列表中的数据个数
*/
int GetListCount(HashChain* table)
{
return table->count;
} /**
* 哈希函数-映射,将value映射为hash table中的一个地址
*/
static int Hashmap(int val)
{
return val % ListSize;
} /**
* 判断映射地址是否发生了冲突
*/
static bool HashCollision(int val)
{
if(hashlist[Hashmap(val)].Node != NULL ) //检查val的映射地址的第一个节点是否存在,存在则说明冲突。时间复杂度O(1)
return false; return true;
} /**
* 插入数据
*/
bool InsertList(HashChain* table, int X)
{
HashChain* list, * temp; if(ListFull(table)) //如果list满了
{
fprintf(stderr, "HashList is full!\n");
return false; //提前退出
}
else if(HashCollision(X)) //如果没冲突
{
hashlist[Hashmap(X)].Node = (HashChain*)malloc(sizeof(HashChain)); //对该地址申请一个节点,把节点保存到list中
temp = (HashChain*)malloc(sizeof(HashChain));
temp->key = X;
temp->next = NULL;
hashlist[Hashmap(X)].Node->next = temp;
}
else if(!HashCollision(X)) //如果冲突
{
list = hashlist[Hashmap(X)].Node; //为了防止改变冲突的原地址的节点,把地址赋给一个hash结构变量,让它来完成查找插入的位置的任务
while(list->next != NULL) //链表的线性查找,使得新插入的值插入到该地址中的链表的尾部
{
list = list->next;
}
temp = (HashChain*)malloc(sizeof(HashChain));
temp->key = X;
list -> next = temp;
temp -> next = NULL;
}
++table->count; //计数 return true;
} /**
* 查找数据
*/
HashChain* FindListKey(HashChain* table, int X)
{
HashChain* list; if(ListEmpty(table)) //如果表为空,节省一点查找的时间
{
fprintf(stderr, "HashList is empty!\n");
return NULL;
}
else if(HashCollision(X)) //如果没有冲突,说明表中不存在该数据
{
fprintf(stderr, "The data does not exist!\n");
return NULL;
}
else //如果冲突,则从该地址中的链表的第一个节点开始比较查找,查找的平均时间复杂度为O(1+n),n为链表的节点个数,最坏情况为O(ListSize),最好情况为O(1)
{
list = hashlist[Hashmap(X)].Node;
while(list->next != NULL)
{
list = list->next;
}
return list;
}
} /**
* 删除数据
*/
bool DeleteList(HashChain* table, int X)
{
HashChain* list, * temp;
int flag; if(ListEmpty(table)) //如果表为空,提前退出节省一点查找的时间
{
fprintf(stderr, "HashList is empty!\n");
return false;
}
else if(HashCollision(X)) //如果没有冲突,说明表中不存在该数据
{
fprintf(stderr, "The data does not exist!\n");
return false;
}
else if(!HashCollision(X)) //如果冲突有两种情况,1.删除的数据的映射地址和该地址相同,但数据不存在,2.删除的数据存在
{
list = hashlist[Hashmap(X)].Node;
while(list->next != NULL)
{
temp = list;
list = list->next; //放在判断的前面是因为头结点没有数据
if(list->key != X)
flag = 0;
else
flag = 1;
}
if(flag) //存在则删除
{
temp = list->next;
list->next = list->next->next;
free(temp);
temp = NULL; //防止野指针
}
else
{
fprintf(stderr, "The data does not exist!\n");
return false;
}
}
--table->count; return true;
} int main(void)
{
HashChain* table, * PrintKey;
int val;
char c; table = initHashtable();
puts("1) 添加 2) 删除");
puts("3) 查找 4) 关键值个数");
puts("5) 显示指定地址中的所有关键值");
puts("6) 退出"); while((c = getch()) != '6')
{
switch(c)
{
case '1' : printf("\n添加数据:");
scanf("%d", &val);
InsertList(table, val);
break;
case '2' : printf("\n删除数据:");
scanf("%d", &val);
DeleteList(table, val);
break;
case '3' : printf("\n查找数据:");
scanf("%d", &val);
if(FindListKey(table, val)->key == val)
printf("您查找的数据为:%d\n", FindListKey(table, val)->key);
else
printf("The data does not exist!\n");
break;
case '4' : printf("\n当前关键值个数为: %d\n", GetListCount(table));
break;
case '5' : printf("输入地址:");
scanf("%d", &val); printf("显示数据:");
if(hashlist[val].Node != NULL && val < ListSize)
{
PrintKey = hashlist[val].Node;
while(PrintKey->next != NULL)
{
PrintKey = PrintKey->next;
printf("%d ", PrintKey->key);
}
printf("\n");
}
else
printf("The address does not exist!\n");
break;
}
}
free(table);
table = NULL; return 0;
}
等自己学到更多姿势了,把其他解决冲突的办法和更好的映射方法再补充上。。。
Hash Table(散列表)的更多相关文章
- Python与数据结构[4] -> 散列表[0] -> 散列表与散列函数的 Python 实现
散列表 / Hash Table 散列表与散列函数 散列表是一种将关键字映射到特定数组位置的一种数据结构,而将关键字映射到0至TableSize-1过程的函数,即为散列函数. Hash Table: ...
- HashMap、lru、散列表
HashMap HashMap的数据结构:HashMap实际上是一个数组和链表("链表散列")的数据结构.底层就是一个数组结构,数组中的每一项又是一个链表. hashCode是一个 ...
- 散列表(hash table)——算法导论(13)
1. 引言 许多应用都需要动态集合结构,它至少需要支持Insert,search和delete字典操作.散列表(hash table)是实现字典操作的一种有效的数据结构. 2. 直接寻址表 在介绍散列 ...
- [转载] 散列表(Hash Table)从理论到实用(上)
转载自:白话算法(6) 散列表(Hash Table)从理论到实用(上) 处理实际问题的一般数学方法是,首先提炼出问题的本质元素,然后把它看作一个比现实无限宽广的可能性系统,这个系统中的实质关系可以通 ...
- [转载] 散列表(Hash Table)从理论到实用(中)
转载自:白话算法(6) 散列表(Hash Table)从理论到实用(中) 不用链接法,还有别的方法能处理碰撞吗?扪心自问,我不敢问这个问题.链接法如此的自然.直接,以至于我不敢相信还有别的(甚至是更好 ...
- [转载] 散列表(Hash Table) 从理论到实用(下)
转载自: 白话算法(6) 散列表(Hash Table) 从理论到实用(下) [澈丹,我想要个钻戒.][小北,等等吧,等我再修行两年,你把我烧了,舍利子比钻戒值钱.] ——自扯自蛋 无论开发一个程序还 ...
- Java 集合 散列表hash table
Java 集合 散列表hash table @author ixenos 摘要:hash table用链表数组实现.解决散列表的冲突:开放地址法 和 链地址法(冲突链表方式) hash table 是 ...
- Hash表(hash table ,又名散列表)
直接进去主题好了. 什么是哈希表? 哈希表(Hash table,也叫散列表),是根据key而直接进行访问的数据结构.也就是说,它通过把key映射到表中一个位置来访问记录,以加快查找的速度.这个映射函 ...
- Hash表 hash table 又名散列表
直接进去主题好了. 什么是哈希表? 哈希表(Hash table,也叫散列表),是根据key而直接进行访问的数据结构.也就是说,它通过把key映射到表中一个位置来访问记录,以加快查找的速度.这个映射函 ...
随机推荐
- 题解 P2320 【[HNOI2006]鬼谷子的钱袋】
P2320 [HNOI2006]鬼谷子的钱袋 挺有趣的一道题,之所以发这篇题解是因为感觉思路的更清晰一点qwq 此题主要有两种方法: 一.分治思想 例如要凑出1~20,假如我们已经能凑出1~10了,那 ...
- C#应用程序部署到集群若干问题
1. MemoryCache中的缓存在集群中的每个节点不能同步 解决方案: A. 将缓存内容迁移到系统外部的Redis缓存 B. 在使用MemoryCache的时候设置过期时间(当对数据同步要求不是那 ...
- 7_2 最大乘积(UVa11059)<枚举连续子序列>
给一个数字集合{ S1,S2,…,Sn },请从这个数字集合里找出一段连续数字,使他们的乘积是最大的.以Case 1为例子,2 x 4 = 8为这个集合的最大乘积:而Case 2则为2 x 5 x(– ...
- opencv:轮廓逼近与拟合
轮廓逼近,本质上是减少编码点 拟合圆,生成最相似的圆或椭圆 #include <opencv2/opencv.hpp> #include <iostream> using na ...
- Bugku-CTF之这是一个神奇的登陆框
Day32 这是一个神奇的登陆框 http://123.206.87.240:9001/sql/ flag格式flag{}
- io型和有状态的应用不放入k8s,而是做服务映射
io型和有状态的应用不放入k8s,而是做服务映射 待办 在实际应用中,一般不会把mysql这种重IO.有状态的应用直接放入k8s中,而是使用专用的服务器来独立部署.而像web这种无状态应用依然会运行在 ...
- Some series and integrals involving the Riemann zeta function binomial coefficients and the harmonic numbers
链接:http://pan.baidu.com/s/1eSNkz4Y
- Spring Cloud Alibaba 实战 之 Nacos 服务注册和发现
服务注册与发现,服务发现主要用于实现各个微服务实例的自动化注册与发现,是微服务治理的核心,学习 Spring Cloud Alibaba,首先要了解框架中的服务注册和发现组件——Nacos. 一.Sp ...
- springboot 框架 - 探究-pom文件
一.pom文件 父项目 <parent> <groupId>org.springframework.boot</groupId> <artifactId> ...
- wordpress 不用插件添加友情链接
哎,也不知道为啥,网上说的那个link manager这个插件死活找不到啊, 找了一个类似的,但是不是,这么多的英文看了好几遍才发现不是 然后从大神哪里找到一个好方法 在你用的那个主题的functio ...