散列表分析(Java实现)

算法 专栏收录该内容
26 篇文章 0 订阅

散列表分析(Java实现)


一、 散列表的原理

散列表是一种空间换时间的存储结构,是在算法中提升效率的一种比较常用的方式。

散列表(哈希表),是指可以通过关键字key直接访问到内容value的一种数据结构。可以一个value对应多个key,但是一个key只能对应一个value,其中是通过key映射到一个位置上,来直接访问value。

这里写图片描述

而映射位置的机制,会导致可能不同key指向相同位置,这种现象成为“碰撞”。这对哈希表来说是非常不希望发生的现象。而对key进行处理转换可以避免“碰撞”现象的发生。

我们通过哈希函数使内容均匀的分配在数据结构中。

二、常见的哈希函数:

1. 直接寻址法

取关键字或关键字的某个线性函数值为散列地址。

2.数字分析法

通过对数据的分析,发现数据中冲突较少的部分,用于构造散列地址。

3.平方取中法

先求出key的平方值,然后按需要取平方值的中间几位作为散列地址。

4.取随机数法

使用rand()等随机函数构造。

5.除留取余法

取关键字被某个不大于散列表的表长n的数m除后所得的余数p为三列表地址。

三、解决“碰撞”的方法

1.开放地址法

key哈希后,发现该地值已被占用,可对该地址不断加1,直到遇到一个空的地址。

2.再哈希法

发生“碰撞”后,可对key的一部份再进行哈希处理。
详细分析http://blog.csdn.net/weiyastory/article/details/52069305

3.链地址法

链地址法是通过将key映射在同一地址上的value,做成一个链表。这是较常用方法,本文将详细讲解这种方法。

这里写图片描述

四、Java以链地址法实现散列表及分析

注意事项

  • 扩容因子
    散列表的地址使用率越大,发生“碰撞”的可能性就越大,一般在散列表地址使用率达到一定值的时候,就对散列表进行扩容,这个值称为扩容因子,为小数。

  • 关键字key
    关键字可以不为整形,但一般高级语言会把所有类型的key都转换为整形,而Java中有hashCode()方法,可以得到任何类型量的哈希值。



import java.util.Objects;

/**
 * Created by max on 17-5-3.
 */
public class MyHashTable {

    private static final int DEFAULT_INITAL_CAPACITY = 5;//定义的是默认长度

    private static final float LOAD_FACTOR = 0.75f;//扩容因子

    private Entry[] table = new Entry[DEFAULT_INITAL_CAPACITY];//初始化
    private int size =0;//哈系表大小
    private int use =0;//使用的地址数量

    private class Entry{
        int key;//关键字
        int value;
        Entry next;//链表

        public Entry(int key,int value ,Entry entry)//构造函数
        {
            super();
            this.key = key;
            this.value = value;
            this.next = entry;
        }
    }

    public void put(int key,int value){//压入内容
        int index =hash(key);//通过hash方法转换,采用的是直接法

        if (table[index]==null)//说明位置未被使用
        {
         table[index] = new Entry(-1,-1,null);
        }

        Entry tmp = table[index];
        if (tmp.next == null)//说明位置未被使用
        {
            table[index].next = new Entry(key,value,null);
            size++;
            use++;
            if (use >= table.length*LOAD_FACTOR)//判断是否需要扩容
            {
                resize();//扩容方法
            }
        }else{//已被使用,则直接扩展链表
            for (tmp = tmp.next;tmp!=null;tmp = tmp.next)
            {
                int k =tmp.key;
                if(k==key)
                {
                    tmp.value = value;
                    return;
                }
            }

            Entry temp = table[index].next;
            Entry newEntry = new Entry(key,value,temp);
            table[index].next = newEntry;
            size++;
        }

    }
    public void remove(int key) //删除,链表的中间值删除方法
    {
        int index =hash(key);
        Entry e = table[index];
        Entry pre = table[index];
        if (e!=null&& e.next!=null)
        {
            for (e=e.next;e!=null;pre =e,e =e.next)
            {
                int k =e.key;
                if(k==key)
                {
                    pre.next = e.next;
                    size--;
                    return;
                }
            }
        }
    }

    public int get(int key)//通过key提取value
    {
        int index = hash(key);
        Entry e =table[index];
        if (e!=null&&e.next!=null)
        {
            for (e=e.next;e!=null;e=e.next)
            {
                int k = e.key;
                if (k ==key)
                {
                    return e.value;
                }
            }
        }
        return -1;
    }

    public int size(){//返回元素个数
        return size;

    }

    public int getLength(){//哈系表大小
        return table.length;
    }


    private void resize() {
        int newLength = table.length*2;
        Entry[] oldTable = table;
        table = new Entry[newLength];
        use = 0;
        for(int i =0 ;i<oldTable.length;i++)
        {
            if (oldTable[i]!=null&&oldTable[i].next !=null)
            {
                Entry e = oldTable[i];
                while(null!=e.next)
                {
                    Entry next = e.next;
                    int index =hash(next.key);
                    if (table[index]==null)
                    {
                        use++;
                        table[index] = new Entry(-1,-1,null);
                    }
                    Entry temp = table[index].next;
                    Entry newEntry =new Entry(next.key,next.value,temp);
                    table[index].next = newEntry;

                    e = next;
                }
            }
        }

    }

    private int hash(int key) {//哈希方法
        return key%table.length;
    }



}

参考 《轻松学算法互联网算法面试宝典》赵烨

  • 1
    点赞
  • 0
    评论
  • 6
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

/* * 基于散列表实现的(无序)词典结构 * 采用分离链策略解决冲突 */ package dsa; public class Dictionary_HashTable implements Dictionary { private Dictionary[] A;//桶数组,每个桶本身也是一个(基于列表实现的)词典结构 private int N;//散列表长 private final double maxLemda = 0.75;//装填因子上限 private int size;//词典结构的规模 private EqualityTester T;//判等器 //默认构造方法 public Dictionary_HashTable() { this(0, new EqualityTesterDefault()); } //构造方法 public Dictionary_HashTable(int n, EqualityTester t) { T = t; N = p(n);//桶数组容量取为不小于n的最小素数 A = new Dictionary[N]; for (int i=0; i<N; i++) A[i] = new Dictionary_DLNode(T); size = 0; } /***************************** 辅助方法 *****************************/ //散列定址函数(采用模余法) private int h(Object key) { return key.hashCode() % N; } //判断n是否为素数 private static boolean prime(int n) { for (int i=3; i<1+Math.sqrt(n); i++) if (n/i*i == n) return false; return true; } //取不小于n的最小素数 private static int p(int n) { if (3>n) n = 3; n = n | 1;//奇数化 while (!prime(n)) n += 2; return n; } /***************************** ADT方法 *****************************/ //查询词典结构当前的规模 public int getSize() { return size; } //判断词典结构是否为空 public boolean isEmpty() { return 0==size; } //若词典中存在以key为关键码的条目,则返回其中的一个条目;否则,返回null public Entry find(Object key) { return A[h(key)].find(key); } //返回由关键码为key的条目组成的迭代器 public Iterator findAll(Object key) { return A[h(key)].findAll(key); } //插入条目(key, value),并返回该条目 public Entry insert(Object key, Object value) { Entry entry = A[h(key)].insert(key, value);//将新条目插至桶A[h(key)]对应的子词典 size ++;//更新规模记录 if (size > N * maxLemda) rehash();//若装填因子过大,则重散列 return entry;//返回null标志 } //若词典中存在以key为关键码的条目,则将其摘除并返回;否则,返回null public Entry remove(Object key) { Entry oldEntry = A[h(key)].remove(key); if (null!=oldEntry) size--; return oldEntry; } //返回词典中所有条目的一个迭代器 public Iterator entries() { List L = new List_DLNode(); for (int i=0; i<N; i++) { Iterator it = A[i].entries(); while (it.hasNext()) L.insertLast(it.getNext()); } return new IteratorElement(L); } //重散列 private void rehash() { Iterator it = this.entries(); N = p(N<<1); A = new Dictionary[N];//桶数组容量至少加倍 for (int i=0; i<N; i++) A[i] = new Dictionary_DLNode(T);//为每个桶分配一个子词典 while (it.hasNext()) {//将其对应的词典结构中的 Entry e = (Entry)it.getNext();//各条目逐一取出,将其 Object k = e.getKey();//关键码和 Object v = e.getValue();//数据对象 A[h(k)].insert(k, v);//整合为新的条目,插入对应的子词典中 } } }
©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值