精华内容
下载资源
问答
  • 福 建 工 程 学 院 课 程 设 计 课程 题目 专业 班级 座号 姓名 算法与数据结构 哈希表 网络工程 xxxxxx 班 xxxxxxxxxxxx xxxxxxx 2011 年 12 月 31 日 实验题目哈希表 一 要解决的问题 针对同班同学信息设计一个...
  • 实验报告 课程名称 数据结构 实验项目 哈希查找 实验地点 专业班级 学号 170731063 学生姓名 指导教师 黄永来 年 月 日 七实验评价 七实验评价 学生实验须知 一实验前的准备 1实验前必须充分预习明确实验目的和要求...
  • 哈希表大总结

    2020-11-26 18:32:09
    哈希表总结 哈希表 记录的存储位置和它的关键字之间建立一确定的对应关系f,使每个关键字和结构中一个唯一的存储位置相对应。 因而查找时,只需根据这个对应关系f找到给定值K的像f(K)。 若结构中存在关键字和K相等...

    哈希表总结

    哈希表

    记录的存储位置和它的关键字之间建立一确定的对应关系f,使每个关键字和结构中一个唯一的存储位置相对应。

    因而查找时,只需根据这个对应关系f找到给定值K的像f(K)。

    若结构中存在关键字和K相等的记录,则必定在f(K)的存储位置上,由此不需要进行比较便可直接取得所查记录。

    在此,称这个对应关系f为哈希函数,按这个思想建立的表为哈希表(又称为杂凑法或散列表。

    哈希表不可避免冲突(collision)现象:

    • 对不同的关键字可能得到同一哈希地址 即key1≠key2,而hash(key1)=hash(key2)。
    • 具有相同函数值的关键字对该哈希函数来说称为同义词(synonym)。
    • 因此,在建造哈希表时不仅要设定一个好的哈希函数,而且要设定一种处理冲突的方法。
    • 可如下描述哈希表:根据设定的哈希函数H(key)和所选中的处理冲突的方法,
    • 将一组关键字映象到一个有限的、地址连续的地址集(区间)上并以关键字在地址集中的“象”作为相应记录在表中的存储位置,这种表被称为哈希表。

    在这里插入图片描述

    散列表查找步骤

    散列表(也叫哈希表,Hash table) ,是根据关键码的值进行访问的数据结构.散列表的实现常常叫做散列(hasing),散列是一种用于以常数平均时间执行插入,删除和查找的技术

    整个散列过程分为两步

    1. 通过散列函数计算记录的散列地址,并按照散列地址储存该记录

      无论什么记录我们都需要用同一个散列函数计算地址,然后再存储。

    2. 查找通过同样的散列函数计算记录的散列地址,按此散列地址访问该记录.因为我们存和取的时候用的都是一个散列函数,因此结果肯定相同。

    散列函数是什么呢?

    • 假设某个函数为 f,使得 存储位置 = f (key) 那样我们就能通过查找关键字不需要比较就可获得需要的记录的存储位置。这种存储技术被称为散列技术。散列技术是在通过记录的存储位置和它的关键字之间建立一个确定的对应关系 f ,使得每个关键字 key 都对应一个存储位置 f(key)

    这里的f就是我们所描述的哈希函数(散列函数),我们利用散列技术将记录储存在一块连续的储存空间中,这块连续的储存空间就是----(哈希)散列

    对不同的关键字可能得到同一散列地址,即k1≠k2,而f(k1)=f(k2),这种现象称为冲(英语:Collision)。具有相同函数值的关键字对该散列函数来说称做同义词。对于这种情况我们能找到有效的方法解决

    哈希函数的特殊性:

    创建哈希函数是必须遵循一下原则:

    1. 必须一致性

      无论什么记录我们都需要用同一个散列函数计算地址

    2. 计算简单

      在保证不哈希冲突的情况下,使得计算简单,如果这个算法计算复杂,会耗费很多时间.散列函数的计算时间不应该超过其他查找技术与关键字的比较时间,不然的话我们干嘛使用哈希技术了

    3. 散列地址分布均匀

      让散列地址尽量均匀分布在储存空间中,这样既保证了空间的有效性,有减少了处理冲突而消耗的时间


    散列函数构造方法

    直接定址法:

    1. 取关键字或关键字的某个线性函数值为散列地址。
    2. 即H(key)=key或H(key) = a·key + b,其中a和b为常数(这种散列函数叫做自身函数)。
    3. 若其中H(key)中已经有值了,就往下一个找,直到H(key)中没有值了,就放进去。

    在这里插入图片描述

    优点:

    • 简单,均匀,无冲突

    应用场景

    • 需要事先知道关键字的分布情况,适合查找表较小且连续的情况

    数字分析法

    1. 该方法也是十分简单的方法,就是分析我们的关键字,取其中一段,或对其位移,叠加,用作地址
    2. 比如一组员工的出生年月日,这时我们发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。
    3. 因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。

    优点:

    • 简单、均匀、适用于关键字位数较大的情况

    应用场景:

    • 关键字位数较大,知道关键字分布情况且关键字的若干位较均匀

    折叠法

    1. 将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。
    2. 比如我们的关键字是123456789,则我们分为三部分 123 ,456 ,789 然后将其相加得 1368 然后我们再取其后三位 368 作为我们的散列地址。

    优点

    • 事先不需要知道关键字情况

    应用场景

    • 适合关键字位数较多的情况

    除法散列法

    1. 在用来设计散列函数的除法散列法中,通过取key除p的余数,将关键字映射到某一个上,对于散列表长度为 m 的散列函数公式为
    2. f(k) = k mod p (p <= m)

    如果散列表长度为 12,即 m = 12 ,我们的参数 p 也设为12,那 k = 100时 f(k) = 100 % 12 = 4

    我们只需要做一次除法操作,所以除法散列法非常快

    由上面的公式可以看出,该方法的重点在于 p 的取值,如果 p 值选的不好,就可能会容易产生同义词(哈希冲突)。见下面这种情况。我们哈希表长度为6,我们选择6为p值,则有可能产生这种情况,所有关键字都得到了0这个地址数。

    在这里插入图片描述

    那我们在选用除法散列法时选取 p 值时应该遵循怎样的规则呢?

    1. m 不应为 2 的幂,因为如果 m = 2^p ,则 f(k) 就是 k 的 p 个最低位数字。例 12 % 8 = 4 ,12的二进制表示位1100,后三位为100。
    2. 若散列表的长度为m,通常p为小于或者等于表长(接近)的最小质数或者包含不小于20质因子的合数
    合数:合数是指在大于1的整数中除了能被1和本身整除外,还能被其他数(0除外)整除的数。
    
    质因子:质因子(或质因数)在数论里是指能整除给定正整数的质数。
    

    在这里插入图片描述

    注:这里的2,3,5为质因子

    根据规则选择 5 为 p 值,我们再来看。这时我们发现只有 6 和 36 冲突,相对来说就好了很多。

    优点:

    • 计算效率高,灵活

    应用场景:

    • 不知道关键字分布情况

    乘法散列法

    构造散列函数的乘法散列法主要包含两个步骤

    • 用关键字 k 乘上常数 A(0 < A < 1),并提取 k A 的小数部分
    • 用 m 乘以这个值,再向下取整

    散列函数为:

    f (k) = ⌊ m(kA mod 1) ⌋

    这里的 kA mod 1 的含义是取 keyA 的小数部分,即 kA - ⌊kA⌋

    优点:对 m 的选择不是特别关键一般选择它为 2 的某个幂次(m = 2 ^ p ,p为某个整数)

    应用场景:不知道关键字情况


    平方取中法

    • 这个方法就比较简单了,假设关键字是 321,那么他的平方就是 103041,再抽取中间的 3 位就是 030 或 304 用作散列地址。
    • 再比如关键字是 1234 那么它的平方就是 1522756 ,抽取中间 3 位就是 227 用作散列地址.

    优点

    灵活,适用范围广泛

    适用场景

    不知道关键字分布,而位数又不是很大的情况


    随机数法

    选择一随机函数,取关键字的随机值作为散列地址,即H(key)=random(key)其中random为随机函数,通常用于关键字长度不等的场合。

    处理散列冲突的方法

    hash 函数之后发现关键字 key1 不等于 key2 ,但是 f(key1) = f(key2),即有冲突,

    开放地址法

    一旦发生冲突,就会寻找下一个空的散列地址,只要列表足够大,空的散列地址总能找到,并将记录存入,为了使用开放寻址法插入一个元素,需要连续检查散列表,称为探查,我们常用的有线性探测,二次探测,随机探测

    线性探测法

    在这里插入图片描述

    我们来看一个例子,我们的关键字集合为{12,67,56,16,25,37,22,29,15,47,48,21},表长为12,我们再用散列函数 f(key) = key mod 12。

    我们求出每个 key 的 f(key)见下表

    在这里插入图片描述

    我们查看上表发现,前五位的 f(key) 都不相同,即没有冲突,可以直接存入,但是到了第六位 f(37) = f(25) = 1,那我们就需要利用上面的公式 f(37) = f (f(37) + 1 ) mod 12 = 2,这其实就是我们的订包间的做法。下面我们看一下将上面的所有数存入哈希表是什么情况吧。

    注:蓝色为计算哈希值,红色为存入哈希表

    在这里插入图片描述

    他第一次会落在下标为 10 的位置,那么如果继续使用线性探测的话,则需要通过不断取余后得到结果,数据量小还好,要是很大的话那也太慢了吧,但是明明他的前面就有一个空房间呀,如果向前移动只需移动一次即可。在这里插入图片描述

    二次探测法

    其实理解了我们的上个例子之后,这个一下就能整明白了,根本不用费脑子,这个方法就是更改了一下di的取值

    在这里插入图片描述

    注:这里的是 -1^2 为负值 而不是 (-1)^2

    所以对于我们的34来说,当di = -1时,就可以找到空位置了。在这里插入图片描述

    二次探测法的目的就是为了不让关键字聚集在某一块区域。另外还有一种有趣的方法,位移量采用随机函数计算得到,接着往下看吧.

    随机探测法

    大家看到这是不又有新问题了,刚才我们在散列函数构造规则的第一条中说

    (1)必须是一致的

    我们 di 是随机生成的呀,这里的随机其实是伪随机数,伪随机数含义为,我们设置随机种子相同,则不断调用随机函数可以生成不会重复的数列,我们在查找时,用同样的随机种子它每次得到的数列是相同的,那么相同的 di 就能得到相同的散列地址

    在这里插入图片描述
    在这里插入图片描述

    随机种子(Random Seed)是计算机专业术语,一种以随机数作为对象的以真随机数(种子)为初始条件的随机数。一般计算机的随机数都是伪随机数,以一个真随机数(种子)作为初始条件,然后用一定的算法不停迭代产生随机数
    

    再哈希法

    这个方法其实也特别简单,利用不同的哈希函数再求得一个哈希地址,直到不出现冲突为止。

    f,(key) = RH,( key ) (i = 1,2,3,4……k)

    这里的RH就是不同的散列函数,可以把我们之前说的那些散列函数都用上,每当发生哈希冲突时就换一个散列函数,相信总有一个能够解决冲突的。这样的代价就是增加了计算时间

    链地址法

    1. key 不同 f(key) 相同的情况,我们将这些同义词储存在一个单链表,这种表叫做同义词子表.散列表中只储存同义词表的头指针…
    2. 关键字集合为{12,67,56,16,25,37,22,29,15,47,48,21},表长为12,
    3. 我们再用散列函数 f(key) = key mod 12
    4. 用了链地址法之后就在也不冲突,无论有多少冲突,我们只需在同义词子表中添加结点即可。下面我们看下链地址法的存储情况。

    在这里插入图片描述

    链地址法虽然不能够产生冲突,但是查找时需要遍历单链表的性能.

    公共溢出区法

    将有冲突的,放入到别的地方(溢出表),这样你就有地方住了。我们为所有冲突的关键字建立了一个公共的溢出区来存放。

    在这里插入图片描述

    怎样查找: 通过散列函数计算出散列地址后,先于基本表对比,如果不相等就到溢出表中顺序查找.,对于冲突很少的情况性能还是非常高的

    散列表查找算法(线性探测法)

    首先需要定义散列列表的结构以及一些相关常数,

    • elem代表散列表数据存储数组
    • count代表的是当前插入元素个数
    • size代表哈希表容量
    • NULLKEY散列表初始值
    • 然后我们如果查找成功就返回索引,如果不存在该元素就返回元素不存在。
    • 我们将哈希表初始化,为数组元素赋初值。

    在这里插入图片描述

    插入操作的具体步骤

    1. 通过哈希函数(除法散列法),将key转化为数组下标
    2. 如果该下标中没有元素,则插入,否则说明冲突,则利用线性探测法处理冲突

    在这里插入图片描述

    查找

    1. 通过哈希函数(同输入时一样),将key转化成为数组下标
    2. 通过数组下标找到key值,如果key一致,则查找成功,否则利用线性探测法继续查找

    在这里插入图片描述
    blog.csdnimg.cn/img_convert/1684f4dd64d5c14d42e6cc624f68c6d2.png)

    完整代码

    在这里插入图片描述

    散列表性能分析

    如果没有冲突的话,散列表查找是效率最高的,时间复杂度为O(1),

    散列查找的平均查找长度取决于哪些方面呢

    1. 散列函数是否均匀

    2. 处理冲突的方法

      比如我们线性探测有时会堆积,则不如二次探测法好,因为链地址法处理冲突时不会产生任何堆积,因而具有最佳的平均查找性能

    3. 散列表的填装因子

      装填因子 α = 填入表中的记录数 / 散列表长度

    实际应用

    什么是文件的hash值呢?

    MD5-Hash-文件的数字文摘通过Hash函数计算得到。不管文件长度如何,它的Hash函数计算结果是一个固定长度的数字。与加密算法不同,这一个Hash算法是一个不可逆的单向函数。采用安全性高的Hash算法,如MD5、SHA时,两个不同的文件几乎不可能得到相同的Hash结果。因此,一旦文件被修改,就可检测出来。

    总结:

    • 一般的线性表、树中,记录在结构中的相对位置是随机的即和记录的关键字之间不存在确定的关系,在结构中查找记录时需进行一系列和关键字的比较。这一类查找方法建立在“比较”的基础上,查找的效率与比较次数密切相关。理想的情况是能直接找到需要的记录,因此必须在记录的存储位置和它的关键字之间建立一确定的对应关系f,使每个关键字和结构中一个唯一的存储位置相对应。因而查找时,只需根据这个对应关系f找到给定值K的像f(K)。若结构中存在关键字和K相等的记录,则必定在f(K)的存储位置上,由此不需要进行比较便可直接取得所查记录。在此,称这个对应关系f为哈希函数,按这个思想建立的表为哈希表(又称为杂凑法或散列表)。
    • 哈希表不可避免冲突(collision)现象:对不同的关键字可能得到同一哈希地址 即key1≠key2,而hash(key1)=hash(key2)。具有相同函数值的关键字对该哈希函数来说称为同义词(synonym)。因此,在建造哈希表时不仅要设定一个好的哈希函数,而且要设定一种处理冲突的方法。可如下描述哈希表:根据设定的哈希函数H(key)和所选中的处理冲突的方法,将一组关键字映象到一个有限的、地址连续的地址集(区间)上并以关键字在地址集中的“象”作为相应记录在表中的存储位置,这种表被称为哈希表。
    • 对于动态查找表而言,1) 表长不确定;2)在设计查找表时,只知道关键字所属范围,而不知道确切的关键字。因此,一般情况需建立一个函数关系,以f(key)作为关键字为key的录在表中的位置,通常称这个函数f(key)为哈希函数。(注意:这个函数并不一定是数学函数)
    • 哈希函数是一个映象,即:将关键字的集合映射到某个地址集合上,它的设置很灵活,只要这个地址集合的大小不超出允许范围即可。
    • 现实中哈希函数是需要构造的,并且构造的好才能使用的好。
    • 用途:加密,解决冲突问题。

    转载百度百科

    袁厨的算法小屋

    展开全文
  • 数据结构课程设计 哈希表实验报告 福建工程学院 课程设计 课程 算法与数据结构 题目 哈希表 专业 网络工程 班级 xxxxxx班 座号 xxxxxxxxxxxx 姓名: xxxxxxx 12月 31日 实验题目哈希表 一 要解决的问题 针对同班同学...
  • 教育资料 HUNAN 课程实习报告 题 目 哈希表 学生姓名 唐鹏 学生学号 201208080216 专业班级 物联2班 指导老师 吴帆 完 成 日 期 2014年4月2日 需 求 分 析 本程序来自于图书馆靠书名来检索想要查找的书问题 本程序...
  • 数据结构课程设计 之姓名哈希表的建 立及查找 课程设计任务书 学生姓名 刘颖 专业班级 计科1003班 指导教师 谭新明 工作单位 计算机科学系 题目哈希表的设计与实现 初始条件 针对某个集体比如你所在的班级中的人名...
  • Python 哈希查找及哈希表的实现

    千次阅读 2019-05-30 23:15:41
    使用哈希表这种数据结构,先遍历一个数列建立一个哈希表,时间复杂度O(n); 然后查询第二列中的每个元素是否在表中,时间复杂度为O(n),所以总时间为O(2n)。效率提高很多。 一个数列中是否存在重复元素 思路与1)类似...

    墙裂建议阅读 :Problem-Solving-with-Algorithms-and-Data-Structures-Using-Python 5.5. Hashing

    为什么提出哈希查找

    python中list和array是常见的线性结构,创建数组的时候,内存开辟一块连续的,大小确定的空间用于存放数据。
    再说说链表,由于链表包含了一个数据域和指针域,链表在内存中不需要连续,无论下一个节点在哪里,上一个节点总是携带下一个节点的位置。
    回归正题,我们创建一个连续的内存且大小固定,往列表里面添加一个数使用append和下标索引的时间复杂度O(1)。然而使用查找的方式查询列表里是否含有某个值时,我们需要从头到尾查一遍,时间复杂度时O(n)。 当n的数量级很大时,这个查询时间时不可接受的。

    能不能把查询的时间复杂度也做到O(1)?
    答案是可以,使用哈希表。

    一个简单的想法就是,当有一个值,能不能直接根据这个值计算它应该存放的位置,然后再存放?
    如果可以,那么我就能直接通过下表索引,实现O(1)的时间复杂度。数学上有一个概念叫做映射,我们能不能建立一种映射关系,将值和将要存放的位置关联起来。
    假如我们使用余数法作为映射关系,即y = x % len_list, len_list为需要存放的数据数量。有10个值,当一个值x = 2, 那么存放的位置y = 2%10 = 2.
    这样我想要看看列表里面有没有2,只要通过上面的映射关系得到他的下标,取出来对比一下只有O(1)的时间复杂度。
    其实这个所谓的映射关系专业的说法就是哈希函数,只不过这个函数不会像我举例说的那么简单。

    hash函数

    哈希函数在设计的时候需要考虑空间的问题。
    另外在使用哈希映射时还会发生两个或者多个值映射同一个位置的冲突情况,这也是需要考虑的问题。
    最后还要考虑哈希函数计算复杂度不能太高。
    设计一个完美的哈希函数是不太可能的,但是原则是最大限度地减少冲突数,易于计算,并均匀分布在哈希表中的项。
    常用的简单哈希函数:
    (1)分组求和法
    如电话号码 436-555-4601 ,每两位分一组得到43,65,55,46,01,求和 43 + 65 + 55 + 46 + 01 ,我们得到 210。假设哈希表有 11 个槽,除以 11 下, 210%11 为 1,因此 436-555-4601 散列到槽 1。
    (2)平方取中法
    如 44,先计算 44^2 = 1,936 ,取结果中间两个数字 93 ,假设哈希表有 11 个槽。得到 93%11=5 。

    哈希查找

    哈希表的一个重要特性就是查询的时间复杂度为O(1). 那么在需要查询的场合就排上用场了。
    查重/去重

    1. 给定两个数列,判断两个数列中元素的差异。
      假设两个数列的长度为n,使用for-loop逐个比较,时间复杂度是O(n^2).
      使用哈希表这种数据结构,先遍历一个数列建立一个哈希表,时间复杂度O(n); 然后查询第二列中的每个元素是否在表中,时间复杂度为O(n),所以总时间为O(2n)。效率提高很多。
    2. 一个数列中是否存在重复元素
      思路与1)类似。

    Python 哈希表的实现

    代码摘自 http://interactivepython.org/courselib/static/pythonds/SortSearch/Hashing.html

    class HashTable:
        def __init__(self):
            self.size = 11
            self.slots = [None] * self.size
            self.data = [None] * self.size
    
        def put(self, key, value):
            hashvalue = self.hashfunction(key, len(self.slots))
            if self.slots[hashvalue] is None:
                self.slots[hashvalue] = key
                self.data[hashvalue] = value
            else:
                if self.slots[hashvalue] == key:
                    self.data[hashvalue] = value
    
                else:
                    nextslot = self.rehash(hashvalue, len(self.slots))
                    while self.slots[nextslot] is not None and self.slots[nextslot] != key:
                        nextslot = self.rehash(nextslot, len(self.slots))
    
                    if self.slots[nextslot] is None:
                        self.slots[nextslot] = key
                        self.data[nextslot] = value
                    else:
                        self.data[nextslot] = value
    
        def rehash(self, oldhash, size):
            return (oldhash + 1) % size
    
        def hashfunction(self, key, size):
            return key % size
    
        def get(self, key):
            startslot = self.hashfunction(key, len(self.slots))
            data = None
            found = False
            stop = False
            pos = startslot
            while pos is not None and not found and not stop:
                if self.slots[pos] == key:
                    found = True
                    data = self.data[pos]
                else:
                    pos = self.rehash(pos, len(self.slots))
                    # 回到了原点,表示找遍了没有找到
                    if pos == startslot:
                        stop = True
            return data
    
        # 重载载 __getitem__ 和 __setitem__ 方法以允许使用 [] 访问
        def __getitem__(self, key):
            return self.get(key)
    
        def __setitem__(self, key, value):
            return self.put(key, value)
    
    
    if __name__ == '__main__':
        H = HashTable()
        H[54] = "cat"
        H[26] = "dog"
        H[93] = "lion"
        H[17] = "tiger"
        H[77] = "bird"
        H[31] = "cow"
        H[44] = "goat"
        H[55] = "pig"
        H[20] = "chicken"
    
        print(H.slots)  # [77, 44, 55, 20, 26, 93, 17, None, None, 31, 54]
        print(H.data)  # ['bird', 'goat', 'pig', 'chicken', 'dog', 'lion', 'tiger', None, None, 'cow', 'cat']
        print(H[20])  # 'chicken'
        H[20] = 'duck'
        print(H[20])  # duck
        print(H[99])  # None
    
    展开全文
  • 基于c语言的哈希表电话簿

    多人点赞 2020-12-23 19:56:14
    三、详细设计 3.1建立哈希表(实例)的存储结构 将每个人的信息作为一条记录,包括电话号码、用户名、地址,还有一个整型变量用来记录冲突的次数,便于计算ASL,然后哈希表由记录数组、表中现存量、表容量组成,具体...

    一、需求分析 1
    二、概要设计 2
    三、详细设计 3-8
    四、调试分析 9-11
    五、运行结果 12-21
    六、参考文献 21
    七、附录 21-47

    一、需求分析
    1.设计目的
    电话簿是人们用来记录亲人朋友电话的工具,发展至现在已经不只是传统的纸制本子,电脑,手机等电子产品也都具备了记录的功能。电话簿作为手机的基本功能之一,每天都被我们频繁地使用着,根据手机功能使用调查显示,有八成以上的消费者使用手机电话簿功能。
    本次设计的电话簿是一款绝对专业的个人通讯信息管理软件,本次设计的电话簿就是为了方便消费者以不同的方式查找和储存电话。电话簿中储存方式大致可分为两类,一类是按照姓名来储存入电话簿,另一种是按照电话号码来储存入电话簿。为了更贴近用户需求,本次设计的电话簿包含了以上两种储存和查找方式。

    2.程序所能达到的功能
    (1)用户可以通过键盘输入来选择以号码或者名字为关键字建表。
    (2)从键盘输入要插入的的记录,分别以线性探测法和二次探测法来解决冲突。
    (3)用户通过输入电话号码查找,可以给出电话号码的记录。
    (4)用户通过输入用户名查找,可以给出电话号码的记录。
    (5)在哈希函数确定的前提下,分别以线性探测法和二次探测法来解决冲突,并计算平均查找长度。
    (6)展示表中所有的电话记录。

    二、概要设计
    本系统旨在通过建立哈希表实现电话簿录入、查询电话记录、展示所有电话记录等功能,帮助用户方便的处理电话记录。
    简单思路:
    在这里插入图片描述

    建立表:
    对于通过电话号码和地址为关键字建立哈希表,使用除留余数法。
    解决冲突:
    分别使用二次探测法和线性探测法来解决冲突。

    三、详细设计
    3.1建立哈希表(实例)的存储结构
    将每个人的信息作为一条记录,包括电话号码、用户名、地址,还有一个整型变量用来记录冲突的次数,便于计算ASL,然后哈希表由记录数组、表中现存量、表容量组成,具体数据类型见下:
    address[20] 地址
    name[30]; 名字
    num[30] 电话号码
    int c; 查找次数
    结构体 {

    char name[30];名字

    char address[20];地址
    char num[30];电话号码
    int c;查找次数

    }record;

    结构体{
    record data[Size];哈希表
    int count;现在有的数据个数
    int size;哈希表长度

    } Hashtable;

    在这里插入图片描述

    3.2建立哈希表(实例)的操作
    为了实现上述程序的功能,需要定义下列抽象数据类型:
    ADT hashtable {
    数据对象:哈希表中存储的个条电话记录;
    数据关系:表中相邻元素之间有前去和后继的关系;
    基本操作:
    init(Hashtable &h)
    操作结果:初始化了哈希表
    int exchange(char str[])
    操作结果:请关键字从char型转为int;
    int HashSearch1(Hashtable &h,char *str,int &p)
    操作结果:在表中以电话号码线性探测数据,返回数据插入位置;
    int HashSearch1_n(Hashtable &h,char *str,int &p)
    操作结果:在表中以姓名线性探测数据,返回数据的插入位置;
    int HashSearch2(Hashtable &h,char *str,int &p)
    操作结果:在表中以电话号码二次探测数据,返回数据的插入位置;
    int HashSearch2(Hashtable &h,char *str,int &p)
    操作结果:在表中以姓名二次探测数据,返回数据的插入位置;
    void disp(Hashtable h)
    操作结果:显示出哈希表中储存的电话记录;
    }ADT hashtable

    3.3程序具体实行流程
    (1)主菜单和次菜单的进入和退出。
    利用while(1){}和swith语句来实现主菜单和次菜单的转换

    while(1)语句可以让程序一直运行,每结束一次操作后重新再来;
    swith(主菜单)语句在主菜单选择建立哈希表的关键字;
    swith(次菜单)语句在次菜单选择电话簿功能;
    cc=menu_1();重新选择swith(主菜单);

    在这里插入图片描述

    (2)建立哈希函数——采取除留余数法
    第一步:把字符串先转化为一个整数。
    int exchange(char str[]) 使用关键字的哈希函数,各项转化为整数型相加;

    第二步:与哈希表的最大容量使用除留余数法得到哈希函数。
    j=k%Size;

    在这里插入图片描述

    (3)解决冲突——线性探测法和二次探测法

    线性探测法:
    在这里插入图片描述

    二次探测法:

    在这里插入图片描述

    (4)计算ASL

    在这里插入图片描述

    int c;查找次数
    int count;现在有的数据个数
    结构体中定义了c来计录每一个记录的比较次数,方便最后求ASL。
    ASL=

    (5)展示电话簿
    循环使用for循环查找整个哈希表
    在这里插入图片描述

    四、调试分析
    》》》》》》

    主程序

    void main()
    {
     Hashtable h1,h2,h3,h4;
     int i,j,cc,n,a,m,flag=1,k=5;
     char num[30];
     char name[30];
     char address[20];
     init(h1);
     init(h2);
     init(h3);
     init(h4);
    	cc=menu_1();
    	while(1){
    	switch(cc){
    	case 1:
    		system("cls");
    		 j=menu_2();
    		 switch(j){
    		 case 0:
    			 printf("(@ @) 谢谢使用,再见! (^ ^)\n");
    			 exit(0);
    			 getch();
    		 break;
    		 //******
    		 case 1:
    		//重新选择关键字
    		   cc=menu_1();
    			  break;
    		 //******
    		 case 2:
    			 printf("添加电话记录到电话簿1:\n");
    			 printf("请输入您要添加电话记录的数目:\n");
    			 scanf("%d",&n);
    			 for(i=0;i<n;i++){
    				 printf("请输入第%d条电话记录的姓名、地址和电话号码(用空格隔开)\n",i+1);
    				 scanf("%s%s%s",name,address,num);
    				 if(HashSearch1(h1,num,a))
    				{
    					printf("已存在");
    					i--;
    				}
    				 else
    				 {
    					 strcpy(h1.data[a].num,num);
    					 strcpy(h1.data[a].address,address);
    					 strcpy(h1.data[a].name,name);
    					 h1.count++;
    					 printf("插入成功");
    				 }
    			 }
    			
    			 printf("按任意键继续!\n");
    			 getch();
    			 break;
    		 case 3:
    			 printf("添加电话记录到电话簿2:\n");
    			 printf("请输入您要添加电话记录的数目\n");
    			 scanf("%d",&n);
    			 for(i=0;i<n;i++){
    				printf("请输入第%d条电话记录的姓名、地址和电话号码(用空格隔开)\n",i+1); 
    				scanf("%s%s%s",name,address,num);
    				if(HashSearch2(h2,num,a))
    				{
    					printf("已存在");
    					i--;
    				}
    				else
    				{
    					strcpy(h2.data[a].num,num);
    					strcpy(h2.data[a].address,address);
    					strcpy(h2.data[a].name,name);
    					h2.count++;
    					printf("插入成功");
    				}
    			 }
    			 
    			 printf("按任意键继续!\n");
    			 getch();
    			 break;
    		 case 4:
    			 printf("请输入您要在电话簿1中查找的人的电话号码:\n");
    			 scanf("%s",num);
    			 printf("\n");
    			 if(m=HashSearch1(h1,num,a))
    			 {
    				 printf("查找的结果为:\n");
    				 printf("您要查找的人的姓名、地址和电话号码分别为:\n%s,%s,%s\n",h1.data[m].name,h1.data[m].address,h1.data[m].num);
    			 }
    			 else
    				 printf("对不起,没有您要找的人!\n");
    			 printf("按任意键继续!\n");
    			 printf("按任意键继续!\n");
    			 getch();
    			 break;
    		 case 5:
    			 printf("请输入您要在电话簿2中查找的人的电话号码:\n");
    			 scanf("%s",num);
    			 printf("\n");
    			 if(m=HashSearch2(h2,num,a))
    			 {
    				 printf("查找的结果为:\n");
    				 printf("您要查找的人的姓名、地址和电话号码分别为:\n%s,%s,%s\n",h2.data[m].name,h2.data[m].address,h2.data[m].num);
    			 }
    			 else
    				 printf("对不起,没有您要找的人!\n");
    			 printf("按任意键继续!\n");
    			 printf("按任意键继续!\n");
    			 getch();
    			 break;
    		 case 6:
    			 printf("电话簿1:\n");
    			 disp(h1);
    		     printf("在电话簿1中查找的ASL为:\n");
    			 printf("%d / %d\n",compasl(h1),h1.count);
    			 printf("按任意键继续!\n");
    			 getch();
    			 break;
    		 case 7:
    			 printf("电话簿2:\n");
    			 disp(h2);
    			 printf("在电话簿2中查找的ASL为:\n");
    			 printf("%d / %d\n",compasl(h2),h2.count);
    			 printf("按任意键继续!\n");
    			 getch();
    			 break; 
    			 default: 	printf("您的选择有误,请重新选择!\n");
    			 break;
    	}
    	
     	break;
    	 case 2:
    		 system("cls");
    		  j=menu_2();
    		 switch(j){
    		 case 0:
    			 printf("(@ @) 谢谢使用,再见! (^ ^)\n");
    			 exit(0);
    			 getch();
    		 break;
    		 case 1:
    		 //重新选择关键字
    		   cc=menu_1();
    			  break;
    		 //******
    		 case 2:
    			 printf("添加电话记录到电话簿1:\n");
    			 printf("请输入您要添加电话记录的数目:\n");
    			 scanf("%d",&n);
    			 for(i=0;i<n;i++){
    				 printf("请输入第%d条电话记录的姓名、地址和电话号码(用空格隔开)\n",i+1);
    				 scanf("%s%s%s",name,address,num);
    				 if(HashSearch1_n(h3,name,a))
    				{
    					printf("已存在");
    					i--;
    				}
    				 else
    				 {
    					 strcpy(h3.data[a].num,num);
    					 strcpy(h3.data[a].address,address);
    					 strcpy(h3.data[a].name,name);
    					 h3.count++;
    					 printf("插入成功");
    				 }
    			 }
    			 
    			 printf("按任意键继续!\n");
    			 getch();
    			 break;
    		 case 3:
    			 printf("添加电话记录到电话簿2:\n");
    			 printf("请输入您要添加电话记录的数目\n");
    			 scanf("%d",&n);
    			 for(i=0;i<n;i++){
    				printf("请输入第%d条电话记录的姓名、地址和电话号码(用空格隔开)\n",i+1); 
    				scanf("%s%s%s",name,address,num);
    				if(HashSearch2_n(h4,name,a))
    				{
    					printf("已存在");
    					i--;
    				}
    				else
    				{
    					strcpy(h4.data[a].num,num);
    					strcpy(h4.data[a].address,address);
    					strcpy(h4.data[a].name,name);
    					h4.count++;
    					printf("插入成功");
    				}
    			 }
    			 
    			 printf("按任意键继续!\n");
    			 getch();
    			 break;
    		 case 4:
    			 printf("请输入您要在电话簿1中查找的人的xm:\n");
    			 scanf("%s",name);
    			 printf("\n");
    			 if(m=HashSearch1_n(h3,name,a))
    			 {
    				 printf("查找的结果为:\n");
    				 printf("您要查找的人的姓名、地址和电话号码分别为:\n%s,%s,%s\n",h3.data[m].name,h3.data[m].address,h3.data[m].num);
    			 }
    			 else
    				 printf("对不起,没有您要找的人!\n");
    			 printf("按任意键继续!\n");
    			 printf("按任意键继续!\n");
    			 getch();
    			 break;
    		 case 5:
    			 printf("请输入您要在电话簿2中查找的人的姓名:\n");
    			 scanf("%s",name);
    			 printf("\n");
    			 if(m=HashSearch2_n(h4,name,a))
    			 {
    				 printf("查找的结果为:\n");
    				 printf("您要查找的人的姓名、地址和电话号码分别为:\n%s,%s,%s\n",h4.data[m].name,h4.data[m].address,h4.data[m].num);
    			 }
    			 else
    				 printf("对不起,没有您要找的人!\n");
    			 printf("按任意键继续!\n");
    			 printf("按任意键继续!\n");
    			 getch();
    			 break;
    		 case 6:
    			 printf("电话簿1:\n");
    			 disp(h3);
    			 printf("在电话簿1中查找的ASL为:\n");
    			 printf("%d / %d\n",compasl(h3),h3.count);
    			 printf("按任意键继续!\n");
    			 getch();
    			 break;
    		 case 7:
    			 printf("电话簿2:\n");
    			 disp(h4);
    			 printf("在电话簿2中查找的ASL为:\n");
    			 printf("%d / %d\n",compasl(h4),h4.count);
    			 printf("按任意键继续!\n");
    			 getch();
    			 break;	
    			 default: 	printf("您的选择有误,请重新选择!\n");
    			 break;
    	}
    	break;
    	default: 	printf("您的选择有误,请重新选择!\n");
    	}
    	}
    }
    

    源码地址

    展开全文
  • 本篇文章为笔者的LeetCode刷题笔记。...算法:利用哈希表,将较短的数组1放入哈希表。再遍历较长的数组2,依次与哈希表中的存货比较。 /** * Note: The returned array must be malloced, assume caller c.

    本篇文章为笔者的LeetCode刷题笔记。文章整体分为两部分:1.笔者自己思考的算法及代码。2.LeetCode官方给出的最优算法及代码。通过对比这两部分算法和代码的异同,笔者的算法思想和编程水平有了显著地提升。如果这篇文章能帮到你那真是再好不过了! 

    一、读者思考的算法

    A.算法:利用哈希表,将较短的数组1放入哈希表。再遍历较长的数组2,依次与哈希表中的存货比较。

    
    
    /**
     * Note: The returned array must be malloced, assume caller calls free().
     */         //注意此处返回数组必须是动态分配的才行
     
    int* intersect(int* nums1, int nums1Size, int* nums2, int nums2Size, int* returnSize){
    
        typedef struct {
         int key;                 //C语言对哈希表的配置 定义一个包含键值和出现次数value的结构体
         int value;
         UT_hash_handle hh;
     } Hash;
     Hash *hash = NULL;        //声明一个指向该结构体的指针hash
    
     Hash * find(int key){        //在哈希表中查找是否存在给定key值,若存在返回指向其地址的指针
         Hash *tep = NULL;        //否则返回空指针
         HASH_FIND_INT(hash,&key,tep);
         return tep;
     }
     void insert(int key){        //将较小数组key值和其出现次数插入哈希表
         Hash * tep = find(key);
         if(tep==NULL){            //若找不到,则插入
             Hash *s = (Hash*)malloc(sizeof(Hash)); //动态分配一个结构体
             s->key=key;                             //给其赋值
             s->value=1;
             HASH_ADD_INT(hash,key,s);  //调用库函数,将s所指的Hash结构体插入到hash老巢中key映射的地址上
         }else{
             tep->value++; //若已存在,则出现次数++
         }
     }
        if(nums1Size<nums2Size){  
            int *ret = (int*)malloc(sizeof(int)*(nums1Size));
            *returnSize=0;
            for(int i=0;i<nums1Size;i++){        //将较小数组插入哈希表
                insert(nums1[i]);
            }
            for(int j=0;j<nums2Size;j++){        //依次查找较大数组各元素是否在哈希表中
                Hash *it = find(nums2[j]);
                if(it==NULL){}     //若不存在则为空,且此处必须加这一行,否则下一行it为空会报错
                else if(it->key==nums2[j]&&it->value!=0){ //当存在且哈希表中此key出现次数>0
                    it->value--;        //出现次数--
                    ret[(*returnSize)++]=nums2[j];    //将交集依次放入新建数组ret
                }        //注意此处*returnSize初始化为0,这样写可以省一个int index变量
            }
            return ret;    //返回该数组
        }else{
            int *ret = (int*)malloc(sizeof(int)*(nums2Size));
            *returnSize=0;
            for(int i=0;i<nums2Size;i++){
                insert(nums2[i]);
            }
            for(int j=0;j<nums1Size;j++){
                Hash *it = find(nums1[j]);
                if(it==NULL){}
                else if(it->key==nums1[j]&&it->value!=0){
                    it->value--;
                    ret[(*returnSize)++]=nums1[j];
                }
            }
            return ret;
        }
        
    
    }

    B.算法复杂度分析

    时间复杂度:O(m+n) m和n依次为两个数组的长度。需要对两个数组依次遍历,再对哈希表进行操作。操作哈希表的时间复杂度为O(1)。

    空间复杂度: O( min(m,n) ) m和n依次为两个数组的长度。哈希表中需要存放较小数组的元素,所以哈希表的大小不大于较小数组大小。返回数组也不大于较小数组的长度。

    二.官方算法

    官方算法与我的算法几乎一致

    第二种算法是先排序再用双指针,很容易实现。

    A.算法:

    如果两个数组是有序的,则可以使用双指针的方法得到两个数组的交集。首先对两个数组进行排序,然后使用两个指针遍历两个数组。初始时,两个指针分别指向两个数组的头部。每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数字的指针右移一位,如果两个数字相等,将该数字添加到答案,并将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束。

    int cmp(const void* _a, const void* _b) {
        int *a = _a, *b = (int*)_b;
        return *a == *b ? 0 : *a > *b ? 1 : -1;
    }
    
    int* intersect(int* nums1, int nums1Size, int* nums2, int nums2Size,
                   int* returnSize) {
        qsort(nums1, nums1Size, sizeof(int), cmp);
        qsort(nums2, nums2Size, sizeof(int), cmp);
        *returnSize = 0;
        int* intersection = (int*)malloc(sizeof(int) * fmin(nums1Size, nums2Size));
        int index1 = 0, index2 = 0;
        while (index1 < nums1Size && index2 < nums2Size) {
            if (nums1[index1] < nums2[index2]) {
                index1++;
            } else if (nums1[index1] > nums2[index2]) {
                index2++;
            } else {
                intersection[(*returnSize)++] = nums1[index1];
                index1++;
                index2++;
            }
        }
        return intersection;
    }
    

    C.算法复杂度分析

    时间复杂度:O(mlogm+nlogn),其中 m 和 n 分别是两个数组的长度。对两个数组进行排序的时间复杂度是O(mlogm+nlogn),遍历两个数组的时间复杂度是 O(m+n),因此总时间复杂度是O(mlogm+nlogn)。
    空间复杂度:O(min(m,n)),其中m 和 n分别是两个数组的长度。为返回值创建一个数组intersection,其长度为较短的数组的长度。

    三.笔者小结

    1. 通过这道题学会了哈希表的使用方法。哈希表查找操作的时间复杂度为O(1)。因为哈希表有专门的映射函数,可以只通过一步计算就得出key所对应的地址,而不用像数组或链表一样依次遍历查找。例如Hash(key)=key,Hash(1)=1,指的是key为1的地址就为1。

    2. 用C实现哈希表的都是勇士,我是学完考研408四门专业课后直接开始刷LeetCode的,所以只会C语言。现在在慢慢自学Java了,用Java或者C++直接可以用几行代码就搞定C几十行。。。

    3. 关于C语言哈希表的使用:https://blog.csdn.net/nameofcsdn/article/details/107297361

    Keep calm and carry on!

    谢谢你看到这里!

    展开全文
  • 数据结构——哈希表的详解与实现

    千次阅读 2020-08-07 21:30:41
    数据结构——哈希表(HashTable) 1.前言 ​ 当我们频繁的查找数据中的某个元素时,我们通常会选择数组来存放数据,因为数组的的内存是连续的,可以直接通过下标访问数据,但是它添加和删除数据比较麻烦;虽然链表...
  • 大二专业课程数据结构课程实验设计,使用C++语言开发,查询为线性探测,实现了增删查改功能,学弟学妹想偷懒可以直接拿来用,附带实验报告
  • 哈希表

    千次阅读 2014-10-04 22:51:53
    哈希表,就是这么一个表:将数据存放在按照散列函数计算结果位置上,或者发现该位置已被占用,即有冲突的情况下,按照一定规则,一直找到一个新位置存放为止。那么反过来,利用这个哈希表,给出指定值,按照散列函数...
  • 目 录 TOC \o "1-2" \h \z \u 1 序言 1 2 需求分析 1 2.1 任务和要求 1 2.2 运行环境 1 2.3 开发工具 2 3 分析和设计 2 3.1 系统分析及设计思绪 2 3.2 关键数据结构及算法 2 3.3 函数步骤图 3 4 具体代码实现 6 5 ...
  • 作者 |袁厨来源 | 袁厨的算法小屋(ID:tan45du_me)今天我们来说一种新的数据结构散列(哈希。散列是应用非常广泛的数据结构,在我们的刷题过程中,散列表的出场率特别高。所...
  • GPU无锁跳步哈希表.pdf

    2021-09-25 17:55:07
    GPU无锁跳步哈希表.pdf
  • Python2.7中dict实现原理和哈希表介绍

    千次阅读 2018-05-27 13:21:08
    引言 Python中dict对象是表明了其是一个原始的Python数据类型,按照键值对的方式存储,其中文名字翻译为字典,顾名思义其通过键名...在Python2中,dict的底层是依靠哈希表(Hash Table)进行实现的,使用开放地址法解...
  • 算法哈希表PPT学习教案.pptx
  • 实验四 哈希表设计 学院   专业(班级)   姓名   学号   教师   实验四 哈希表设计   ...
  • 数据结构---哈希表

    2020-10-18 16:26:44
    前面讲到,哈希表就是利用数组和链表进行存储数据,那什么样的数组+链表可以被称作哈希表呢? 哈希表 = hash函数 + 数组(+链表) so,什么是hash函数(也可以成为hash算法)? 如果想要专业的解释,请查看百度百科...
  • 书上把 hash_tables 翻译为哈希表,个人感觉还是翻译为哈希表更好,故而把哈希表替换为哈希表 一、哈希表 假设你在一家杂货店上班。有顾客来买东西时,你得在一个本子中查找价格。 如果本子的内容不是按...
  • C语言哈希表用法

    千次阅读 多人点赞 2020-07-12 11:56:03
    为了认证C语言专业级上机编程,我特地学习了C语言的哈希表哈希表在头文件"uthash.h"中已经有了,只需要简单学习一下用法即可。 1,哈希结构体 #include "uthash.h" typedef struct { int key; int value;...
  • 线性探测法构建哈希表

    千次阅读 2017-03-26 16:27:04
    下面看下2010年2010年全国硕士研究生入学统一考试计算机科学与技术学科联考计算机学科专业基础综合试题中一个考哈希表的题。 Question1: 将关键字序列(7、8、30、11、18、9、14)散列存储到散列表中。散列表的...
  • LeetCode 1207 哈希表

    2020-10-28 22:22:20
    (这就是百度解释) 按照我的理解就是,哈希表,就是将一个个数值(value)放在一个个格子里,并打上一个标签(key),但是这样一点也不专业,实质上的解释就是: 哈希表就是一个函数:hashtable(value,key) 存入...
  • 例子:(2010年全国硕士研究生入学统一考试计算机科学与技术学科联考计算机学科专业基础综合试题第一题) 将关键字序列(7、8、30、11、18、9、14)散列存储到散列表中。散列表的存储空间是一个下标从0开始的一维...
  • #资源达人分享计划#
  • #资源达人分享计划#
  • #资源达人分享计划#
  • 下面我们看一下将上面的所有数存入哈希表什么情况吧。 注:蓝色为计算哈希值,红色为存入哈希表 我们把这种解决冲突的开放地址法称为线性探测法。下面我们通过视频来模拟一下线性探测法的存储过程。 另外我们在...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 17,551
精华内容 7,020
关键字:

哈希表什么专业