本文根据《大话数据结构》一书,实现了Java版的一个简单的散列表(哈希表)

基本概念

对关键字key,将其值存放在f(key)的存储位置上。由此,在查找时不需比较,只需计算出f(key)便可直接取得所查记录。这个函数 f() 就叫做散列函数,按这个思想建立的表称为散列表

散列技术即是一种存储方法,又是一种查找方法:

  存储过程:根据关键字key,算出f(key),将记录存放在f(key)的位置上;

  查找过程:根据关键字key,算出f(key),该位置上的值即为要找的记录。

散列函数的构造方法

直接定址法

直接取关键字的线性函数为散列地址:f(key)=a×key+b(a,b为常数)

如:对下表的记录,关键字key取为出生年份,令f(key)=key-1980即可。

数字分析法

分析一组数据,找出其规律,尽可能利用这些数据来构造冲突几率较低的散列地址

如:以员工的手机号码作为关键字,前7位数字基本相同,可以选择后面四位数字作为散列地址。

平方取中法

当无法确定关键字中哪几位分布较均匀时,可以先求出关键字的平方值,然后按需要取平方值的中间几位作为散列地址。

折叠法

将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。

除留余数法

最为常用的方法,取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。

f(key) = key MOD p,p<=m。

随机数法

选择一随机函数(伪随机),取关键字的随机值作为散列地址,通常用于关键字长度不同的场合。

处理散列冲突的方法

当两个关键字key1和key2不同时,有f(key1)=f(key2),这种现象称为冲突。一般情况下,我们会尽量设计恰当的散列函数减少冲突,但无法完全避免,这就需要对冲突进行处理。

开放寻址法

一旦发生冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。根据下一个位置的不同,又可分为以下三种:

①线性探测法:

②二次探测法

③随机探测法

再散列函数法

在同义词产生地址冲突时计算另一个散列函数地址,直到冲突不再发生,这种方法不易产生“聚集”,但增加了计算时间。如下图所示(RHi代表不同的散列函数):

链地址法

相同地址的记录存放在一个单链表中,散列表值存储所有同义词子表的头指针。如下图所示:

公共溢出区法

为所有冲突的关键字建立一个公共的溢出区来存放。

代码实现

接下来建立一个简单的散列表,其散列函数采用上述的除留余数法,处理冲突的方法采用开放定址法下的线性探测法

Java代码如下:

package HashTable;

/**
* 散列表
* @author Yongh
*
*/
public class HashTable {
int[] elem;
int count;
private static final int Nullkey = -32768; public HashTable(int count) {
this.count = count;
elem = new int[count];
for (int i = 0; i < count; i++) {
elem[i] = Nullkey; // 代表位置为空
}
} /*
* 散列函数
*/
public int hash(int key) {
return key % count; // 除留余数法
} /*
* 插入操作
*/
public void insert(int key) {
int addr = hash(key); // 求散列地址
while (elem[addr] != Nullkey) { // 位置非空,有冲突
addr = (addr + 1) % count; // 开放地址法的线性探测
}
elem[addr] = key;
} /*
* 查找操作
*/
public boolean search(int key) {
int addr = hash(key); // 求散列地址
while (elem[addr] != key) {
addr = (addr + 1) % count; // 开放地址法的线性探测
if (addr == hash(key) || elem[addr] == Nullkey) { // 循环回到原点或者到了空地址
System.out.println("要查找的记录不存在!");
return false;
}
}
System.out.println("存在记录:" + key + ",位置为:" + addr);
return true;
} public static void main(String[] args) {
int[] arr = { 12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34 };
HashTable aTable = new HashTable(arr.length);
for (int a : arr) {
aTable.insert(a);
}
for (int a : arr) {
aTable.search(a);
}
}
}

  

存在记录:,位置为:
存在记录:,位置为:
存在记录:,位置为:
存在记录:,位置为:
存在记录:,位置为:
存在记录:,位置为:
存在记录:,位置为:
存在记录:,位置为:
存在记录:,位置为:
存在记录:,位置为:
存在记录:,位置为:
存在记录:,位置为:

HashTable

  代码中重点可以看:插入操作是如何处理冲突 以及查找操作是如何判断记录是否存在的。

【Java】 大话数据结构(13) 查找算法(4) (散列表(哈希表))的更多相关文章

  1. 【Java】 大话数据结构(11) 查找算法(2)(二叉排序树/二叉搜索树)

    本文根据<大话数据结构>一书,实现了Java版的二叉排序树/二叉搜索树. 二叉排序树介绍 在上篇博客中,顺序表的插入和删除效率还可以,但查找效率很低:而有序线性表中,可以使用折半.插值.斐 ...

  2. 【Java】 大话数据结构(12) 查找算法(3) (平衡二叉树(AVL树))

    本文根据<大话数据结构>一书及网络资料,实现了Java版的平衡二叉树(AVL树). 平衡二叉树介绍 在上篇博客中所实现的二叉排序树(二叉搜索树),其查找性能取决于二叉排序树的形状,当二叉排 ...

  3. 【Java】 大话数据结构(10) 查找算法(1)(顺序、二分、插值、斐波那契查找)

    本文根据<大话数据结构>一书,实现了Java版的顺序查找.折半查找.插值查找.斐波那契查找. 注:为与书一致,记录均从下标为1开始. 顺序表查找 顺序查找  顺序查找(Sequential ...

  4. Java HashMap源码分析(含散列表、红黑树、扰动函数等重点问题分析)

    写在最前面 这个项目是从20年末就立好的 flag,经过几年的学习,回过头再去看很多知识点又有新的理解.所以趁着找实习的准备,结合以前的学习储备,创建一个主要针对应届生和初学者的 Java 开源知识项 ...

  5. 算法导论-散列表(Hash Table)-大量数据快速查找算法

    目录 引言 直接寻址 散列寻址 散列函数 除法散列 乘法散列 全域散列 完全散列 碰撞处理方法 链表法 开放寻址法 线性探查 二次探查 双重散列 随机散列 再散列问题 完整源码(C++) 参考资料 内 ...

  6. Java中常用的查找算法——顺序查找和二分查找

    Java中常用的查找算法——顺序查找和二分查找 神话丿小王子的博客 一.顺序查找: a) 原理:顺序查找就是按顺序从头到尾依次往下查找,找到数据,则提前结束查找,找不到便一直查找下去,直到数据最后一位 ...

  7. Java学习之二分查找算法

    好久没写算法了.只记得递归方法..结果测试下爆栈了. 思路就是取范围的中间点,判断是不是要找的值,是就输出,不是就与范围的两个临界值比较大小,不断更新临界值直到找到为止,给定的集合一定是有序的. 自己 ...

  8. SDUT 3379 数据结构实验之查找七:线性之哈希表

    数据结构实验之查找七:线性之哈希表 Time Limit: 1000MS Memory Limit: 65536KB Submit Statistic Problem Description 根据给定 ...

  9. SDUT 3377 数据结构实验之查找五:平方之哈希表

    数据结构实验之查找五:平方之哈希表 Time Limit: 400MS Memory Limit: 65536KB Submit Statistic Problem Description 给定的一组 ...

随机推荐

  1. 【CH6201】走廊泼水节

    题目大意:给定一棵树,要求增加若干条边,将其转化为完全图,且该完全图以该树为唯一的最小生成树,求增加的边权最小是多少. 题解:完全图的问题一般要考虑组合计数.重新跑一遍克鲁斯卡尔算法,每次并查集在合并 ...

  2. 输入一个十进制的数到dx_ax,然后十六进制转十进制输出

    ;HtoD data segment n dw ? data ends stack segment db dup(?) stack ends code segment assume cs:code,s ...

  3. NO.4: 确定对象被使用前已被初始化

    1.为内置对象进行 "手工初始化",因为C++不保证初始化他们(内置类型在赋值与初始化销毁基本相同,最好还是进行初始化列表),在内置类型过多情况下,可选择private函数统一初始 ...

  4. C/C++ 类成员函数指针 类成员数据指针

    普通函数指针:  "return_type (*ptr_name)(para_types) " 类成员函数指针: "return_type (class_name::*p ...

  5. Redis数据类型和常用命令

    Redis相较于其它的数据库虽然简单,但是要熟记所有命令的用法也并非易事.一个简单的技巧是通过要操作的数据类型来将这些命令进行结构化. 数据类型和对应命令 所有存储于redis中的数据都对应于一个键值 ...

  6. Linux下使用cron让Python程序持久化运行

    正常情况下,一个python程序如果希望实现一直运行,不出错不奔溃是很难的,即使编译为可持续文件也是一样 幸运的是很多需求并不是需要24小时不间断运行,而是每隔一段时间运行一次即可 Linux系统自带 ...

  7. Java基础-Java中的并法库之重入读写锁(ReentrantReadWriteLock)

    Java基础-Java中的并法库之重入读写锁(ReentrantReadWriteLock) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 在学习Java的之前,你可能已经听说过读 ...

  8. javascript函数以及作用域简介

    javascript函数以及作用域简介 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.基本函数 对于JavaScript中函数参数,实际参数的个数可能小于形式参数的个数,函数内 ...

  9. bzoj千题计划175:bzoj1303: [CQOI2009]中位数图

    http://www.lydsy.com/JudgeOnline/problem.php?id=1303 令c[i]表示前i个数中,比d大的数与比d小的数的差,那么如果c[l]=c[r],则[l+1, ...

  10. ASP.NET记录错误日志的方式

    程序记录错误日志是一种看起来对一般用户没什么作用,但对程序开发者用处很大的东西,它能查出错误或异常的程序马迹.那么,常用的记录错误日志的方式有哪些呢? 大多数情况下使用的是 1.直接记录为txt/xm ...