散列表

1 前言

散列表(hash table)ADT,常常叫做散列(hashing),基本结构是数组+链表,是一种用于以常数平均时间 O ( 1 ) O(1) O(1) 执行插入 ,删除和查找的技术,排序不会得到有效的支持,诸如findMin和findMax都是散列不支持的。
通多key --value 进行存储数据,key作为关键字,通过散列函数分布到数组,value则插入到关键字对应的位置的数据结构中。

2 散列函数

通过散列函数计算key的位置,即对应散列数组的下标。

  • 散列要保证均匀分布
  • 加载因子: 散列表元素个数/散列表长度,HashMap的是0.75,超过就要扩容。
    一个简单的散列
// 使用 字符串的ASCII 或 Unicode 编码,如果碰到 大的表,不能保证 关键字均匀分配
    public static int hash(String key,int tableSize){
        int hashVal = 0;
        for(int i=0;i<key.length();i++){
            hashVal+=key.charAt(i);
        }
        System.out.println("hashVal is "+hashVal);
        return hashVal%tableSize;
    }

这是一个不好的,计算简单,但散列的数量有限,分布不均匀。

  • 散列冲突: 当两个数散列到同一个位置,就产生了冲突,主要编程就是解决冲突。
  • 散列表长度,即数组长度保持素数,散列效果好。

一个好的散列

 // hk = ((k2)*37+k1)*37+k0
    public static int goodHash(String key,int tableSize){
        int hashVal = 0;
        for(int i=0;i<key.length();i++){
            hashVal = 37*hashVal+key.charAt(i);
        }
        hashVal %= tableSize;
        if(hashVal<0){
            hashVal+=tableSize;
        }
        return hashVal;
    }

3 解决散列冲突

3.1 分离链接法

将散列到同一个位置的元素保留在一个表中,比如HashMap就是这样解决的。

  • 基本结构: 数组+链表
    插入的时候,通过计算hash码,找到存放数据的数组下标,将新元素插入到对应的链表头部。因为新插入的元素可能不久要被访问。
    下面是实现
package 散列.分离链接法;

import java.util.LinkedList;
import java.util.List;

public class SeparateChainingHashTable <T>{
    private static final int DEFAULT_TABLE_SIZE = 101; // 默认表长度
    private List<T>[] theLists; // theLists 是数组,里边的元素是双向链表
    private int currentSize; // 已经散列的元素个数
    public SeparateChainingHashTable() {
        this(DEFAULT_TABLE_SIZE);
    }
    public SeparateChainingHashTable(int size) {
        theLists = new LinkedList[nextPrime(size)];
        for (int i = 0; i < theLists.length; i++) {
            theLists[i] = new LinkedList<>();
        }
    }
    // 此处 另加载因子等于 1
    public void insert(T x){
        List<T> whichList = theLists[myHash(x)]; // myhash 函数的得出 插入元素 x 在 数组存放分下表,返回结果说就是 要插入的 链表
        if(!whichList.contains(x)){ // 先检查 是是否存在,然后 添加
            whichList.add(x);
            if (++currentSize > theLists.length){
                reHash();
            }
        }
    }
    public void remove(T x){
        List<T> whichList = theLists[myHash(x)];
        if(!whichList.contains(x)){
            whichList.remove(x);
            currentSize--;
        }
    }
    public boolean contains(T x){
        List<T> whichList  = theLists[myHash(x)];
        return whichList.contains(x);
    }
    public void makeEmpty(T x){
        for (int i = 0; i < theLists.length; i++) {
            theLists[i].clear();
            currentSize = 0;
        }
    }


    private void reHash(){
        List<T>[] oldLists=theLists;//复制一下一会要用    theLists在又一次new一个
        //对在散列的表同样进行素数修正
        theLists=new LinkedList[nextPrime(2*theLists.length)];
        for(int i=0;i<theLists.length;i++)
        {
            theLists[i]=new LinkedList<T>();
        }
        //把原来的元素拷贝到新的数组中  注意是把集合中的元素复制进去
        for(int i=0;i<oldLists.length;i++)
        {
            for (T t : oldLists[i])
            {
                insert(t);
            }
        }
    }
    // 将 hashCode 返回的int,转成适当的数组下标
    private int myHash(T x){
        int hashVal = x.hashCode();
        hashVal %= theLists.length;
        if(hashVal<0){
            hashVal+=theLists.length;
        }
        return hashVal;
    }
    // 求 下一个素数
    private static int nextPrime(int n){
        while(!isPrime(n))
        {
            n++;
        }
        return n;

    }
    // 判断是否是 素数
    private static boolean isPrime(int n){
        int i=1;
        while((n%(i+1))!=0) {
            i++;
        }
        if(i==n-1)
            return true;
        else
            return false;
    }
}

上面我们的加载因子 是 1,通过数学证明我们知道,散列表的大小对于效率影响不大,加载因子才是最重要的。

2.1 不用链表的(开放地址法)

分离链接法是使用一些链表,因为给新单元分配地址需要时间,导致算法速度减慢,可以采用探测散列表的方法,找出空的位置,这中思路要求 加载因子低于 0.5。

2.1.1 线性探测法

利用线性函数 f ( i ) = i f(i)=i f(i)=i,逐个探测找到空的单元。
比如
散列函数是 h a s h v a l u e hashvalue hashvalue mod 10,

在这里插入图片描述
初始数组长度 10,下标 是 0到9,第一次插入 89,散列后是 9的位置,第二次插入18,放到 8的位置,第三次插入49,散列结果是9,因为9的位置已经有89了,所以需要找到一个空的单元,从第0个位置开始,发现空的位置,就插入,插入58 同理。

  • 只要表足够大,总能找到一个位置,但花费时间也多,占据单元也会形成一些聚集区块。

2.1.2 平方探测法

和线性原理一样,使用的函数是 f ( i ) = i 2 f(i)=i^2 f(i)=i2

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值