Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >查找-散列查找

查找-散列查找

作者头像
全栈程序员站长
发布于 2022-08-28 02:01:22
发布于 2022-08-28 02:01:22
1.6K00
代码可运行
举报
运行总次数:0
代码可运行

大家好,又见面了,我是你们的朋友全栈君。

1.散列的相关概念

散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。查找时,根据这个确定的对应关系找到给定值key的映射f(key),若查找集合中存在这个记录,则必定在f(key)的位置上。

这里我们把这种对应关系f称为散列函数,又称为哈希(Hash)函数。按这个思想,采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。那么关键字对应的记录存储位置,我们称为散列地址。

2.散列表查找步骤

(1)在存储时,通过散列函数计算记录的散列地址,并按此散列地址存储该记录。

(2)当查找记录时,我们通过同样的散列函数计算记录的散列地址,并按此散列地址访问该记录。

散列技术既是一种存储方法,也是一种查找方法。然而它与线性表、树、图等结构不同的是,前面几种结构,数据元素之间都存在某种逻辑关系,可以用连线图表示出来,而散列技术的记录之间不存在什么逻辑关系,它只与关键字有关联。因此,散列主要是面向查找的存储结构。 散列结束最适合的求解问题是查找与给定值相等的记录。对于查找来说,简化了比较过程,效率就会大大提高。但散列技术不具备很多常规数据结构的能力。

在理想的情况下,每一个关键字,通过散列函数计算出来的地址都是不一样的,可现实中,这只是一个理想。我们时常会碰到两个关键字key1≠key2,但是却没有f(key1)=f(key2),这种现象我们称为冲突(collision),并把key1和key2称为这个散列函数的同义词(synonym)。

3.散列函数的构造方法

(1)直接定址法

我们可以取关键字的某个线性函数值为散列地址,即 ∗∗f(key)=a∗key+b(a、b为常数) **f(key)=a*key+b(a、b为常数)**

这样的散列函数有点就是简单、均匀,也不会产生冲突,但问题是这需要事先知道关键字的分布情况,适合查找表较小且连续的情况。由于这样的限制,在现实应用中,此方法虽然简单,但却并不常用。

(2)数字分析法

如果我们的关键字是位数较多的数字,比如我们的11位手机号”130xxxx1234”,其中前三位是接入号,一般对应不同运营商公司的子品牌,如130是联通如意通、136是移动神州行、153是电信等;中间四位是HLR识别号,表示用户号的归属地;后四位才是真正的用户号。

若我们现在要存储某家公司员工登记表,如果用手机号作为关键字,那么极有可能前7位都是相同的。那么我们选择后面的四位称为散列地址就是不错的选择。如果这样的抽取工作还是容易出现冲突问题,还可以对抽取出来的数字再进行反转(如1234改成4321)、右环位移(如1234改成4123)、左环位移、甚至前两数与后两数叠加(如1234改成12+34=46)等方法。总的目的就是为了提供一个散列函数,能够合理地将关键字分配到散列表的各位置。

这里我们提到了一个关键词-抽取。抽取方法是使用关键字的一部分来计算散列存储位置的方法,这在散列函数中是常常用到的手段。

数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑这个方法。

(3)平方取中法

这个方法计算很简单,假设关键字是1234,那么它的平方就是1522756,再抽取中间的3位就是227,用做散列地址。再比如关键字是4321,那么它的平方就是18671041,抽取中间的3位就可以是671,也可以是710,用做散列地址。平方取中法比价适合于不知道关键字的分布,而位数又不是很大的情况。

(4)折叠法

折叠法是将关键字从左到右分割成位数相等的几部分(注意最后一部分位数不够时可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。

比如我们的关键字是9876543210,散列表表长为三位,我们将它分为四组,987|654|321|0,然后将它们叠加求和987+654+321+0=1962,再求后3位得到散列地址为962。

有时可能这还不能够保证均匀分布,不妨从一端向另一端来回折叠后对齐相加。比如我们将987和321反转,再与654和0相加,变成789+654+123+0=1566,此时散列地址为566。

折叠法事先不需要知道关键字的分布,适合关键字位数较多的情况。

(5)除留余数法

此方法为最常用的构造散列函数方法。对于散列表长为m的散列函数公式为:

f(key) = key mod p (p≤m)

mod是取模的意思。事实上,这方法不仅可以对关键字直接取模,也可以折叠、平方取中后再取模。很显然,本方法的关键就在于选择合适的p,p如果选得不好,就可能会容易产生同义词。如下表所示,我们对于有12个记录的关键字构造散列表时,就用了f(key)=key.mod 12的方法。比如29 mod 12 = 5,所以它存储在下标为5的位置。

不过这也是存在冲突的可能的,因为12=2*6=3*4。如果关键字中有像18(3*6)、30(5*6)、42(7*6)等数字,它们的余数都为6,这就和78对应的下标位置冲突了。

甚至极端一些,对于下表的关键字,如果我们让p为12的话,就可能出现下面的情况,所有的关键字都得到了0这个地址数,这未免也太糟糕了点。

我们不选用p=12来做除留余数法,而选用p=11,如下表所示。

此时就只有12和144有冲突,相对来说,就要好很多。

因此根据前辈们的经验,若散列表表长为m,通常p为小于或等于表长(最好接近m)的最小质数或不包含小于20质因子的合数。

(6)随机数法

选择一个随机数,取关键字的随机函数值为它的散列地址。也就是f(key)=random(key)。这里random是随机函数。当关键字的长度不等时,采用这个方法构造散列函数是比较合适的。

4.处理散列冲突的方法

(1)开放定址法

所谓的开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。

fi(key)=(f(key)+di)MODm(di是一个随机数列) f_i(key)=(f(key)+d_i) MOD m(d_i是一个随机数列)

(2)再散列函数法

对于我们的散列表来说,我们事先准备多个散列函数。

fi(key)=RHi(key)(i=1,2,...,k) f_i(key)=RH_i(key)(i=1,2,...,k)

这里 就是不同的散列函数,你可以把前面说的什么除留余数、折叠、平方取中全部用上。每当发生散列地址冲突时,就换一个散列函数计算,相信总会有一个可以吧冲突解决掉。这种方法能够使得关键字不产生聚集,当然,相应地也增加了计算的时间。

(3)链地址法

将所有关键字为同义词的记录存储在一个单链表中,我们称这种表为同义词子表,在散列表中只存储所有同义词子表的头指针。对于关键字集合{12,67,56,16,25,37,22,29,15,47,48,34},我们同样的用12为除数,进行除留余数法,可得到如下图所示结构,此时,已经不存在什么冲突换址的问题,无论有多少个冲突,都只是在当前位置给单链表增加结点的问题。

链地址法对于可能会造成很多冲突的散列函数来说,提供了绝不会出现找不到地址的保障。当然,这也就带来了查找时需要遍历单链表的性能损耗。

(4)公共溢出去法

这个方法其实就更加好理解,你不是冲突吗?好吧,凡事冲突的都跟我走,我给你们这些冲突找个地儿待着。这就如同孤儿院收留所有无家可归的孩子一样,我们为所有冲突的关键字建立了一个公共的溢出区来存放。

就前面的例子而言,我们共有三个关键字{37,48,34}与之前的关键字位置有冲突,那么将它们存储到溢出表中,如下图所示:

在查找时,对给定值通过散列函数计算出散列地址后,先与基本表的相应位置进行比对,如果相等,则查找成功;如果不相等,则到溢出表去进行顺序查找。如果相对于基本表而言,有冲突的数据很少的情况下,公共溢出区的结构对查找性能来说还是非常高的。

5.散列表查找实现

(1)散列表查找算法实现

首先是需要定义一个散列表结构以及一些相关的常数。其中HashTable就是散列表结构。结构当中的elem为一个动态数组。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
**#define SUCCESS 1**
**#define UNSUCCESS 0**
**#define HASHSIZE 12** /*定义散列表长为数组的长度*/
**#define NULLKEY -32768**
typedef struct
{
    int *elem;  /*数据元素存储基址,动态分配数组*/
    int count;  /*当前数据元素个数*/
}HashTable;
int m=0;    /*散列表表长,全局变量*/

/*初始化散列表*/
Status InitHashTable(HashTable *H)
{
    int i;
    m=HASHSIZE;
    H->count=m;
    H->elem=(int *)malloc(m*sizeof(int));
    for(i=0;i<m;i++)
        H->elem[i]=NULLKEY;
    return OK;
}

为了插入时计算地址,我们需要定义散列函数,散列函数可以根据不同情况更改算法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/*散列函数*/
int Hash(int key)
{
    return key % m; /*除留余数法*/
}

初始化完成后,我们可以对散列表进行插入操作。假设我们插入的关键字集合就是前面的{12,67,56,16,25,37,22,29,15,47,48,34}。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/*插入关键字进散列表*/
void InsertHash(HashTable *H,int key)
{
    int addr = Hash(key);   /*求散列地址*/
    while(H->elem[addr] != NULLKEY) /*如果不为空,则冲突*/
        addr = (addr+1) % m;    /*开放地址法的线性探测*/
    H->elem[addr] = key;        /*直到有空位后插入关键字*/
}

代码中插入关键字时,首先算出散列地址,如果当前地址不为空关键字,则说明有冲突。此时我们应用开放地址法的线性探测进行重新寻址,此处也可更改为链地址法等其他解决冲突的办法。

散列表存在后,我们在需要时就可以通过散列表查找要的记录。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/*散列表查找关键字*/
Status SearchHash(HashTable H,int key,int *addr)
{
    *addr = Hash(key);  /*求散列地址*/
    while(H.elem[*addr] != key) /*如果不为空,则冲突*/
    {
        *addr = (*addr+1) % m;  /*开放定址法的线性探测*/
        if(H.elem[*addr] == NULLKEY || *addr == Hash(key))
        {
            return UNSUCCESS;   /*则说明关键字不存在*/
        }
    }

    return SUCCESS;
}

查找代码与插入的代码非常类似,只需做一个不存在关键字的判断而已。

(2)散列表查找实现代码(Java)

工程目录结构

散列表查找类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.red.hash.search;

public class HashSearch { 
   

    public static int searchHash(int[] hash, int hashLength, int key) {
        //哈希函数
        int hashAddress = key % hashLength;

        //指定hashAddress对应值存在但不是关键值,则用开放寻址法解决
        while(hash[hashAddress] != 0 && hash[hashAddress] != key) {
            hashAddress = (++hashAddress) % hashLength;
        }

        //查找到了开放单元,表示查找失败
        if(hash[hashAddress]==0) {
            return -1;
        }

        return hashAddress;
    }

    //数据插入哈希表
    public static void insertHash(int[] hash, int hashLength, int data) {
        //哈希函数
        int hashAddress = data % hashLength;

        //如果key存在,则说明已经被别人占用,此时必须解决冲突
        while(hash[hashAddress] != 0) {
            //用开放寻址法找到
            hashAddress = (++hashAddress) % hashLength;
        }

        //将data存入字典中
        hash[hashAddress] = data;
    }

}

测试类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.red.hash.search;

public class HashSearchTest { 
   
    public static void main(String[] args) {

        int hashLength = 12;
        int[] hash = new int[hashLength];

        HashSearch.insertHash(hash, hashLength, 1);
        HashSearch.insertHash(hash, hashLength, 2);
        HashSearch.insertHash(hash, hashLength, 3);
        HashSearch.insertHash(hash, hashLength, 4);

        int location1 = HashSearch.searchHash(hash, hashLength, 1);
        int location2 = HashSearch.searchHash(hash, hashLength, 2);
        int location3 = HashSearch.searchHash(hash, hashLength, 3);
        int location4 = HashSearch.searchHash(hash, hashLength, 4);
        System.out.println("1所在的位置:" + location1);
        System.out.println("2所在的位置:" + location2);
        System.out.println("3所在的位置:" + location3);
        System.out.println("4所在的位置:" + location4);
    }
}

输出结果

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1所在的位置:1
2所在的位置:2
3所在的位置:3
4所在的位置:4

6.复杂度分析

单纯论查找复杂度,对于无冲突的hash表而言,查找复杂度为O(1)(在查找之前需要构建相应的Hash表)。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/146581.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年5月1,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
程序员必读:教你摸清哈希表的脾气
在哈希表中,记录的存储位置 = f (关键字),通过查找关键字的存储位置即可,不用进行比较。散列技术是在记录的存储位置和它的关键字之间建立一个明确的对应关系f 函数,使得每个关键字 key 对应一个存储位置 f(key) 且这个位置是唯一的。这里我们将这种对应关系 f 称为散列函数,又称为哈希(Hash)函数。采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。
谭庆波
2018/08/10
3980
程序员必读:教你摸清哈希表的脾气
散列查找和哈希查找_散列检索
散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。建立了关键字与存储位置的映射关系,公式如下:
全栈程序员站长
2022/11/15
1K0
数据结构之哈希表(HASH)
   当我们在编程过程中,往往需要对线性表进行查找操作。在顺序表中查找时,需要从表头开始,依次遍历比较a[i]与key的值是否相等,直到相等才返回索引i;在有序表中查找时,我们经常使用的是二分查找,通过比较key与a[i]的大小来折半查找,直到相等时才返回索引i。最终通过索引找到我们要找的元素。    但是,这两种方法的效率都依赖于查找中比较的次数。我们有一种想法,能不能不经过比较,而是直接通过关键字key一次得到所要的结果呢?这时,就有了散列表查找(哈希表)。
全栈程序员站长
2022/07/21
6190
数据结构之哈希表(HASH)
散列表
总结:这上面三种方法都是在同一个数组中进行处理,没有超过数组的范畴,改变的都是d的取值方式
大忽悠爱学习
2021/11/15
6590
散列表
是根据键 (Key) 而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。
周三不加班
2019/09/03
7370
散列表
数据结构-常用的查找算法
本篇讲讲数据结构里面常用的几个查找算法,数据结构理论篇系列差不多接近尾声了,接下来会分享一些比较特殊的概念,比如KMP、郝夫曼树等等,讲完概念以后会进入刷题阶段。刷题会用Python来,请持续关注。
张俊红
2019/03/06
2.2K0
数据结构-常用的查找算法
C语言实现散列查找的编程
Integrated Development Environment:Viusal Studio 2022
timerring
2025/05/24
1120
C语言实现散列查找的编程
重学数据结构(八、查找)
顺序查找的基本思想:从表的一端开始,顺序扫描线性表,依次扫描到的结点关键字和给定的K值相比较,若当前扫描到的结点关键字与 K相等,则查找成功;若扫描结束后,仍未找到关键字等于 K的结点,则查找失败。
三分恶
2020/12/16
8820
重学数据结构(八、查找)
哈希游戏化:系统开发时哈希表查找算法的实现
首先定义一个散列表的结构以及一些相关的常数。其中,HashTables是散列表结构。结构当中的elem为一个动态数组。
KFZ433
2022/06/21
3780
哈希表基本概念介绍及哈希冲突的处理方法(附源码)
  哈希表(散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做哈希(散列)函数,存放记录的数组叫做哈希(散列)表。
嵌入式与Linux那些事
2021/05/20
9500
哈希表基本概念介绍及哈希冲突的处理方法(附源码)
《大话数据结构》 查找 以及一个简单的哈希表例子
第八章 查找 定义:查找就是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。 8.2 查找概论 查找表(Search table):是由同一类型的数据元素构成的集合。 关键字(key):是数据元素中某个数据项的值,又称为键值。 若此关键字可以唯一的标识一个记录,则称此关键字为主关键字(Primary key)。 对于那些可以识别多个数据元素的关键字,我们称为次关键字(Secondary key)。 查找表按照操作方式来分有两大种:静态查找表和动态查找表 静态查找表(Static
xcywt
2018/03/28
2.4K0
《大话数据结构》 查找 以及一个简单的哈希表例子
散列查找
散列同顺序、链接和索引一样,是又一种数据存储方法。散列存储的方法是:以数据集合中的每个元素的关键字k为自变量,通过一种函数h(k)计算出函数值,把这个值用做一块连续存储空间(即数组或文件空间)中的元素存储位置(即下标),将该元素存储到这个下标位置上。散列存储中使用的函数h(k)被称为散列函数或哈希函数,它实现关键字到存储位置(地址)的映射(或称转换),h(k)被称为散列地址或哈希地址;使用的数组或文件空间是对数据集合进行散列存储的地址空间,所以被称为散列表或哈希表。在散列表上进行查找时,首先根据给定的关键字k,用与散列存储时使用的同一散列函数h(k)计算出散列地址,然后按此地址从散列表中取出对应的元素。
全栈程序员站长
2022/08/27
1.4K0
散列查找
数据结构:查找
衡量标准:查找过程中对关键字的平均比较次数——平均查找长度ASL。设查找到第i个元素的概率为p,比较次数为c,则查找成功的ASL_{succ}=\sum^n_{i=1}p_ic_i
ttony0
2022/12/26
1K0
数据结构:查找
数据结构与算法之哈希表
哈希表也叫散列表。 散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。 给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
袁新栋-jeff.yuan
2020/08/26
7700
散列表的相关概念
HashMap是Java源码中非常优秀的源码,涉及到很多的概念,算法、红黑树、数组、链表... 之前也尝试过硬着头皮去学习,但是由于基础本身就不是很牢固,所以后面也没有多少收获。那么,这次笔者先来梳理一下HashMap的一些概念。
飞翔的竹蜻蜓
2020/07/08
7440
散列表的相关概念
从淘宝推荐到微信搜索:查找算法如何支撑亿级用户——动画可视化数据结构之查找算法
盛透侧视攻城狮
2025/03/31
1260
从淘宝推荐到微信搜索:查找算法如何支撑亿级用户——动画可视化数据结构之查找算法
【408&数据结构】散列 (哈希)知识点集合复习&考点题目
散列查找是一种高效的查找方法,它通过散列函数将关键字映射到数组的一个位置,从而实现快速查找。这种方法的时间复杂度平均为(O(1)),但最坏情况下可能会退化到(O(n))。
苏泽
2024/09/09
3300
【408&数据结构】散列 (哈希)知识点集合复习&考点题目
哈希相关知识再学习
在平时工作和源码学习的过程中经常遇到哈希相关的问题,每次都会上网找资料回忆哈希相关的知识点。趁这机会记录下来,防止以后又忘记了!!
静默加载
2020/05/29
7920
学生物的女朋友都能看懂的哈希表总结!
之前给大家介绍了链表,栈和队列今天我们来说一种新的数据结构散列(哈希)表,散列是应用非常广泛的数据结构,在我们的刷题过程中,散列表的出场率特别高。所以我们快来一起把散列表的内些事给整明白吧,文章框架如下。
公众号袁厨的算法小屋
2020/11/25
9000
学生物的女朋友都能看懂的哈希表总结!
散列表(一):散列表概念、 散列函数构造方法、 常见字符串哈希函数(测试冲突)
一、散列表基本概念 1、散列表(hash table) ,也叫哈希表,是根据关键码而直接进行访问的数据结构。也就是说,它通过把关键码映射到表中一个位置 来访问记录,以加快查找的速度。这个映射函数叫做散
s1mba
2017/12/28
2.2K0
散列表(一):散列表概念、 散列函数构造方法、 常见字符串哈希函数(测试冲突)
相关推荐
程序员必读:教你摸清哈希表的脾气
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档