编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 01.Hash算法待完善

目录介绍

  • 01.再哈希法
  • 02.代码案例
  • 03.测试代码

好消息

  • 博客笔记大汇总【15年10月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!所有博客陆续更新到GitHub上!
  • 链接地址:https://github.com/yangchong211/YCBlogs
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!

01.再哈希法

  • 为了消除原始聚集和二次聚集,我们使用另外一种方法:再哈希法。
  • 我们知道二次聚集的原因是,二测探测的算法产生的探测序列步长总是固定的:1,4,9,16以此类推。那么我们想到的是需要产生一种依赖关键字的探测序列,而不是每个关键字都一样,那么,不同的关键字即使映射到相同的数组下标,也可以使用不同的探测序列。
  • 方法是把关键字用不同的哈希函数再做一遍哈希化,用这个结果作为步长。对于指定的关键字,步长在整个探测中是不变的,不过不同的关键字使用不同的步长。
  • 第二个哈希函数必须具备如下特点:
    • 一、和第一个哈希函数不同
    • 二、不能输出0(否则,将没有步长,每次探测都是原地踏步,算法将陷入死循环)。
  • 专家们已经发现下面形式的哈希函数工作的非常好:stepSize = constant - key % constant; 其中constant是质数,且小于数组容量。
  • 再哈希法要求表的容量是一个质数,假如表长度为15(0-14),非质数,有一个特定关键字映射到0,步长为5,则探测序列是0,5,10,0,5,10,以此类推一直循环下去。算法只尝试这三个单元,所以不可能找到某些空白单元,最终算法导致崩溃。如果数组容量为13, 质数,探测序列最终会访问所有单元。即0,5,10,2,7,12,4,9,1,6,11,3,一直下去,只要表中有一个空位,就可以探测到它。

02.代码案例

  • 代码如下所示
    public class HashDouble {
        private DataItem[] hashArray;   //DataItem类,表示每个数据项信息
        private int arraySize;//数组的初始大小
        private int itemNum;//数组实际存储了多少项数据
        private DataItem nonItem;//用于删除数据项
         
        public HashDouble(){
            this.arraySize = 13;
            hashArray = new DataItem[arraySize];
            nonItem = new DataItem(-1);//删除的数据项下标为-1
        }
        //判断数组是否存储满了
        public boolean isFull(){
            return (itemNum == arraySize);
        }
         
        //判断数组是否为空
        public boolean isEmpty(){
            return (itemNum == 0);
        }
         
        //打印数组内容
        public void display(){
            System.out.println("Table:");
            for(int j = 0 ; j < arraySize ; j++){
                if(hashArray[j] != null){
                    System.out.print(hashArray[j].getKey() + " ");
                }else{
                    System.out.print("** ");
                }
            }
        }
        //通过哈希函数转换得到数组下标
        public int hashFunction1(int key){
            return key%arraySize;
        }
         
        public int hashFunction2(int key){
            return 5 - key%5;
        }
         
        //插入数据项
        public void insert(DataItem item){
            if(isFull()){
                //扩展哈希表
                System.out.println("哈希表已满,重新哈希化...");
                extendHashTable();
            }
            int key = item.getKey();
            int hashVal = hashFunction1(key);
            int stepSize = hashFunction2(key);//用第二个哈希函数计算探测步数
            while(hashArray[hashVal] != null && hashArray[hashVal].getKey() != -1){
                hashVal += stepSize;
                hashVal %= arraySize;//以指定的步数向后探测
            }
            hashArray[hashVal] = item;
            itemNum++;
        }
     
        /**
         * 数组有固定的大小,而且不能扩展,所以扩展哈希表只能另外创建一个更大的数组,然后把旧数组中的数据插到新的数组中。
         * 但是哈希表是根据数组大小计算给定数据的位置的,所以这些数据项不能再放在新数组中和老数组相同的位置上。
         * 因此不能直接拷贝,需要按顺序遍历老数组,并使用insert方法向新数组中插入每个数据项。
         * 这个过程叫做重新哈希化。这是一个耗时的过程,但如果数组要进行扩展,这个过程是必须的。
         */
        public void extendHashTable(){
            int num = arraySize;
            itemNum = 0;//重新计数,因为下面要把原来的数据转移到新的扩张的数组中
            arraySize *= 2;//数组大小翻倍
            DataItem[] oldHashArray = hashArray;
            hashArray = new DataItem[arraySize];
            for(int i = 0 ; i < num ; i++){
                insert(oldHashArray[i]);
            }
        }
         
        //删除数据项
        public DataItem delete(int key){
            if(isEmpty()){
                System.out.println("Hash Table is Empty!");
                return null;
            }
            int hashVal = hashFunction1(key);
            int stepSize = hashFunction2(key);
            while(hashArray[hashVal] != null){
                if(hashArray[hashVal].getKey() == key){
                    DataItem temp = hashArray[hashVal];
                    hashArray[hashVal] = nonItem;//nonItem表示空Item,其key为-1
                    itemNum--;
                    return temp;
                }
                hashVal += stepSize;
                hashVal %= arraySize;
            }
            return null;
        }
         
        //查找数据项
        public DataItem find(int key){
            int hashVal = hashFunction1(key);
            int stepSize = hashFunction2(key);
            while(hashArray[hashVal] != null){
                if(hashArray[hashVal].getKey() == key){
                    return hashArray[hashVal];
                }
                hashVal += stepSize;
                hashVal %= arraySize;
            }
            return null;
        }
        public static class DataItem{
            private int iData;
            public DataItem(int iData){
                this.iData = iData;
            }
            public int getKey(){
                return iData;
            }
        }
    }

参考文献

  • 极客时间,王争大神,数据结构和算法之美

01.关于博客汇总链接

  • 1.技术博客汇总
  • 2.开源项目汇总
  • 3.生活博客汇总
  • 4.喜马拉雅音频汇总
  • 5.其他汇总

02.关于我的博客

  • github:https://github.com/yangchong211
  • 知乎:https://www.zhihu.com/people/yczbj/activities
  • 简书:http://www.jianshu.com/u/b7b2c6ed9284
  • csdn:http://my.csdn.net/m0_37700275
  • 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/
  • 开源中国:https://my.oschina.net/zbj1618/blog
  • 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
  • 邮箱:yangchong211@163.com
  • 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
  • segmentfault头条:https://segmentfault.com/u/xiangjianyu/articles
  • 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e
贡献者: yangchong211