Python 哈希查找及哈希表的实现

墙裂建议阅读 :Problem-Solving-with-Algorithms-and-Data-Structures-Using-Python 5.5. Hashing

为什么提出哈希查找

python中lis和array是常见的线性结构,创建数组的时候,内存开辟一块连续的,大小确定的空间用于存放数据。
再说说链表,由于链表包含了一个数据域和指针域,链表在内存中不需要连续,无论下一个节点在哪里,上一个节点总是携带下一个节点的位置。
回归正题,我们创建一个连续的内存且大小固定,往列表里面添加一个数使用append和下标索引的时间复杂度O(1)。然而使用查找的方式查询列表里是否含有某个值时,我们需要从头到尾查一遍,时间复杂度时O(n)。 当n的数量级很大时,这个查询时间时不可接受的。

能不能把查询的时间复杂度也做到O(1)?
答案是可以,使用哈希表。

一个简单的想法就是,当有一个值,能不能直接根据这个值得到这个值计算它应该存放的位置,然后再存放?
如果可以,那么我就能直接通过下表索引,实现O(1)的时间复杂度。数学上有一个概念叫做映射,我们能不能建立一种映射关系,将值和将要存放的位置关联起来。
假如我们使用余数法作为映射关系,即y = x % _list, len_list为需要存放的数据数量。有10个值,当一个值x = 2, 那么存放的位置y = 2%10 = 2.
这样我想要看看列表里面有没有2,只要通过上面的映射关系得到他的下标,取出来对比一下只有O(1)的时间复杂度。
其实这个所谓的映射关系专业的说法就是哈希函数,只不过这个函数不会像我举例说的那么简单。

hash函数

哈希函数在设计的时候需要考虑空间的问题。
另外在使用哈希映射时还会发生两个或者多个值映射同一个位置的冲突情况,这也是需要考虑的问题。
最后还要考虑哈希函数计算复杂度不能太高。
设计一个完美的哈希函数是不太可能的,但是原则是最大限度地减少冲突数,易于计算,并均匀分布在哈希表中的项。
常用的简单哈希函数:
(1)分组求和法
如电话号码 436-555-4601 ,每两位分一组得到43,65,55,46,01,求和 43 + 65 + 55 + 46 + 01 ,我们得到 210。假设哈希表有 11 个槽,除以 11 下, 210%11 为 1,因此 436-555-4601 散列到槽 1。
(2)平方取中法
如 44,先计算 44^2 = 1,936 ,取结果中间两个数字 93 ,假设哈希表有 11 个槽。得到 93%11=5 。

哈希查找

哈希表的一个重要特性就是查询的时间复杂度为O(1). 那么在需要查询的场合就排上用场了。
查重/去重

  1. 给定两个数列,判断两个数列中元素的差异。
    假设两个数列的长度为n,使用for-loop逐个比较,时间复杂度是O(n^2).
    使用哈希表这种数据结构,先遍历一个数列建立一个哈希表,时间复杂度O(n); 然后查询第二列中的每个元素是否在表中,时间复杂度为O(n),所以总时间为O(2n)。效率提高很多。
  2. 一个数列中是否存在重复元素
    思路与1)类似。

Python 哈希表的实现

代码摘自 http://interactivepython.org/courselib/static/pythonds/SortSearch/Hashing.html

class HashTable:
    def __init__(self):
        self.size = 11
        self.slots = [None] * self.size
        self.data = [None] * self.size

    def put(self, key, value):
        hashvalue = self.hashfunction(key, len(self.slots))
        if self.slots[hashvalue] is None:
            self.slots[hashvalue] = key
            self.data[hashvalue] = value
        else:
            if self.slots[hashvalue] == key:
                self.data[hashvalue] = value

            else:
                nextslot = self.rehash(hashvalue, len(self.slots))
                while self.slots[nextslot] is not None and self.slots[nextslot] != key:
                    nextslot = self.rehash(nextslot, len(self.slots))

                if self.slots[nextslot] is None:
                    self.slots[nextslot] = key
                    self.data[nextslot] = value
                else:
                    self.data[nextslot] = value

    def rehash(self, oldhash, size):
        return (oldhash + 1) % size

    def hashfunction(self, key, size):
        return key % size

    def get(self, key):
        startslot = self.hashfunction(key, len(self.slots))
        data = None
        found = False
        stop = False
        pos = startslot
        while pos is not None and not found and not stop:
            if self.slots[pos] == key:
                found = True
                data = self.data[pos]
            else:
                pos = self.rehash(pos, len(self.slots))
                # 回到了原点,表示找遍了没有找到
                if pos == startslot:
                    stop = True
        return data

    # 重载载 __getitem__ 和 __setitem__ 方法以允许使用 [] 访问
    def __getitem__(self, key):
        return self.get(key)

    def __setitem__(self, key, value):
        return self.put(key, value)


if __name__ == '__main__':
    H = HashTable()
    H[54] = "cat"
    H[26] = "dog"
    H[93] = "lion"
    H[17] = "tiger"
    H[77] = "bird"
    H[31] = "cow"
    H[44] = "goat"
    H[55] = "pig"
    H[20] = "chicken"

    print(H.slots)  # [77, 44, 55, 20, 26, 93, 17, None, None, 31, 54]
    print(H.data)  # ['bird', 'goat', 'pig', 'chicken', 'dog', 'lion', 'tiger', None, None, 'cow', 'cat']
    print(H[20])  # 'chicken'
    H[20] = 'duck'
    print(H[20])  # duck
    print(H[99])  # None

你可能感兴趣的:(数据结构和算法)