ch8 - Hash哈希表

支持的操作:O(1)的插入,O(1)的查找,O(1)的删除
java中hash table(线程安全,有加锁机制)、hash map(线程不安全)、hash set(只有key,没有value)的区别

目录:

  • 1.hash function - (常用hash函数、open hash解决冲突需要掌握)
    1.2 hash-function (128 in lintcode)
    1.3 strstr-ii 字符串查找 (594 in lintcode) (★★★★★)
  • 2.Rehashing
    2.2 rehashing (129 in lintcode) (★★★★★)
    2.3 LRU-cache LRU缓存策略 (134 in lintcode) (★★★★★)
    2.3 其他相关题目

1. Hash Function

1). 根据hash function,计算出key对应的下标,然后下标访问数组的复杂度是O(1).
2). key space << hash 数组的大小,最好差距在10倍以上
3). 一些著名的hash算法: MD5、SHA-1、SHA-2,用于加密,不是用在hash 表中的,这些复杂度太高了。
4). 常用的hash函数:
 a*31^2 + b * 31 +c* 31^0

在这里插入图片描述
a. 取模运算对加减乘除的次序没有影响。为了避免溢出,边乘边取模,不能直接用pow函数。

b. 31是经验值,其他也可以,但是乘31效果比较好。选择质数会更好,数字太大,会影响计算速度,数字太小,冲突太多。像Apache的底层库中,用的是33.

5). 整数或者double型的数怎么处理:将每个字节看作一个字符,如整数是4个字节,则看作是4个字符处理
6). 解决hash冲突:再好的hash函数也会存在冲突(collision)

**2种解决方案:**Open Hashing vs Closed Hashing(两个链接有动画)
https://www.cs.usfca.edu/~galles/visualization/ClosedHash.html
https://www.cs.usfca.edu/~galles/visualization/OpenHash.html

A. closed Hash - 占坑法
在这里插入图片描述
删除时:
在这里插入图片描述
B. open hash - 拉链法(较常用)- 实现方法:hash 函数+基本的链表操作

每个数组位置是一个链表的头。
在这里插入图片描述

1.2 hash-function (128 in lintcode)

1).题目

http://www.lintcode.com/zh-cn/problem/hash-function/
http://www.jiuzhang.com/solution/hash-function/

在数据结构中,哈希函数是用来将一个字符串(或任何其他类型)转化为小于哈希表大小且大于等于零的整数。一个好的哈希函数可以尽可能少地产生冲突。一种广泛使用的哈希函数算法是使用数值33,假设任何字符串都是基于33的一个大整数,比如:

hashcode("abcd") = (ascii(a) * 333 + ascii(b) * 332 + ascii(c) *33 + ascii(d)) % HASH_SIZE 
                              = (97* 333 + 98 * 332 + 99 * 33 +100) % HASH_SIZE
                              = 3595978 % HASH_SIZE

其中HASH_SIZE表示哈希表的大小(可以假设一个哈希表就是一个索引0 ~ HASH_SIZE-1的数组)。
给出一个字符串作为key和一个哈希表的大小,返回这个字符串的哈希值。

2).代码
class Solution {
public:
    /*
     * @param key: A string you should hash
     * @param HASH_SIZE: An integer
     * @return: An integer
     */
    int hashCode(string &key, int HASH_SIZE) {
        // write your code here
        long res = 0;
        for(int i=0;i<key.size();++i){
            res = (res *33 % HASH_SIZE + key[i]) % HASH_SIZE;
        }
        return res;
    }
};

1.3 strstr-ii 字符串查找 (594 in lintcode) (★★★★★)

1).题目

http://www.lintcode.com/zh-cn/problem/strstr-ii/
http://www.jiuzhang.com/solution/strstr-ii/

实现时间复杂度为 O(n + m)的方法 strStr。
strStr 返回目标字串在源字串中第一次出现的第一个字符的位置. 目标字串的长度为 m , 源字串的长度为 n . 如果目标字串不在源字串中则返回 -1。

2).代码
class Solution {
public:
    const int BASE = 1000000;

    /*
     * @param source: A source string
     * @param target: A target string
     * @return: An integer as index
     */
    int strStr2(const char* source, const char* target) {
        // write your code here
        if(source == NULL || target == NULL){
            return -1;
        }
        int m = strlen(target);
        int n = strlen(source);
        if(m==0){
            return 0;
        }

        //compute 31^m
        int power=1;
        for(int i=0;i<m;++i){
            power = power * 31 % BASE; 
        }

        //hashCode of target
        int targetCode = 0;
        for(int i=0;i<m;++i){
            targetCode = (targetCode * 31 % BASE + target[i]) % BASE; //直接赋值,不是相加
        }

        //hashCode of source
        int hashCode = 0;
        for(int i=0;i<n;++i){                                                                                                                                                 
            //abc+d
            hashCode = (hashCode * 31 % BASE + source[i]) % BASE;

            //abcd-a
            if(i>=m){
                hashCode -= source[i-m] * power % BASE;
                if(hashCode < 0){
                    hashCode += BASE;
                }
            }

            //判断
            if(i>=m-1 && hashCode == targetCode){
                char tmp[m]; //为什么是m
                memcpy(tmp,&source[i-m+1], m);
                tmp[m] = '\0';
                if(strcmp(tmp,target)==0){
                    return i-m+1;
                }
            }
        }

        return -1;
    }
};

2. Rehashing

2.1.hash表的饱和度

饱和度 = 实际存储元素个数 / 总共开辟的空间大小 = size / capacity

一般来说,超过1/10(经验值)的时候,说明需要进行rehashing

不是原有的数组被填满了才是不够,如有100个位置的数组,已经放了10个数,那么就认为已经满了

2.2 Rehashing - 129 in lintcode

1). 题目

http://www.lintcode.com/problem/rehashing/
http://www.jiuzhang.com/solutions/rehashing/

哈希表容量的大小在一开始是不确定的。如果哈希表存储的元素太多(如超过容量的十分之一),我们应该将哈希表容量扩大一倍,并将所有的哈希值重新安排。假设你有如下一哈希表:
size=3, capacity=4

[null, 21, 14, null]
        ↓    ↓
        9   null
        ↓
       null

哈希函数为:

int hashcode(int key, int capacity) {
     return key % capacity;
 }

这里有三个数字9,14,21,其中21和9共享同一个位置因为它们有相同的哈希值1(21 % 4 = 9 % 4 = 1)。我们将它们存储在同一个链表中。

重建哈希表,将容量扩大一倍,我们将会得到:
size=3, capacity=8

index:   0    1    2    3     4    5    6   7
hash : [null, 9, null, null, null, 21, 14, null]

给定一个哈希表,返回重哈希后的哈希表。

注意事项
哈希表中负整数的下标位置可以通过下列方式计算:

  • C++/Java:如果你直接计算-4 % 3,你会得到-1,你可以应用函数:a % b = (a % b + b) % b得到一个非负整数。
  • Python:你可以直接用-1 % 3,你可以自动得到2。
2) 代码
/**
 * Definition of ListNode
 * class ListNode {
 * public:
 *     int val;
 *     ListNode *next;
 *     ListNode(int val) {
 *         this->val = val;
 *         this->next = NULL;
 *     }
 * }
 */
class Solution {
public:
    /**
     * @param hashTable: A list of The first node of linked list
     * @return: A list of The first node of linked list which have twice size
     */   
    vector<ListNode*> rehashing(vector<ListNode*> hashTable) {
        // write your code here
        if(hashTable.size()==0){
            return hashTable;
        }

        int cap = hashTable.size();
        int newCap = cap * 2;
        vector<ListNode*> resTable(newCap, NULL);
        cout<<resTable.size()<<endl;

        for(int i=0;i < hashTable.size();++i){
            ListNode* head = hashTable[i];
            while(head){
                cout<<i<<endl;
                int val = head->val;
                int newval = (val % newCap + newCap) % newCap;
                if(!resTable[newval]){
                    resTable[newval] = new ListNode(val);
                }
                else{
                    ListNode* cur = resTable[newval];
                    while(cur->next){
                        cur = cur->next;
                    }
                    cur->next = new ListNode(val);
                }
                head = head->next;
            }
        }
        return resTable;
    }
};

2.3 LRU-cache LRU缓存策略 - 134 in lintcode (★★★★★)

1)题目

http://www.lintcode.com/problem/lru-cache/
http://www.jiuzhang.com/solutions/lru-cache/
Example: [2 1 3 2 5 3 6 7]

为最近最少使用(LRU)缓存策略设计一个数据结构,它应该支持以下操作:获取数据(get)和写入数据(set)。

获取数据get(key):如果缓存中存在key,则获取其数据值(通常是正数),否则返回-1。
写入数据set(key, value):如果key还没有在缓存中,则写入其数据值。当缓存达到上限,它应该在写入新数据之前删除最近最少使用的数据用来腾出空闲位置。

2)思路 & 代码:

删除最近最少使用的,即当缓存达到上限之后,需要删除使用时间离现在最远的元素。
在这里插入图片描述
所以需要支持中间删除、头部删除,以及尾部追加,适合用LinkedList来实现。
在这里插入图片描述
所以需要 hash表 + LInkedList。在java中该数据结构叫做LinkedHashMap。单双向链表都可以实现,单向链表存上一个节点。

LinkedHashMap = DoublyLinkedList + HashMap

 HashMap<key, DoublyListNode> DoublyListNode {
          prev, next, key, value;
      }

Newest node append to tail.
Eldest node remove from head.

单向链表:
Singly List 是否可行?

  • 可以,在 Hash 中存储 Singly List 中的 prev node 即可
    如 linked list = dummy->1->2->3->null 时
    hash[1] = dummy, hash[2] = node1
class keyValueNode{
public:
    int key,val;
    keyValueNode* next; //存当前结点的前一个节点
    keyValueNode(int _key,int _val){
        key = _key;
        val = _val;
        next = NULL;
    }
    keyValueNode(){
        key = 0;
        val = 0;
        next = NULL;
    }
};

class LRUCache {
private:
    void moveToTail(keyValueNode * prev){
        if(prev->next == tail){
            return;
        }

        keyValueNode* node = prev->next;
        prev->next = node->next;
        if(node->next != NULL){
            hash[node->next->key] = prev;
        }
        tail->next = node;
        node->next = NULL;
        hash[node->key] = tail;
        tail = node;
    }
public:
    unordered_map<int, keyValueNode*> hash;
    keyValueNode* head,*tail;
    int capacity,size;
    /*
    * @param capacity: An integer
    */LRUCache(int capacity) {
        // do intialization if necessary
        this->head = new keyValueNode(0,0);
        this->tail = head;
        this->capacity = capacity;
        this->size = 0;
        hash.clear();
    }

    /*
     * @param key: An integer
     * @return: An integer
     */
    int get(int key) {
        // write your code here
        if(hash.find(key) == hash.end()){
            return -1;
        }

        moveToTail(hash[key]);
        return hash[key]->next->val;
    }

    /*
     * @param key: An integer
     * @param value: An integer
     * @return: nothing
     */
    void set(int key, int value) {
        // write your code here
        if(hash.find(key)!=hash.end()){
            hash[key]->next->val = value;
            moveToTail(hash[key]);
        }
        else{
            keyValueNode * node = new keyValueNode(key,value);
            tail->next = node;
            hash[key] = tail;
            tail = node;
            size++;
            if(size > capacity){
                hash.erase(head->next->key);
                head->next = head->next->next;
                if(head->next!=NULL){
                    hash[head->next->key] = head;
                }
                size--;
            }
        }
    }
};

2.4 相关题目

http://www.lintcode.com/problem/subarray-sum/
http://www.lintcode.com/problem/copy-list-with-random-pointer/
http://www.lintcode.com/problem/anagrams/
http://www.lintcode.com/problem/longest-consecutive-sequence/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值