散列(一)

作者: 大海孤了岛 | 来源:发表于2017-03-14 13:43 被阅读48次

定义:
  • 散列是一种用于以常数平均时间执行插入,删除和查找的技术。
  • 我们将每个关键字被映射到从0到TableSize - 1这个范围中的某个数。这个映射称为散列函数。
  • 散列函数需要保证任何两个不同的关键字映射到不同的单元。
  • 当两个关键字散列到同一个值时(这叫做冲突), 应该做什么以及如何确定散列表的大小。
hash_table.png
散列函数:
  • 一般简单合理的方法是直接返回Key mod TableSize, 并且保证表的大小为素数。

a. 假设关键字是字符串,一种选择办法是将字符串的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);
        }
        return hashVal % tableSize;
}

这种方法实现起来简单合理,但如果表很大的话,并不会很好地分配关键字。例如,TableSize = 10007,假设所有的关键字至多8个字符长,由于ASCII码字符的值最多是127,那么它们的值是在0到1016之间,显然这不是一种均匀的分配。

*b. *假设关键字至少有三个字符,可以采用如下方法:

 public static int hash(String key, int tableSize){
        return (key.charAt(0) + key.charAt(1) * 27 + key.charAt(2) * 729) % tableSize;
 }

但这样依旧不能完全均匀地分配,因为3个字母的不同组合数实际只有2851,也就是只有大约表的28%被真正散列到。

c. 根据Horner法则计算一个(37)的多项式函数:

Paste_Image.png
public static 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;
    }
解决冲突的两种方法:
  • 分离链接法:将散列到同一个值的所有元素保留到一个表中。
  • 开放定址法:尝试另外一些单元,直到找到空的单元为止。

*a. 分离链表法: *

separate_chaining.png
  • 定义表的基本变量:
    //定义默认表的大小
    private static final int DEFAULT_TABLE_SIZE = 101;
    //定义表的存储
    private List<AnyType>[] theLists;
    //定义表当前的大小
    private int currentSize;
  • 定义其构造函数,进行初始化:
    public SeparateChainingHashTable(){
        this(DEFAULT_TABLE_SIZE);
    }

    //进行初始化操作
    public SeparateChainingHashTable(int size){
        //初始化表,这里调用了一个nextPrime函数,以保证其表的大小为素数
        theLists = new LinkedList[nextPrime(size)];
        //初始化表中的链表
        for (int i = 0; i < theLists.length; i ++){
            theLists[i] = new LinkedList<AnyType>();
        }
    }

    //返回下一个素数
    private static int nextPrime(int n){
        while (!isPrime(n)){
            n ++;
        }
        return n;
    }
    //判断是否为素数
    private static boolean isPrime(int n){
        for (int i = 2; i <= Math.sqrt(n); i ++){
            if (n % i == 0 && n != 2){
                return false;
            }
        }
        return true;
    }
  • 进行插入操作:如果这个元素是新元素,则它将被插入到链表的前端,因为往往新近插入的元素最有可能不久被访问。
    public void insert(AnyType x){
        //获取x根据hash后找到所对应的位置
        List<AnyType> whichList = theLists[myHash(x)];
        //如果当前位置链表不包含元素,则进行插入操作
        if (!whichList.contains(x)){
            //进行添加
            whichList.add(x);
            //判断是否达到表的长度,若达到则进行rehash操作,以扩大表的大小
            if (++ currentSize > theLists.length){
                rehash();
            }
        }
    }

    //根据值获取到其对应的hash位置
    private int myHash(AnyType x){
        int hashVal = x.hashCode();
        hashVal %= theLists.length;
        //考虑到负数的情况
        if (hashVal < 0){
            hashVal += theLists.length;
        }
        return hashVal;
    }

    private void rehash(){
        //获取到原来的表
        List<AnyType>[] oldLists = theLists;
        //对现有的表大小进行扩大
        theLists = new List[nextPrime(2 * theLists.length)];
        //初始化新表
        for (int j = 0; j < theLists.length; j ++){
            theLists[j] = new LinkedList<AnyType>();
        }
        //初始化大小
        currentSize = 0;
        //将旧表中的数据重新插入到新表中
        for (int i = 0; i < oldLists.length; i ++){
            for (AnyType item : oldLists[i]){
                insert(item);
            }
        }
    }
  • 删除操作:
    //删除操作
    public void remove(AnyType x){
        //获取到对应位置的链表
        List<AnyType> whichList = theLists[myHash(x)];
        //检查是否包含元素,包含则进行删除操作,并大小--
        if (whichList.contains(x)){
            whichList.remove(x);
            currentSize --;
        }
    }
  • 检查元素:
    //检查是否包含某个元素
    public boolean contains(AnyType x){
        List<AnyType> whichList = theLists[myHash(x)];
        return whichList.contains(x);
    }
  • 打印链表:
    //打印链表结构
    public void printHashTable(){
        for (int i = 0; i < theLists.length; i ++){
            if (theLists[i] != null && theLists[i].size() > 0){
                System.out.println("current position is : " + i );
                for (AnyType x : theLists[i]){
                    System.out.println(x);
                }
            }
        }
    }
  • 进行检测:为了效果更明显,我们强制表的结果为10,当然在实际项目中不可如此,这里只是为了测试作用。
public static void main(String[] args){
        int[] arr = {0, 81,1, 64,4, 25,36,16,49,9};
        SeparateChainingHashTable<Integer> separateChainingHashTable = new SeparateChainingHashTable<>(10);
        //进行插入操作
        for (int i = 0; i < arr.length; i ++){
            separateChainingHashTable.insert(arr[i]);
        }
        //打印链表
        separateChainingHashTable.printHashTable();
    }
  • 运行结果如下:
current position is : 0
0
current position is : 1
81
1
current position is : 4
64
4
current position is : 5
25
current position is : 6
36
16
current position is : 9
49
9
separate chaning.png
  • 当然除了插入默认的基本类型,我们还可以插入对象,但一定要重定义对象的equals()和hashCode()方法
public class Employee {
    private String name;
    private double salary;
    private int seniority;
    //构造函数
    public Employee(String name, double salary, int seniority){
        this.name = name;
        this.salary = salary;
        this.seniority = seniority;
    }
    //判断对象是否相同
    public boolean equals(Object rhs){
        //满足为Employee类型且名字相同
        return rhs instanceof Employee && name.equals(((Employee) rhs).name);
    }
    //重写hashCode
    public int hashCode(){
        return name.hashCode();
    }
    //打印对象
    @Override
    public String toString() {
        return "name: " + name + " salary: " + salary + " seniority: " + seniority + "\n";
    }
}
public static void main(String[] args){
        SeparateChainingHashTable<Employee> hashTable = new SeparateChainingHashTable<>(11);
        hashTable.insert(new Employee("worker", 1000,3));
        hashTable.insert(new Employee("worker", 1500,3));
        hashTable.insert(new Employee("worker", 1600,3));
        hashTable.insert(new Employee("Manager", 2000, 2));
        hashTable.insert(new Employee("Manager", 2500, 2));
        hashTable.insert(new Employee("boss", 3000, 1));
        hashTable.printHashTable();
    }

结果如下:

current position is : 6
name: Manager salary: 2000.0 seniority: 2
current position is : 7
name: boss salary: 3000.0 seniority: 1
current position is : 10
name: worker salary: 1000.0 seniority: 3

我们可以看到对于name相同的对象是不会重复插入的。

完整代码:
public class SeparateChainingHashTable<AnyType> {

    public SeparateChainingHashTable(){
        this(DEFAULT_TABLE_SIZE);
    }

    //进行初始化操作
    public SeparateChainingHashTable(int size){
        //初始化表,这里调用了一个nextPrime函数,以保证其表的大小为素数
        theLists = new LinkedList[nextPrime(size)];
        //初始化表中的链表
        for (int i = 0; i < theLists.length; i ++){
            theLists[i] = new LinkedList<AnyType>();
        }
    }

    public void insert(AnyType x){
        //获取x根据hash后找到所对应的位置
        List<AnyType> whichList = theLists[myHash(x)];
        //如果当前位置链表不包含元素,则进行插入操作
        if (!whichList.contains(x)){
            //进行添加
            whichList.add(x);
            //判断是否达到表的长度,若达到则进行rehash操作,以扩大表的大小
            if (++ currentSize > theLists.length){
                rehash();
            }
        }
    }
    //删除操作
    public void remove(AnyType x){
        //获取到对应位置的链表
        List<AnyType> whichList = theLists[myHash(x)];
        //检查是否包含元素,包含则进行删除操作,并大小--
        if (whichList.contains(x)){
            whichList.remove(x);
            currentSize --;
        }
    }

    //检查是否包含某个元素
    public boolean contains(AnyType x){
        List<AnyType> whichList = theLists[myHash(x)];
        return whichList.contains(x);
    }

    public void makeEmpty(){
        for (int i = 0; i < theLists.length; i ++){
            theLists[i].clear();
        }
    }

    //定义默认表的大小
    private static final int DEFAULT_TABLE_SIZE = 101;
    //定义表的存储
    private List<AnyType>[] theLists;
    //定义表当前的大小
    private int currentSize;

    //根据值获取到其对应的hash位置
    private int myHash(AnyType x){
        int hashVal = x.hashCode();
        hashVal %= theLists.length;
        //考虑到负数的情况
        if (hashVal < 0){
            hashVal += theLists.length;
        }
        return hashVal;
    }

    private void rehash(){
        //获取到原来的表
        List<AnyType>[] oldLists = theLists;
        //对现有的表大小进行扩大
        theLists = new List[nextPrime(2 * theLists.length)];
        //初始化新表
        for (int j = 0; j < theLists.length; j ++){
            theLists[j] = new LinkedList<AnyType>();
        }
        //初始化大小
        currentSize = 0;
        //将旧表中的数据重新插入到新表中
        for (int i = 0; i < oldLists.length; i ++){
            for (AnyType item : oldLists[i]){
                insert(item);
            }
        }
    }

    //返回下一个素数
    private static int nextPrime(int n){
        while (!isPrime(n)){
            n ++;
        }
        return n;
    }
    //判断是否为素数
    private static boolean isPrime(int n){
        for (int i = 2; i <= Math.sqrt(n); i ++){
            if (n % i == 0 && n != 2){
                return false;
            }
        }
        return true;
    }

    //打印链表结构
    public void printHashTable(){
        for (int i = 0; i < theLists.length; i ++){
            if (theLists[i] != null && theLists[i].size() > 0){
                System.out.println("current position is : " + i );
                for (AnyType x : theLists[i]){
                    System.out.println(x);
                }
            }
        }
    }
}

相关文章

  • python数据结构教程 Day10

    本节重点: 散列 散列函数 完美散列函数 hashlib 散列函数设计 冲突解决方案 一、散列 能够使得查找的次数...

  • 散列 & 线性散列

    Hashing 散列 原理: use key value to compute page address of t...

  • 散列(一)

    定义: 散列是一种用于以常数平均时间执行插入,删除和查找的技术。 我们将每个关键字被映射到从0到TableSize...

  • 散列

    散列值与相等性 等值对象的散列值必须相等。散列相等未必等值。 散列表算法 其他说明 key必须是可散列的。可散列需...

  • 数据结构与算法JavaScript描述(6) —— 散列(Has

    散列 散列是一种常用的数据存储技术,散列后的数据可以快速地插入或取用。散列使用的数据结构叫做散列表。在散列表上插入...

  • 散列

  • 散列

    定义 散列是一种常见的数据存储技术,散列后的数据可以快速插入或者取用。散列使用的数据解构叫做散列表。在散列中插入、...

  • 散列

    HashMap HashMap也是我们使用非常多的Collection,它是基于哈希表的 Map 接口的实现,以k...

  • 散列

    哈希码是一个散列值,通过单向函数求得,范围是int,数量有限,所以会发生散列值冲突。HashMap、Hashtab...

  • 散列

    散列的定义与整数散列 问题提出:给出 N 个正整数,再给出 M 个正整数,问这 M 个数中的每个数分别是否在 N ...

网友评论

    本文标题:散列(一)

    本文链接:https://www.haomeiwen.com/subject/keicnttx.html