乐趣区

关于java:我所知道数据结构之哈希表

作者前言


大家好, 我是阿濠, 今篇内容跟大家分享的是数据结构之哈希表, 很快乐分享到 segmentfault 与大家一起学习交换, 初次见面请大家多多关照, 一起学习提高.

前言介绍

在后面的文章中,先后学习了 线性表、数组、字符串和树 ,并着剖析它们对于 数据的增删改查操作 对于数据处理他们彼此 各有千秋,例如

  • 线性表中的 栈和队列 增删 有严格 要求 ,它们会更关注 数据的程序
  • 数组和字符串 须要放弃 数据类型的对立 ,并且在 基于索引查找 上会更有 劣势
  • 的劣势则体现在 数据的档次上

然而它们也存在一些缺点:数据条件的查找 ,都须要对 全副数据或者局部数据 进行 遍历

有没有一种办法能够 省去数据比拟的过程 ,从而进一步晋升数值条件 查找的效率 呢?

那么接下来将介绍一种高效率的查找神器:哈希表

一、什么是哈希表?

根本介绍

哈希表 名字源于 Hash, 也能够叫作 散列表 。哈希表是一种非凡的数据结构,是依据 关键码值 (Key value) 而间接进行 拜访的数据结构。它与数组、链表以及树等数据结构相比,有显著的区别。

哈希表的核心思想

之前的数据结构 里,数据的存储地位和数据的具体数值之间 不存在任何关系 ,因而在查找问题时,这些 有些数据结构 必须采取 逐个比拟 的办法实现。

而哈希表的设计采纳了 函数映射的思维 ,将记 录存储地位和记录关键字关联起来 ,能够 疾速定位 须要查找的记录 以及 不须要与表中的记录进行比拟 后再查找

也就是说,它通过把 关键码值 映射到 表中一个地位 来拜访记录,以放慢查找的速度。这个 映射函数 叫做 散列函数 寄存记录的数据 叫做 散列表

示例意识哈希表

如果对一个手机通讯录进行存储,并且依据姓名找出一个人的手机,如下所示

  • 张一:13212345678
  • 张二:13112345678
  • 张三:18112345678
  • 张思:18012345678

办法一:定义蕴含 姓名、手机号码 的构造体、再 通过链表 把四个联系人的 存储 起来当要判断 '张四' 是否在链表中,或者通过 '手机号' 查找时,须要 从链表头结点开始 遍历,顺次 将节点中的 姓名字段 '张四'比拟。

直到 查找胜利 或者 全副遍历一遍 为止,这种做法的工夫复杂度为O(n);

办法二:借助 哈希表的思路 ,升高工夫复杂度构建 姓名到地址 映射函数 ,这样能够在O(1) 的工夫复杂度实现数据的查找,即 ”地址 = F(姓名)

哈希函数

如果对下面的例子采纳的 Hash 函数 为:姓名的每个字 拼音结尾大写字母 ASSII 码之和,那么下面的手机通讯录例子如下所示

  • address(张一) = ASCII(Z) + ASCII(Y) = 90 +89 =179
  • address(张二) = ASCII(Z) + ASCII(E) = 90 +69 =159
  • address(张三) = ASCII(Z) + ASCII(S) = 90 +83 =173
  • address(张四) = ASCII(Z) + ASCII(S) = 90 +83 =173

那么就会呈现一个问题:'张三'与 ’张四 '哈希函数值统一,这就造成 哈希抵触 了!

从实质上来说,哈希抵触只能 尽可能的缩小 不能完全避免,因为输出的数据是凋谢汇合

这样就阐明哈希表须要 设计正当的哈希函数 ,并且对抵触有 一套解决的机制 解决

如何设计哈希函数

对于哈希抵触问题,那么咱们如何设计呢?先来看看 罕用的哈希函数办法

一、间接定制法

哈希函数为 关键字到地址 线性函数,即 H(key) =a * key + b, 这里 a 和 b 是常数

二、数字分析法

假如 关键字汇合 中每个 关键字 key 由 s 位数字 组成 (k1,k2,k3,…,ks),则 从提取散布平均的若干位 组成哈希地址

三、平方取中法

如果关键字的 每一位都有某些数字反复 呈现,并且 频率很高 ,能够先 求关键字的平方值 ,并且通过平方 扩充差别,并取两头几位作为最终存储地址

四、折叠法

如果 关键字的位数很多 ,能够将关键字宰割为 几个等长的局部 ,取它们的 叠加和的值(舍去进位)作为哈希地址

五、除留余数法

事后设置一个数,对关键字 进行取余 (%) 运算。即地址为 key mod p

如何解决哈希抵触

罕用的哈希函数办法一旦产生哈希抵触,咱们该怎么解决?罕用办法解决介绍

一、线性探测法

一个关键字和另一个关键字发生冲突 时,应用 某种探测技术 在哈希表中造成一个 探测序列 ,而后沿着 探测序列顺次查找 上来,一旦碰到 一个空的单元时,则插入其中

顺次插入 12,13,25 则无问题,当插入 23 时 (23 % 11)= 1 造成抵触,地址一 已 被占用 ,因而 沿着地址一探测 直至地址四为空,则将插入其中

二、链地址法

将哈希地址雷同的记录存储在一张线性链表中

哈希表的劣势:

它以 提供十分快的 插入 - 删除 - 查找操作 ,无论多少数据,插入和删除只须要靠近 常量的工夫,在查找方面,有时候比树还快,根本能够一下定位到查找的元素

哈希表的有余:

哈希表中的数据时 没有程序概念 的,所以 不能 以一种 固定的形式 (比方从小到大)来 遍历其中的元素 ,并且哈希表中key 是不容许反复 的。

二、通过利用实例意识哈希表

先来看一个理论需要,有一个上机题:

有一个公司, 当 有新的员工 来报道时要求将该 员工的信息退出(id、性别、年龄、住址 …)

问题:当 输出 该员工的 id 时, 要求 查找 到该员工的 所有信息.

要求:不应用数据库, 尽量节俭内存, 速度越快越好, 增加时保障 id 从低到高插入

这个时候就能够应用 链表哈希表 (Hash table),解决抵触采纳 链地址法

思路图解剖析

哈希表里每个元素都是一个链表,指向雇员信息而雇员信息又能够指向下一个雇员信息,链表治理雇员,而哈希表治理链表 …..

// 示意雇员类
class Emp{

    public  int id;     // 雇员 Id

    public String name; // 雇员名称

    public Emp next;    // 下一位雇员

   
    public Emp(int id, String name) {
            this.id = id;
            this.name = name;
    }
    
}

//EmpLinkedList 雇员链表
class EmpLinkedList{

    // 头指针 指向第一个雇员 链表头指针间接指向第一个 Emp
    private Emp headEmp;

    // 增加雇员到链表
    //1. 假如增加雇员时 id 是自增长 即 id 总是从小到大的
    // 因而咱们能够假如增加雇员到链表的最初
    public void add(Emp emp){

        // 如果增加的是第一个雇员
        if(headEmp == null){
            headEmp = emp;
            return;
        }

        // 如果不是第一个雇员则应用指针指向帮忙定位到最初
        Emp curEmp = headEmp;
        while (true){
            // 阐明已到链表最初了
            if (curEmp.next == null){break;}
            curEmp = curEmp.next;
        }
        // 退出时将以后 emp 退出链表
        curEmp.next = emp;
    }

    // 依据 id 找到对应雇员所在的链表地位
    // 若没有则返回为空
    public Emp findEmpById(int id){

        // 若链表以后头指针为空则代表链表里没有数据
        if(headEmp == null){System.out.println("以后链表为空");
        }

        Emp curEmp = headEmp;
        while (true){

            // 若链表里的 id == 要找的 id 则代表找到了
            if(curEmp.id == id){break;}
            // 链表最初元素也不满足 则链表里没有要找的 id
            if(curEmp.next ==null){
                curEmp = null;
                break;
            }
            // 进行后移查找
            curEmp = curEmp.next;
        }

        return curEmp;
    }



    // 遍历以后链表的所有雇员信息
    public void list(int no){

        // 判断以后链表是否有数据
        if(headEmp == null){System.out.println("以后"+(no+1)+"链表为空!");
            return;
        }
        System.out.println("以后"+(no+1)+"链表信息为");
        Emp curEmp = headEmp;
        while (true){
            // 输入信息
            System.out.printf("=> id=%d name=%s \t",curEmp.id,curEmp.name);

            // 如果是最初一个则完结
            if (curEmp.next == null){break;}
            // 往后挪动
            curEmp=curEmp.next;
        }
    }
}

//hasTab 治理多条链表
class HashTab{private EmpLinkedList[] linkedListArray;

    private int size;// 示意有多少条链表

    public HashTab(int size){
    
        this.size = size;
        
        linkedListArray = new EmpLinkedList[size];
        
        // 哈希表里的每个链表都初始化创立,否则为链表为空
        for (int i =0; i<size; i++){linkedListArray[i] = new EmpLinkedList();}
    }

    // 链表增加雇员
    public void add(Emp emp){
        // 依据以后员工的 id 找到适合增加的链表
        int emplikedListNo = hashFun(emp.id);
        linkedListArray[emplikedListNo].add(emp);
    }

    // 遍历 linkedListArray 里的所有的链表
    public void list(){for (int i = 0 ;i< linkedListArray.length; i++){linkedListArray[i].list(i);
        }
    }

    // 依据输出的 id 找到适合的链表进行查找信息
    public void findEmpById(int id){

        // 确定查找的 id 在那个链表里
        int emplinkedNo = hashFun(id);
        Emp emp = linkedListArray[emplinkedNo].findEmpById(id);
        if (emp!=null){System.out.printf("在 %d 该链表中找到该 id = %d 雇员 \n",(emplinkedNo+1),id);
        }else {System.out.println("在哈希表中没有找到输出的 id 雇员");
        }
    }


    // 编写散列函数 应用取模法
    public int hashFun(int id){return id % size;}

}

接下来增加数据测试一下把

public static void main(String[] args) {

    // 创立长度为 7 的哈希表
    HashTab hash =new HashTab(7);
    // 初始化雇员数据
    Emp emp1 =new Emp(1,"张三");
    Emp emp2 =new Emp(2,"李四");
    Emp emp3 =new Emp(3,"王五");
    Emp emp4 =new Emp(8,"王五");

    // 依据以后雇员 id 找到适合的链表地位
    hash.add(emp1);
    hash.add(emp2);
    hash.add(emp3);
    hash.add(emp4);

    // 查看以后哈希表里的链表数据
    hash.list();

    // 查找 id = 9 的雇员
    hash.findEmpById(9);

    // 查找 id = 3 的雇员
    hash.findEmpById(3);

}

输入后果为:以后 1 链表为空!以后 2 链表信息为 => id=1 name= 张三      => id=8 name= 王五      
以后 3 链表信息为 => id=2 name= 李四      
以后 4 链表信息为 => id=3 name= 王五      
以后 5 链表为空!以后 6 链表为空!以后 7 链表为空!在哈希表中没有找到输出的 id 雇员
在 4 该链表中找到该 id = 3 雇员
退出移动版