散列表1: 基于分离链接法的散列表

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/LIZHONGPING00/article/details/66472233

散列表

散列表的实现称为散列(hashing)。只支持二叉查找树的一部分操作,可以以常数平均时间执行插入、删除和查找,对元素间有着排序信息的树操作(如findMin、findMax等)不支持。

原理

散列表的查找算法分为两步:
1. 用散列函数将被查找的键转化为数组的一个索引。
2. 解决碰撞冲突的问题(多个键映射到数组的同一个索引的问题)

解决碰撞的两种方法:
1. 分离链接法
2. 线性探测法

散列函数

散列函数能够将任意键转化为该数组范围内的索引[0, M-1] 范围的整数。
散列函数与键的类型有关(整数、浮点数、字符串)
整数通常采用除留余数法(保证表的大小为素数)。

一种好的散列函数的实现
简单、速度快

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;
}

根据霍纳法则计算一个37的多项式函数

分离链表法

思想

设计思想:将散列到同一个值的所有元素保留到一个表中。

如图所示:
这里写图片描述

Java实现

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

/*
 * 散列表---分离链接法
 */
public class SeparateChainingHashTable<Item> {
    private static final int DEFAULT_TABLE_SIZE = 101;

    // 链表数组(多态)
    private List<Item>[] 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<>();
    }

    /*
     * 使链表数组为空
     */
    public void makeEmpty() {
        for (int i = 0; i < theLists.length; i++)
            theLists[i].clear();
        currentSize = 0;
    }

    /*
     * 是否包含x
     */
    public boolean contains(Item x) {
        List<Item> whichList = theLists[myhash(x)];
        return whichList.contains(x); // 调用List 中contains方法
    }

    /*
     * 插入散列表,如果item存在,不做操作
     */
    public void insert(Item x) {
        // 使用myhash(x)得到元素x散列后再链表数组中的下标
        // whichList 是链表数组中某下标位置的链表
        List<Item> whichList = theLists[myhash(x)];
        if (!whichList.contains(x)) { // 如果之前不包含,则添加
            whichList.add(x);

            // 再散列
            if (++currentSize > theLists.length)
                rehash();
        }
    }

    /*
     * 从散列表中删除
     */
    public void remove(Item x) {
        List<Item> whichList = theLists[myhash(x)];
        if (whichList.contains(x)) {
            whichList.remove(x);   // List 操作
            currentSize--;
        }
    }

    /*
     * hash(x)函数 
     * 涉及到关键字中所有字符,并将结果限制在适当的范围中,允许溢出
     */
    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;
    }

    /*
     * 再散列 -- 动态调整链表数组 开销比较大O(n)
     */
    private void rehash() {
        List<Item>[] oldLists = theLists;

        // 创建一个双倍长度的空表数组
        theLists = new List[nextPrime(2 * theLists.length)];
        for (int j = 0; j < theLists.length; j++)
            theLists[j] = new LinkedList<>(); // 每个元素初始化为链表

        currentSize = 0;
        for (List<Item> list : oldLists)
            for (Item item : list)
                insert(item);
    }

    // 将元素x 生成散列值,得到元素可以在表数组插入的数组下标
    private int myhash(Item x) {
        int hashVal = x.hashCode();

        hashVal %= theLists.length;
        if (hashVal < 0)
            hashVal += theLists.length;

        return hashVal;
    }

    /*
     * 找出大于n的素数,作为数组下标
     */
    private static int nextPrime(int n) {
        if (n % 2 == 0)
            n++;

        while (!isPrime(n))
            n += 2;

        return n;
    }

    /*
     * 判断n是否为素数
     */
    private static boolean isPrime(int n) {
        if (n == 2 || n == 3)
            return true;

        if (n == 1 || n % 2 == 0)
            return false;

        for (int i = 3; i * i <= n; i += 2)
            if (n % i == 0)
                return false;

        return true;
    }

    /*
     * 测试
     */
    public static void main(String[] args) {
        SeparateChainingHashTable<Integer> H = new SeparateChainingHashTable<>();

        long startTime = System.currentTimeMillis();

        final int NUMS = 2000000;
        final int GAP = 37;

        System.out.println("Checking... (no more output means success)");

        for (int i = GAP; i != 0; i = (i + GAP) % NUMS)
            H.insert(i);
        for (int i = 1; i < NUMS; i += 2)
            H.remove(i);

        for (int i = 2; i < NUMS; i += 2)
            if (!H.contains(i))
                System.out.println("Find fails " + i);

        for (int i = 1; i < NUMS; i += 2) {
            if (H.contains(i))
                System.out.println("OOPS!!! " + i);
        }

        long endTime = System.currentTimeMillis();

        System.out.println("Elapsed time: " + (endTime - startTime));
    }

}

没有更多推荐了,返回首页

私密
私密原因:
请选择设置私密原因
  • 广告
  • 抄袭
  • 版权
  • 政治
  • 色情
  • 无意义
  • 其他
其他原因:
120
出错啦
系统繁忙,请稍后再试