数据结构散列自学笔记1

散列数据结构的目的

常数时间进行插入,删除,查找的结构。但是会丢失任何元素的排序,以及位置的信息。在后面我将用自己的几种实现来测试常数时间的运行。

散列的一些定义

散列表:固定大小的数组,来存储数据,每个数据有对应的关键字来进行查找,每个存放的数据通过关键字映射到散列表进行存储
散列函数:关键字映射到的位置 被称为散列函数
冲突:多个关键字映射到同一位置,后面会介绍解决冲突的办法以及对比
填装因子:散列表中元素个数/散列表总大小大

散列函数的选择

在选择散列函数之前需要说明散列函数的选取原则

  1. 计算方便,理解方便,也就是不复杂。
  2. 散列的位置相对均匀

接下来进行散列函数的选取

关键字为整数

一般情况下整数的选择是 key mod tablesize 关键字对散列表大小求余,且散列表的大小为素数。

    /**
     * 整数为关键字
     * 求余的方式进行散列函数的选取
     */
    private int hash(int key, int tableSize) {
        key %=tableSize;
        if (key < 0) {
            key += tableSize;
        }
        return key;
    }

表大小的控制相关代码如下

    /**
     * 判断是否为素数
     */
    private boolean isPrime(int n) {
        if (n < 2) return false;

        for (int i = 2; i <= Math.sqrt(n); i++) {
            if (n % i == 0)
                return false;
        }
        return true;
    }
    
    /**
     * 获取下一个素数
     */
    private int nextPrime(int domain) {
        if (isPrime(domain)) return domain;
        if (domain % 2 == 0) domain++; //如果是偶数 +1

        while (!isPrime(domain)) {
            domain += 2;
            if (domain <= 2) return 2;
        }
        return domain;
    }

关键字为字符串

一般java语言的关键字选取是取对象的hashcode,上面的求余散列函数已经可以满足。但是也有字符串作为散列函数的情况,参考数据结构预算法分析java语言描述,字符串的散列函数采用多项式函数。

    /**
     * 字符串为关键字
     * 多项式的散列函数  h=((k2)*37+k1)*37+k0 的表现形式
     */
    private int hash(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;
    }

仅做了解,我针对这两种散列函数做了冲突的测试,有兴趣的话我会发出来。

解决冲突的方式

分离连接法

就是将散列到同一个位置的元素装在集合中,如linkedList.每有一个冲突元素则linkedList的长度就增加1.

直接上实现代码

/**
 * 自己实现的分离散列法散列表
 * 散列函数选取 key% tableSize 
 */
public class LinkedHashTable<T> {

    private LinkedList<T>[] hashTable;

    /**
     * 默认大小为101
     */
    private static final int DEFAULT_SIZE = 101;
    
    private int currentSize;

    public LinkedHashTable() {
        this(DEFAULT_SIZE);
    }

    public LinkedHashTable(int size) {
        hashTable = new LinkedList[nextPrime(size)];
        for (int i = 0; i < hashTable.length ; i++) {
            hashTable[i] = new LinkedList();
        }
    }
    
    private int hash(int key, int tableSize) {
      key %= tableSize;
      if (key < 0) {
          key += tableSize;
      }
      return key;
    }
  }

初始化的操作,默认大小为101,如果自己指定大小,则表的大小会选取指定数字的下一个素数。


    public boolean contains(T t) {
        LinkedList<T> linkedList = hashTable[hash(t)];
        return linkedList.contains(t);
    }

    public void insert(T t) {
        LinkedList<T> linkedList = hashTable[hash(t)];
        if (!linkedList.contains(t)) {
            linkedList.add(t);
            // 如果负载因子大于0.75
            if (currentSize++ > (hashTable.length*0.75)) {
                rehash();//重新分配 hash表
            }
        }
    }

    public void remove(T t) {
        LinkedList<T> linkedList = hashTable[hash(t)];
        if (!linkedList.contains(t)) {
            linkedList.remove(t);
            currentSize--;
        }
    }

都是先定位散列位置,然后再执行相应的操作
添加不允许重复元素,注意的是重新分配散列表的操作,称为再散列,运行时间为O(n),因为有n个元素需要重新散列,是一个时间开销非常大的操作。具体操作后面介绍,这里主要说明重新散列的时机。
一般来说当散列因子达到某值时进行再散列操作,负载因子越大时说明冲突的几率越高,当负载因子大于1时则必定会有冲突。 平均遍历次数为 (1+ 负载因子/2) 次, java标准库的 HashSet、HashMap 的负载因子为0.75 平均遍历次数为1.37,所以合理的方式是当填装因子大于0.75时进行再散列操作。
所以对于一个散列表,真正重要的是填装因子的大小,直接决定散列表的性能。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值