详解哈希表的查找


哈希表和哈希函数


在记录的存储位置和它的关键字之间是建立一个确定的对应关系(映射函数),使每个关键字和一个存储位置能唯一对应。


这个映射函数称为哈希函数,根据这个原则建立的表称为哈希表(Hash Table),也叫散列表。


以上描述,如果通过数学形式来描述就是:


若查找关键字为 key,则其值存放在 f(key) 的存储位置上。由此,不需比较便可直接取得所查记录。


注:哈希查找与线性表查找和树表查找最大的区别在于,不用数值比较。


冲突


若 key1 ≠ key2 ,而 f(key1) = f(key2),这种情况称为冲突(Collision)。


根据哈希函数f(key)和处理冲突的方法将一组关键字映射到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储位置,这一映射过程称为构造哈希表。


构造哈希表这个场景就像汽车找停车位,如果车位被人占了,只能找空的地方停。



构造哈希表


由以上内容可知,哈希查找本身其实不费吹灰之力,问题的关键在于如何构造哈希表和处理冲突。


常见的构造哈希表的方法有 5 种:


(1)直接定址法


说白了,就是小学时学过的一元一次方程。


即 f(key) = a * key + b。其中,a和b 是常数。


(2)数字分析法


假设关键字是R进制数(如十进制)。并且哈希表中可能出现的关键字都是事先知道的,则可选取关键字的若干数位组成哈希地址。


选取的原则是使得到的哈希地址尽量避免冲突,即所选数位上的数字尽可能是随机的。


(3)平方取中法


取关键字平方后的中间几位为哈希地址。通常在选定哈希函数时不一定能知道关键字的全部情况,仅取其中的几位为地址不一定合适;


而一个数平方后的中间几位数和数的每一位都相关, 由此得到的哈希地址随机性更大。取的位数由表长决定。


(4)除留余数法


取关键字被某个不大于哈希表表长 m 的数 p 除后所得的余数为哈希地址。


即 f(key) = key % p (p ≤ m)


这是一种最简单、最常用的方法,它不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。


注意:p的选择很重要,如果选的不好,容易产生冲突。根据经验,一般情况下可以选p为素数。


(5)随机数法


选择一个随机函数,取关键字的随机函数值为它的哈希地址,即 f(key) = random(key)。


通常,在关键字长度不等时采用此法构造哈希函数较为恰当。


解决冲突


设计合理的哈希函数可以减少冲突,但不能完全避免冲突。


所以需要有解决冲突的方法,常见有两类


(1)开放定址法


如果两个数据元素的哈希值相同,则在哈希表中为后插入的数据元素另外选择一个表项。


当程序查找哈希表时,如果没有在第一个对应的哈希表项中找到符合查找要求的数据元素,程序就会继续往后查找,直到找到一个符合查找要求的数据元素,或者遇到一个空的表项。


例子


若要将一组关键字序列 {1, 9, 25, 11, 12, 35, 17, 29} 存放到哈希表中。


采用除留余数法构造哈希表;采用开放定址法处理冲突。


不妨设选取的p和m为13,由 f(key) = key % 13 可以得到下表。



需要注意的是,在上图中有两个关键字的探查次数为 2 ,其他都是1。


这个过程是这样的:


a. 12 % 13 结果是12,而它的前面有个 25 ,25 % 13 也是12,存在冲突。


我们使用开放定址法 (12 + 1) % 13 = 0,没有冲突,完成。


b. 35 % 13 结果是 9,而它的前面有个 9,9 % 13也是 9,存在冲突。


我们使用开放定址法 (9 + 1) % 13 = 10,没有冲突,完成。


(2)拉链法


将哈希值相同的数据元素存放在一个链表中,在查找哈希表的过程中,当查找到这个链表时,必须采用线性查找方法。


在这种方法中,哈希表中每个单元存放的不再是记录本身,而是相应同义词单链表的头指针。


例子


如果对开放定址法例子中提到的序列使用拉链法,得到的结果如下图所示:



实现一个哈希表


假设要实现一个哈希表,要求


a. 哈希函数采用除留余数法,即 f(key) = key % p (p ≤ m)


b. 解决冲突采用开放定址法,即 f2(key) = (f(key)+i) % size (p ≤ m)


(1)定义哈希表的数据结构


class HashTable {

    public int key = 0; // 关键字

    public int data = 0; // 数值

    public int count = 0; // 探查次数

}


(2)在哈希表中查找关键字key


根据设定的哈希函数,计算哈希地址。如果出现地址冲突,则按设定的处理冲突的方法寻找下一个地址。


如此反复,直到不冲突为止(查找成功)或某个地址为空(查找失败)。


/**

* 查找哈希表

* 构造哈希表采用除留取余法,即f(key) = key mod p (p ≤ size)

* 解决冲突采用开放定址法,即f2(key) = (f(key) + i) mod p (1 ≤ i ≤ size-1)

* ha为哈希表,p为模,size为哈希表大小,key为要查找的关键字

*/

public int searchHashTable(HashTable[] ha, int p, int size, int key) {

    int addr = key % p; // 采用除留取余法找哈希地址

 

    // 若发生冲突,用开放定址法找下一个哈希地址

    while (ha[addr].key != NULLKEY && ha[addr].key != key) {

        addr = (addr + 1) % size;

    }

 

    if (ha[addr].key == key) {

        return addr; // 查找成功

    } else {

        return FAILED; // 查找失败

    }

}


(3)删除关键字为key的记录


在采用开放定址法处理冲突的哈希表上执行删除操作,只能在被删记录上做删除标记,而不能真正删除记录。


找到要删除的记录,将关键字置为删除标记DELKEY。


public int deleteHashTable(HashTable[] ha, int p, int size, int key) {

    int addr = 0;

    addr = searchHashTable(ha, p, size, key);

    if (FAILED != addr) { // 找到记录

        ha[addr].key = DELKEY; // 将该位置的关键字置为DELKEY

        return SUCCESS;

    } else {

        return NULLKEY; // 查找不到记录,直接返回NULLKEY

    }

}


(4)插入关键字为key的记录


将待插入的关键字key插入哈希表


先调用查找算法,若在表中找到待插入的关键字,则插入失败;


若在表中找到一个开放地址,则将待插入的结点插入到其中,则插入成功。 


public void insertHashTable(HashTable[] ha, int p, int size, int key) {

    int i = 1;

    int addr = 0;

    addr = key % p; // 通过哈希函数获取哈希地址

    if (ha[addr].key == NULLKEY || ha[addr].key == DELKEY) { // 如果没有冲突,直接插入

        ha[addr].key = key;

        ha[addr].count = 1;

    } else { // 如果有冲突,使用开放定址法处理冲突

        do {

            addr = (addr + 1) % size; // 寻找下一个哈希地址

            i++;

        } while (ha[addr].key != NULLKEY && ha[addr].key != DELKEY);

 

        ha[addr].key = key;

        ha[addr].count = i;

    }

}


(5)建立哈希表


先将哈希表中各关键字清空,使其地址为开放的,然后调用插入算法将给定的关键字序列依次插入。


public void createHashTable(HashTable[] ha, int[] list, int p, int size) {

    int i = 0;

    // 将哈希表中的所有关键字清空

    for (i = 0; i < ha.length; i++) {

        ha[i].key = NULLKEY;

        ha[i].count = 0;

    }

    // 将关键字序列依次插入哈希表中

    for (i = 0; i < list.length; i++) {

        this.insertHashTable(ha, p, size, list[i]);

    }

}


完整代码


class HashTable {

    public int key = 0; // 关键字

    public int data = 0; // 数值

    public int count = 0; // 探查次数

} 

public class HashSearch {

    private final static int MAXSIZE = 20;

    private final static int NULLKEY = 1;

    private final static int DELKEY = 2;

    private final static int SUCCESS = 0;

    private final static int FAILED = 0xFFFFFFFF;

    /**

     * 查找哈希表

     * 构造哈希表采用除留取余法,即f(key) = key mod p (p ≤ size)

     * 解决冲突采用开放定址法,即f2(key) = (f(key) + i) mod p (1 ≤ i ≤ size-1)

     * ha为哈希表,p为模,size为哈希表大小,key为要查找的关键字

     */

    public int searchHashTable(HashTable[] ha, int p, int size, int key) {

        int addr = key % p; // 采用除留取余法找哈希地址

        // 若发生冲突,用开放定址法找下一个哈希地址

        while (ha[addr].key != NULLKEY && ha[addr].key != key) {

            addr = (addr + 1) % size;

        }

        if (ha[addr].key == key) {

            return addr; // 查找成功

        } else {

            return FAILED; // 查找失败

        }

    }

    /**

     * 删除哈希表中关键字为key的记录

     * 找到要删除的记录,将关键字置为删除标记DELKEY

     */

    public int deleteHashTable(HashTable[] ha, int p, int size, int key) {

        int addr = 0;

        addr = searchHashTable(ha, p, size, key);

        if (FAILED != addr) { // 找到记录

            ha[addr].key = DELKEY; // 将该位置的关键字置为DELKEY

            return SUCCESS;

        } else {

            return NULLKEY; // 查找不到记录,直接返回NULLKEY

        }

    }

    /**

     * 将待插入的关键字key插入哈希表

     * 先调用查找算法,若在表中找到待插入的关键字,则插入失败;

     * 若在表中找到一个开放地址,则将待插入的结点插入到其中,则插入成功。

     */

    public void insertHashTable(HashTable[] ha, int p, int size, int key) {

        int i = 1;

        int addr = 0;

        addr = key % p; // 通过哈希函数获取哈希地址

        if (ha[addr].key == NULLKEY || ha[addr].key == DELKEY) { // 如果没有冲突,直接插入

            ha[addr].key = key;

            ha[addr].count = 1;

        } else { // 如果有冲突,使用开放定址法处理冲突

            do {

                addr = (addr + 1) % size; // 寻找下一个哈希地址

                i++;

            } while (ha[addr].key != NULLKEY && ha[addr].key != DELKEY);

            ha[addr].key = key;

            ha[addr].count = i;

        }

    }

    /**

     * 创建哈希表

     * 先将哈希表中各关键字清空,使其地址为开放的,然后调用插入算法将给定的关键字序列依次插入。

     */

    public void createHashTable(HashTable[] ha, int[] list, int p, int size) {

        int i = 0        

        // 将哈希表中的所有关键字清空

        for (i = 0; i < ha.length; i++) {

            ha[i].key = NULLKEY;

            ha[i].count = 0;

        }

        // 将关键字序列依次插入哈希表中

        for (i = 0; i < list.length; i++) {

            this.insertHashTable(ha, p, size, list[i]);

        }

    }

    /**

     * 输出哈希表

     */

    public void displayHashTable(HashTable[] ha) {

        int i = 0;

        System.out.format("pos:\t", "pos");

        for (i = 0; i < ha.length; i++) {

            System.out.format("%4d", i);

        }

        System.out.println();

        System.out.format("key:\t");

        for (i = 0; i < ha.length; i++) {

            if (ha[i].key != NULLKEY) {

                System.out.format("%4d", ha[i].key);

            } else {

                System.out.format("    ");

            }

        }

        System.out.println();

        System.out.format("count:\t");

        for (i = 0; i < ha.length; i++) {

            if (0 != ha[i].count) {

                System.out.format("%4d", ha[i].count);

            } else {

                System.out.format("    ");

            }

        }

        System.out.println();

    }

    public static void main(String[] args) {

        int[] list = { 3, 112, 245, 27, 44, 19, 76, 29, 90 };

        HashTable[] ha = new HashTable[MAXSIZE];

        for (int i = 0; i < ha.length; i++) {

            ha[i] = new HashTable();

        } 

        HashSearch search = new HashSearch();

        search.createHashTable(ha, list, 19, MAXSIZE);

        search.displayHashTable(ha);

    }

}


参考资料


《数据结构习题与解析》(B级第3版)


转自:静默虚空

http://www.cnblogs.com/jingmoxukong/p/4332252.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/304146.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微软腾讯京东都在高薪招.NET Core,你准备好了吗!

金三银四跳槽季&#xff0c;古人诚不我欺&#xff0c;2年没更新简历了&#xff0c;还接到好几个电话邀约&#xff0c;打过交道的几个猎头妹子更是殷勤的频繁打招呼。认真了解一下才知道&#xff0c;今年的招聘真的很热&#xff0c;.NET招聘真的很热。头部互联网企业像微软苏州、…

.NET Core dump 分析

服务 CPU 或 内存偶尔飙高是部署环境中经常遇到的问题&#xff0c;一般会采用记录日志的方式来诊断&#xff0c;不过有些情况靠日志可能并不能分析出个所以然&#xff0c;面对实在无头绪的问题也只能暂时使用重启大法先恢复。为了尽可能精准的定位问题&#xff0c;掌握通过 dum…

外国人最常说的100个“中国词”出炉,第一个你绝对想不到…

近几年&#xff0c;“汉语热”在全球兴起&#xff0c;外国人说的念的中国词儿变多了&#xff01;那外国人最常说的、最热的“中国词”到底是啥呢&#xff1f;2月17日&#xff0c;中国外文局首次发布《中国话语海外认知度调研报告》。报告显示&#xff0c;近两年中国话语以汉语拼…

python 什么可以作为变量名_为什么强烈禁止开发人员使用isSuccess作为变量名

在日常开发中&#xff0c;我们会经常要在类中定义布尔类型的变量&#xff0c;比如在给外部系统提供一个RPC接口的时候&#xff0c;我们一般会定义一个字段表示本次请求是否成功的。关于这个”本次请求是否成功”的字段的定义&#xff0c;其实是有很多种讲究和坑的&#xff0c;稍…

自建Git服务器系列——Gitea(Gogs的孪生兄弟)

概述该项目的目标是提供一种最简单&#xff0c;最快&#xff0c;最轻松的方式来建立自托管的Git服务。使用Go&#xff0c;可以在Go支持的所有平台上进行独立的二进制分发 &#xff0c;包括x86&#xff0c;amd64&#xff0c;ARM和PowerPC体系结构上的Linux&#xff0c;macOS和Wi…

干货|吴恩达Coursera课程教你学习神经网络!

吴恩达Coursera机器学习课程系列笔记讲解课程笔记|吴恩达Coursera机器学习 Week1 笔记-机器学习基础干货|机器学习零基础&#xff1f;不要怕&#xff0c;吴恩达机器学习课程笔记2-多元线性回归干货|机器学习零基础&#xff1f;不要怕&#xff0c;吴恩达课程笔记第三周&#xff…

笔记本内置扬声器三强PK

内置扬声器PK要点外观设计差异。外表是否美观直接影响使用者的心情&#xff0c;扬声器的结构设计直接影响放音效果。实际听音较量。利用真实的人耳感受则是最能体现扬声器实际效果的。奥特蓝星&#xff1a;音质纯净&#xff0c;低音欠佳代表机型&#xff1a;惠普&#xff0c;华…

mysql内连接查询原理_MySQL全面瓦解12:连接查询的原理和应用

概述MySQL最强大的功能之一就是能在数据检索的执行中连接(join)表。大部分的单表数据查询并不能满足我们的需求&#xff0c;这时候我们就需要连接一个或者多个表&#xff0c;并通过一些条件过滤筛选出我们需要的数据。了解MySQL连接查询之前我们先来理解下笛卡尔积的原理。数据…

如何在 .NET 中使用 Kafka

Kafka 是一个开源的&#xff0c;分布式的&#xff0c;可扩展的&#xff0c;高性能的发布订阅模式的消息中间件&#xff0c;如果你要构建一个处理海量数据的系统&#xff0c;那么 Kafka 将会是一个非常好的选择&#xff0c;这篇文章我们将会讨论如何基于 Kakfa 构建一个发布订阅…

傅里叶变换和拉普拉斯变换的物理解释及区别

傅里叶变换在物理学、数论、组合数学、信号处理、概率论、统计学、密码学、声学、光学、海洋学、结构动力学等领域都有着广泛的应用&#xff08;例如在信号处理中&#xff0c;傅里叶变换的典型用途是将信号分解成幅值分量和频率分量&#xff09;。傅里叶变换能将满足一定条件的…

Teleport 开源堡垒机的使用

公司的服务器可能会存在这样一种情况&#xff0c;具体的应用是部署在一个或多个内网服务器上&#xff0c;然后由一台外网服务器通过代理的方式对外提供服务&#xff0c;例如下图&#xff1a;我们如果需要进入到内网服务器进行操作就必须先要进入外网服务器&#xff0c;然后再远…

python拼图游戏_乐趣无穷的Python课堂

Python world/特慧编/你所认为的.........pythonpython&枯燥、无趣boring“安全”提示走进特慧编走进“python编程课”让我们进入真正的编程世界&#xff0c;培养逻辑数理思维&#xff0c;学习掌握python特色&#xff0c;让你的学习过程不再枯燥、不再无趣~~~下面跟着我的脚…

让 Python 更加充分的使用 Sqlite3

我最近在涉及大量数据处理的项目中频繁使用 sqlite3。我最初的尝试根本不涉及任何数据库&#xff0c;所有的数据都将保存在内存中&#xff0c;包括字典查找、迭代和条件等查询。这很好&#xff0c;但可以放入内存的只有那么多&#xff0c;并且将数据从磁盘重新生成或加载到内存…

techempower之Plaintext上7百万RPS

在Plaintext这项测试中第一阶梯的分隔线基本算是7百万RPS&#xff0c;Beetlex并没有到到这一阶梯停留在69X万RPS处&#xff0c;虽然只差那数万但在排名上让人感觉不爽。Beetlex在很多项测都微微领先aspcore,但在最基础项落下一点点的确让我感觉到不太满意&#xff0c;更希望Bee…

详解全排列算法

简介给定 {1, 2, 3, , , n}&#xff0c;其全排列为 n! 个&#xff0c;这是最基础的高中组合数学知识。我们以 n4 为例&#xff0c;其全部排列如下图&#xff08;以字典序树形式来呈现&#xff09;&#xff1a;我们很容易想到用递归来求出它的所有全排列。仔细观察上图&#xff…

VS2019 调试技巧之附加进程

C# 创建服务并附加到进程进行调试步骤一&#xff1a;在任务栏右键-》》点击任务管理器-》》选择服务&#xff0c;找到启动的进程PID或者WINR 进入cmd命令 输入 netstat -ano | find "进程端口" 找端口步骤二&#xff1a;VS中找到“调试”菜单&#xff0c;选择“…

sql同时向两个表插入数据_SQL入门-数据库和客户端的安装,表的创建和数据插入...

1、如何验证MySQL数据库安装成功按照上图操作打开SQL命令行客户端输入安装MySQL时设置的密码并按enter键&#xff0c;得到下图&#xff1a;如果有显示出来红框里的内容&#xff0c;就表示安装成功。红框里的内容表示的是MySQL数据库版本号。2、如何用客户端&#xff08;Navicat…

我是怎么进入Oracle这样的大企业的?

导语&#xff1a;人工智能是泡沫么&#xff1f;AI产业的未来将何去何从&#xff1f;机器学习又该怎么学习&#xff1f;AI行业从业者又是怎么看待这个行业的呢&#xff1f;踏入一个行业之前最好对这个行业有个全方位的了解。本文作者饶毅&#xff0c;现就职于甲骨文公司。AI行业…

websocket文档_WebSocket推送 原理扫盲到上手实践

关于服务端推送技术&#xff0c;大家比较熟悉的可能就是轮询&#xff0c;但是轮询只能是由客户端先发起http请求。在HTTP1.1中的keep-alive方式建立的http连接&#xff0c;但是一个Request只能对应一个Response&#xff0c;而且这个Response是被动的&#xff0c;不能主动发起。…

DISCUZ7.2在通达OA2009桌面显示技巧

最近在测试DISCUZ 和通达...猛然间看到,,,可以DISCUZ可以和通达完美结合,禁不住进行了测试.....效果还挺好的...最初效果图如下:感觉挺别扭的,于是将DISCUZ调用代码更改了代码如下:[show1] <table width"100%" > <tr> <td alignleft> <di…