[笔记]算法复习笔记---数组、集合、散列表(下)

    xiaoxiao2021-03-25  67

    散列表是一种空间换时间的数据结构,在算法中提升效率的一种常用的方法。但是,正如其特点,有时候所消耗的空间,真让人头疼,用的时候在二者之间权衡。

    散列表,又叫哈希表(HashTable),是能够通过给定的关键字的值直接访问到具体对应值的数据结构。也就是说,把关键字映到一个表中的位置来直接访问记录,以加快访问速度。

    通常,我们通过Key来找Value,也就是说,通过Key访问一个映射表来得到Value的地址。而这个映射表,也叫作散列函数或者哈希函数,存放记录的数组叫做散列表。

    如果可以通过不同的Key访问到同一个Value,那么就发生了碰撞,我们需要的是通过一个Key可以访问到唯一的Value。

    常用的处理冲突的方法有:

    开放地址法(开放寻址法)

    就是当这个key通过哈希函数找到存放value地址的位置,当这个位置已经存放数据,就存在其紧跟着后面没有占用的位置,如果超过了最大长度,对最大长度取余,这里移动的地址是产生冲突的偏移量。

    再哈希法

    产生冲突后,用关键字其他部分继续计算地址,直到不产生冲突,当这种方法增加了时间。

    链地址法

    当产生冲突,把处于同一地址的数据做成一个链表,这种方法最常见。

    建立公共溢出区

    建立一个公共溢出区,把产生冲突的新地址放在这个公共溢出区里。

    散列表的特点:

    访问速度快

    由于散列表有散列函数,把Key映射到一个地址上,访问key对映的Value时候,不需要查找,直接跳到那个地址,因此,散列表的增删改查等任何操作,速度都比较快。

    需要额外的空间

    当发生冲突,需要额外的空间去存储,空间换时间,有舍才有得。

    无序

    为了快速找到访问的元素,根据散列函数直接找到存储地址,访问速度就快起来,但是有序访问没有办法实现。

    可能产生碰撞

    没有完美的散列函数,无论如何总会产生冲突,采用冲突的处理办法,就会使散列表变得更加复杂。


    下面展示如何实现一个散列表,这里用数组代替散列表元素(在在真实情况下,大多数语言的实现中,大多数元素都是一个特别的数组,每个元素对应一个地址),每个数组元素作为一个地址。

    public class Entry { int key; int value; Entry next; public Entry(int key, int value, Entry next) { super(); this.key = key; this.value = value; this.next = next; } } public class HashTable { /** * 默认散列表的初始长度 * 设置小一点,这样扩容的时候看的清楚 * 在实际使用中其实可以在初始化传参数,因为扩容是很消耗性能的 */ private static final int DEFAULT_INITIAL_CAPACITY = 4; /** * 扩容因子 */ private static final float LOAD_FACTOR = 0.75f; /** * 散列表数组 */ private Entry[] table = new Entry[DEFAULT_INITIAL_CAPACITY]; private int size = 0; private int use = 0; public void put(int key, int value) { int index = hash(key); if (table[index] == null) { table[index] = new Entry(-1, -1, null); } Entry e = table[index]; if (e.next == null) { //不存在的值,向链表中添加,有可能扩容,要用table属性 table[index].next = new Entry(key, value, null); size++; use++; //不存在的值,说明是个未用过的地址,需要判断是否扩容 if (use >= table.length * LOAD_FACTOR) { resize(); } }else { //本身存在的值,修改已有的值 for (e = e.next; e != null; e =e.next) { int k = e.key; if (k == key) { e.value = value; return; } } //不存在相同的值,直接向链表中添加元素 Entry temp = table[index].next; Entry newEntry = new Entry(key, value, temp); table[index].next = newEntry; size ++; } } /** * 删除 * @param key */ 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) { int index = hash(key); Entry e = table[index]; if (e != null && e.next != null) { for (int i = 0; i < table.length; i++) { 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; } /** * 本身散列表是不该有这个方法的,在这里只是为了清楚地看到确实扩容了。 * @return */ public int getLength() { return table.length; } /** * 根据key,通过哈希函数获取位于散列表数组中的哪个位置 * @param key * @return */ public int hash(int key) { return key % table.length; } /** * 扩容 */ public 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 entry = oldTable[i]; while (null != entry.next) { Entry next = entry.next; //重新计算哈希值,放入新的地址 int index = hash(next.key); if (table[index] == null) { use ++; table[index] = new Entry(-1, -1, null); } table[index].next = new Entry(next.key, next.value, table[index].next); //可以用下面三行代替 // Entry temp = table[index].next; // Entry newEntry = new Entry(next.key, next.value, temp); // table[index].next = newEntry; entry = next; } } } } } public class HashTableTest { public static void main(String[] args) { HashTable hashTable = new HashTable(); hashTable.put(1, 10); hashTable.put(2, 20); hashTable.put(5, 50);//和key为1的元素落在一个散列地址上了,实际使用长度为2 System.out.println(hashTable.getLength());//散列表长度为4 hashTable.put(3, 30);//总长度为4,加上该元素后长度就 大于等于3 了,所以扩容 System.out.println(hashTable.getLength());//散列表长为8 //在扩容后4个元素又分别落在不同的地址上 hashTable.put(6, 60);//使用第5 个地址 hashTable.put(7, 70);//使用第6个地址,为8的0.75倍,又需要扩容 System.out.println(hashTable.getLength());//散列表长度为16 System.out.println(hashTable.get(1));//10 System.out.println(hashTable.get(3));//30 System.out.println(hashTable.get(5));//50 System.out.println(hashTable.get(6));//60 } }
    转载请注明原文地址: https://ju.6miu.com/read-33020.html

    最新回复(0)