当前位置: 首页 > news >正文

数据结构学习篇——哈希

哈希的概念与结构

  • 若关键字为k,则其值存放在f(k)的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f为散列函数,按这个思想建立的表为散列表。
  • 对不同的关键字可能得到同一散列地址,即 k 1 ≠ k 2 k1≠k2 k1=k2,而 f ( k 1 ) = f ( k 2 ) f(k1)=f(k2) f(k1)=f(k2),这种现象称为冲突(英语:Colision)。具有相同函数值的关键字对该散列函数来说称做同义词。综上所述,根据散列函数 f ( k ) f(k) f(k)和处理冲突的方法将一组关键字映射到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储位置,这种表便称为散列表,这一映射过程称为散列造表或散列,所得的存储位置称散列地址。
  • 在这里插入图片描述

哈希表的两个关键

  • 散列函数
    直接定址法
    数字分析法
    平方取中法
    折叠法
    随机数法
    除留余数法
  • 冲突解决
    开放定址法
    拉链法
    双散列
    再散列

实现一个HashMap

思路:实现HashMap首先确定结构为数组+链表,桶排序用的就是这个结构。其次保存数据时需要先对桶的下标做一个哈希运算,这里采用除留余数法。然后遇到哈希冲突时,采用了拉链法形成一个列表结构依次往下新增。整体来说,里面基于链表的处理比较多。

public class MyHashMap<K,V> implements IMap<K,V>{Node<K,V>[] buckets;int capacity;int size;public MyHashMap() {this.capacity = 16;this.buckets = new Node[capacity];this.size = 0;}@Overridepublic void clear() {for (int i = 0; i < buckets.length; i++) {buckets[i] = null;}}@Overridepublic boolean containsKey(K key) {int index = hash(key);if (buckets[index] == null) {return false;} else {Node<K, V> p = buckets[index];//相当于在链表中找keywhile (p != null) {K k1 = p.key;//借用java机制,hashcode和equals都来自于Object,用户可以改写这两个方法——制定对象相等的规则if (k1 == key || (k1.hashCode() == key.hashCode() && k1.equals(key))) {return true;}p = p.next;}}return false;}@Overridepublic boolean containsValue(V value) {for (int i = 0; i < buckets.length; i++) {Node<K, V> p = buckets[i];while (p != null) {if (Objects.equals(p.value, value))return true;p = p.next;}}return false;}@Overridepublic V get(K key) {int index = hash(key);if (buckets[index] != null){Node<K,V> p = buckets[index];while (p != null){K pkey = p.key;if (key == pkey || (key != null && pkey != null &&key.hashCode() == pkey.hashCode() && Objects.equals(key,pkey))){return p.value;}if (p.next == null){break;}p = p.next;}}return null;}@Overridepublic boolean isEmpty() {return size == 0;}@Overridepublic void put(K key, V value) {int index = hash(key);Node<K, V> node = new Node<>(key, value);if (buckets[index] == null){buckets[index] = node;}else {Node<K,V> p = buckets[index];while (p != null){K pkey = p.key;if (key == pkey || (key != null && pkey != null &&key.hashCode() == pkey.hashCode() && Objects.equals(key,pkey))){p.value = node.value;break;}if (p.next == null){p.next = node;size++;break;}p = p.next;}}}private int hash(K key) {if (key == null) return 0; // 或者根据设计决定如何处理 null 键//位运算正数return  (key.hashCode() & 0x7FFFFFFF) % capacity;}@Overridepublic void putAll(IMap<? extends K, ? extends V> map) {}@Overridepublic V remove(K key) {int index = hash(key);if (buckets[index] != null){Node<K,V> p = buckets[index];Node<K,V> pre = p;while (p != null){K pkey = p.key;if (key == pkey || (key != null && pkey != null &&key.hashCode() == pkey.hashCode() && Objects.equals(key,pkey))){//移除if (p == pre) {buckets[index] = pre.next;} else {pre.next = p.next;}size--;return p.value;}pre = p;p = p.next;}}return null;}@Overridepublic int size() {return size;}@Overridepublic V[] values() {return null;}private class MapInterator implements Iterator<Node> {int i = 0;Node p = buckets[0];@Overridepublic boolean hasNext() {while (this.i < capacity && p == null) {this.i++;if (this.i == capacity)p = null;elsep = buckets[this.i];}//i是一个非空的桶,p是链表头return p != null;}@Overridepublic Node next() {Node res = p;p = p.next;return res;}}public class Node<K,V>{K key;V value;Node next;public Node(K key, V value) {this.key = key;this.value = value;}@Overridepublic String toString(){return "BSTNode{" + "key=" + key + ", value=" + value + '}';}}@Overridepublic Iterator<Node> iterator() {return new MapInterator();}
}

以上实现基于固定长度的数组,没有实现扩容。以下为JAVA对HashMap的优化:

  1. HashMap的负载因子为0.75,桶中元素超过这个值时会进行扩容,且元素重新计算哈希值。
  2. 链表元素超过8个会变成红黑树。
  3. 哈希函数的优化,key.hashCode()是一个32位的int值,做了位运算后再取模。

常见的哈希算法

常见的如:取模、加每个字符、位运算二进制、乘素数等。目的都是让桶中的值分散更均匀,尽量避免冲突影响性能

public class HashFunctions {/*取余法*/static int hash1(Object x, int prime) {return x.hashCode() % prime;}/*加法*/static int additiveHash(Object key, int prime) {String objStr = key.toString();int hash = objStr.length(), i = 0;//遍历每个字符for (; i < objStr.length(); i++)hash += objStr.charAt(i);return (hash % prime);}/*利用位运算*/static int rotatingHash(String key, int prime) {int hash = key.length(), i = 0;for (; i < key.length(); ++i)hash = (hash << 4) ^ (hash >> 28) ^ key.charAt(i);return (hash % prime);}/*乘法*/static long bernstein(String key, int prime) {long h = 0;long seed = 31;//素数for (int i = 0; i != key.length(); ++i) {h = seed * h + key.charAt(i);}return h % prime;}
}

相关文章:

  • 冰冰一号教程网--介绍采用vuepress搭建个人博客
  • Git 忽略文件配置 .gitignore
  • 客户服务升级:智能语音外呼系统在多领域的场景应用解析
  • navicat中导出数据表结构并在word更改为三线表(适用于navicat导不出doc)
  • rails 创建数据库表
  • java实现序列化与反序列化
  • halcon打开图形窗口
  • SpringBoot+Redis全局唯一ID生成器
  • Vue3中到达可视区域后执行
  • Tauri v1 与 v2 配置对比
  • C++好用的打印日志类
  • Cangjie Magic在医疗领域的应用:智能体技术如何重塑医疗数字化
  • 科研 | 光子技术为人工智能注入新动力
  • Fiori学习专题二十五:Remote OData Service
  • 数据库设计理论:从需求分析到实现的全流程解析
  • 详解具身智能机器人开源数据集:RoboMIND
  • 潇洒郎: 100% 成功搭建Docker私有镜像仓库并管理、删除镜像
  • 偏移成像中,原始地震采集数据的数据规则化(Data Regularization)
  • Java进阶--设计模式
  • 【LeetCode Hot100】二叉树篇
  • Meta一季度净利增长三成:上调全年资本支出,受关税影响亚洲出口电商广告支出减少
  • 五一假期上海口岸出入境客流总量预计达59.4万人,同比增约30%
  • 伊朗外长:伊美第四轮间接谈判将于5月3日举行
  • 摩天大楼天津117大厦复工背后:停工近十年,未知挑战和压力仍在
  • 夜读丨跷脚牛肉乐翘脚
  • 宜昌打造“算力之都”:产业链快速延伸,追逐千亿级产值