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

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

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

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

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

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

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

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

  2. 再哈希法

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

  3. 链地址法

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

  4. 建立公共溢出区

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

散列表的特点:

  1. 访问速度快

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

  2. 需要额外的空间

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

  3. 无序

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

  4. 可能产生碰撞

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


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

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
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值