哈希查找算法

一、算法分析基础

算法名称  时间复杂度     空间复杂度(序列)
顺序      O(N)

黄金      O(log2N)

二分      O(logN) 
        
插补      O(log (logN) )

常规查找算法中,插补是极限,但并不是说没有更好的查找算法了。


顺序查找是一个一个往下找,当总量大的时候,我们要找的目标target又靠后的时候,负担就很重 --- 大海捞针。
散列表:散列表是一种牺牲空间换取时间的存储结构,也是一种提升效率的常用的方法---算力(片面的理解为计算空间有多大)
    散列表使用技巧:“效率” 和 “占用空间大小” 二者权衡。

dict = {key1:value1,key2:value2.....}

中---> z ---> z开头一大堆 --->h.....
举例:通讯录找人,第一步找z,赵、张--->肉眼去看“张”这个字 ---->点开“张三”这个人,然后你就看到了他的电话号码。

0     1      2      3      4      5      6      7  ...
v1    v2     v3     v4     v5     v6     v7     v8 ...

data = [ 12 ,45, 56 ,66 ,77, 80 ,97 ,101 ,120, 101]
在普通查找算法中我们看到的就是值本身,而哈希表中我们看到的不是值本身,只是一个值的标记。
散列函数又称为哈希函数,是给定一个x(包裹本身),它都会相应的输出H(x) (那个编码)。

x--->H(x)的过程我们需要保证以下这么几点:
    ①输入的x是任意的字符串。
    ②输出结果即H(x)的长度是固定的。
    ③计算H(x)的过程是高效的。
    ④尽可能输入一个x就能得到唯一的H(x)。

For example:
    数据集:alist = [77 ,26 , 93 , 17 , 31 , 54]中的每个数据都通过散列函数计算出各个索引值,计算过程如下:
    x--->H(x)的过程: 
    
    "77" ---> H(x) = x % 6 = 5
    "26" ---> H(x) = x % 6 = 2
    ....
    “x”   --->H(x) = 5

    
    此时,由5,2...构成的序列就是哈希表的值,也就是替换v1....vn
    0     1      2      3      4      5      6      7  ...
    v1    v2     v3     v4     v5     v6     v7     v8 ...

    由于x到H(x)的方法不同就会产生不同的哈希算法:
    ①开放定址法----线性探测法
    ②开放定址法----平方探测法
    ③开放定址法----伪随机探测法
    ④链地址法
    ⑤再散列函数法
    ⑥建立公共溢出区

    1.开放定址法的概念:
        开放定址法:当数据求出来的H(x)冲突的时候怎么办的问题?
        ① 线性探测法--->假设散列表足够大,如果遇到冲突,那么就找下一个空的地址。
        “张三” ....新增“张三” * 3 ,
        2 ... 有空位就让第二张三坐下,再往下,第三个张三坐下.....
         key:  0     1      2      3      4      5      6      7  ...11.....101
         value:5    v2     v3     v4     v5     v6     v7     v8 ... 5......5
         
    2.开放定址法的公式:
        H(i) = (H(key) + d(i)) MOD m
        公式参数解释:
            key:要存储的数据
            H(key):散列函数
            m:散列表长度
            d(i):增长序列
            MOD: %
        研究d(i)的获取方法:
        (1): d(i) = 1,2,3,4,....,m-1,这种取法称为线性探测法。
            线性探测法的缺点是:相类似的值变多以后,会出现聚集的情况,有聚合就有冲突。
            eg: 26 36 41 37 55,将这些数据放到数据长度为11的散列表中,步骤如下:
                step 1:确定散列函数
                    H(i) = (H(key) + d(i)) MOD m
                    H(i) = (H(key) + 1) MOD 11 -->意思就是先去放1号位。
                step 2:计算出H(key)的值,求法:数据集中的每个元素除以11取余数。
                    H(key) : 4 3 8 4 0 前提是:0 1 2 3 4 5 6 7 8 9 10
                step 3:将数据37求得的索引值4带入step1的散列函数中去,
                    求得:H(i) = (4 + 1) % 11 = 5

                fina-data:
                    0   1    2    3   4   5   6    7    8   9    10
                    55  None None 36  26  37  None None 41  None None
                
        (2): d(i) = 1²,2²,3²,4²,....,或者d(i) = -1²,-2²,-3²,-4².....,这种取法称为平方探测法。
            为了防止线性探测法出现大规模的数据聚集而产生冲突,所以改良后出现了平方探测法。
            平方探测法,又称为“二次探测法”,d(i)部分与线性探测法不同。
            eg:有一组这样的数据:26 36 41 37 55。使用平方探测法d(i) = 4²时,将这组数据放在长度为11的散列表中,步骤如下:
            step 1:确定散列函数
                H(i) = (H(key) + 4²) MOD 11
            step 2:计算H(key)的值,将这个散列表中的每个数据除以11取余数,得到的索引值如下:
                4 3 8 4 0
            setp 3:将37求得的索引值4带入step1,得到的H(i) = (4 + 4²) % 11 = 9
            结论是:将37放入索引值为9的位置。
            fina-data:
                    0   1    2    3   4   5     6    7    8   9    10
                    55  None None 36  26  None  None None 41  37   None
            练习:
                26 36 41 37 55 26 36 41 37 55
                要求1:使用线性探测法将数据放好。
                fina-data:
                    0   1    2    3   4   5     6    7    8   9    10   11   12   13   14   15   16 
                    55  None None 36  26  None  None None 41  37   None
                要求2:使用平方探测法将数据放好。
                fina-data:
                    0   1    2    3   4   5     6    7    8   9    10   11   12   13   14   15   16 
                    55  None None 36  26  None  None None 41  37   None
        (3): d(i) = 伪随机数序列,这种方法称为伪随机探测法。
            不好拿捏。
            
    effect: 假设真的有一种问题,是线性探测法解决不了的,我们使用平方探测法解决,使用线性+平方,最后都解决不了在使用伪随机探测。

    3.总结
        ①数据线性跨度大、重复数据少、集合(排重性),建议使用线性探测法。
        ②数据量够大的时候,空间浪费足够小,因为d(i)是我们自定义的,平方数越大索引计算出来的位置间隔越大,产生数据冲突的可能性越小,同时也就会留下更多的空隙,那就是牺牲了一部分空间来换取了很小的冲突,说白了就是牺牲空间换取时间的做法。建议使用平方探测法。

二、哈希算法应用举例

以下是几个关于哈希查找算法的编程题目:

    1.实现一个简单的哈希表
        设计并实现一个简单的哈希表,包括插入、查找和删除操作。
        哈希函数可以使用简单的取模运算。
        处理哈希冲突时,可以采用链地址法或开放地址法。
# 1. 实现一个简单的哈希表
# SimpleHashTable类:不存在
class SimpleHashTable:  
    # 构造方法
    # self--->SimpleHashTable
    # capacity = 10 #创造了就一直存在的
    # self.capacity = 10
    # capacity只要没实例化对象,都一直存在的
    def __init__(self, capacity=10):  # 天生
        # 将天生的capacity属性给了类的属性capacity,
        # 反过来说SimpleHashTable就天生具有了capacity这个属性
        self.capacity = capacity  
        # 说明SimpleHashTable天生就是个表
        self.table = [[] for _ in range(capacity)]  
        # [[], [], [], [], [], [], [], [], [], []]
        '''
        [
            [], 
            [], 
            [], [], [], [], [], [], [], []
        ]
        '''
        # for _ in range(10): [[0] [1] [2] [3] [4] [5]]
        print(self.table)
  
    def hash_function(self, key):  
        return key % self.capacity  

    # 往hashTable中插入索引和值
    def insert(self, key, value):  
        index = self.hash_function(key)  
        for item in self.table[index]:  
            if item[0] == key:  
                item[1] = value  
                return  
        self.table[index].append([key, value])  
        print("插入完毕后的哈希表:",self.table)
  
    def search(self, key):  
        index = self.hash_function(key)  
        for item in self.table[index]:  
            if item[0] == key:  
                return item[1]  
        return None  
  
    def delete(self, key):  
        index = self.hash_function(key)  
        for i, item in enumerate(self.table[index]):  
            if item[0] == key:  
                del self.table[index][i]  
                return True  
        return False

sht = SimpleHashTable()
sht.insert(8,"水警")
sht.insert(3,"火警")
# search(3)就是哈希查找算法,它的时间复杂度是O(1). 插补算法的平均时间复杂度是多少:O(Log(logN))
returnValue = sht.search(3)
print("返回的值是:",returnValue)
    2.实现LRU缓存机制
        利用哈希表和双向链表实现一个LRU(最近最少使用)缓存机制。
        需要实现获取数据get和写入数据put两个操作。
        当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,并为新的数据值留出空间。
# 2. 实现LRU缓存机制
class LRUCache:  
    # 写个计算器,只能进行整数加减
    # def __init__(self, capacity: float): 10 int("10")
    def __init__(self, capacity: int):   # capacity: type
        self.capacity = capacity  
        self.cache = {}  
        self.keys = []  
  
    def get(self, key: int) -> int:  
        if key in self.cache:  
            self.keys.remove(key)  
            self.keys.append(key)  
            return self.cache[key]  
        return -1  
    # 作用:将新的数据塞入电池
    def put(self, key: int, value: int) -> None:  
        if key in self.cache:  # 字典中不允许有重复的键
            self.keys.remove(key)  # ?字典中不允许有重复的键
        elif len(self.cache) >= self.capacity:  # 电池装不下了怎么办?删掉占着茅坑不拉屎的数据
            oldest_key = self.keys.pop(0) # 为了找到那些是占着茅坑的  # pop(按下标删0)
            del self.cache[oldest_key]  # 删除这些值
        self.cache[key] = value  
        self.keys.append(key)

#  实例化---将类变成对象的过程---等同于---执行调用了__init__()【初始化】
# lr = LRUCache() 等同于 LRUCache.__init__()
# c1 = 10
# c2 = 100
# lr1 = LRUCache(c1) # capacity = 10
# lr2 = LRUCache(c2) # capacity = 100
# 请问lr1.put() 是否等于 lr2.put() ? 答案:相等的
# 用手摸头function

#这个过程:将类变成对象的过程---实例化:生了一个孩子,“ac”


class Car:
    # 初始化
    def __init__(self):
        pass 

# lr2 = LRUCache(10) 
# LRUCache是类,lr和lr2是对象,不能仅仅按照值是否相等来进行判断是否是同一个对象
# lr.get() == LRUCache.get()
# lr2.get() == LRUCache.get()
# lr.get() != lr2.get()

# n1 = lr.get() #等同 LRUCache.get() 
# n2 = lr2.get() #是不是等同于 LRUCache.get()

# lr.get() !=  lr2.get() 




# lr.get() 等同于 LRUCache.get() 
# LRUCache.get()
# LRUCache.__init__()
# myCar = Car()
    3.哈希表的应用:两数之和
        给定一个整数数组nums和一个目标值target,请你在该数组中找出和为目标值的那两个整数,并返回它们的数组下标。
        你可以假设每种输入只会对应一个答案,但是,数组中同一个元素不能使用两遍。
        有两个值x、y,他们的和x + y == target,返回x和y在数组中的下标。
        提示:可以使用哈希表来优化查找过程。
# 3. 哈希表的应用:两数之和
def two_sum(nums, target):  
    hash_table = {}  # 问hash_table是什么类型?
    for i, num in enumerate(nums):  
        complement = target - num  
        if complement in hash_table:  
            return [hash_table[complement], i]  
        hash_table[num] = i  
    return []
    4.哈希表的应用:快乐数
        编写一个算法来判断一个数 n 是不是快乐数。
        「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。
        提示:使用哈希表来检测循环。
# 4. 哈希表的应用:快乐数
def is_happy(n):  
    def get_next(number):  
        total_sum = 0  
        while number > 0:  
            number, digit = divmod(number, 10)  
            total_sum += digit ** 2  
        return total_sum  
  
    seen = set()  
    while n != 1 and n not in seen:  
        seen.add(n)  
        n = get_next(n)  
    return n == 1
    5.设计哈希集合
        不使用任何内建的哈希表库设计一个哈希集合[表逻辑](HashSet)。
        实现MyHashSet类:
            void add(key) 向哈希集合中插入值 key。
            bool contains(key) 返回哈希集合中是否存在这个值 key。
            void remove(key) 将给定值 key 从哈希集合中删除。如果哈希集合中没有这个值,什么也不做。
# 5. 设计哈希集合
class MyHashSet:  
    def __init__(self):  
        self.size = 1000  
        self.buckets = [[] for _ in range(self.size)]  
  
    def hash(self, key):  
        return key % self.size  
  
    def add(self, key):  
        if not self.contains(key):  
            self.buckets[self.hash(key)].append(key)  
  
    def remove(self, key):  
        bucket = self.buckets[self.hash(key)]  
        if key in bucket:  
            bucket.remove(key)  
  
    def contains(self, key):  
        bucket = self.buckets[self.hash(key)]  
        return key in bucket

哈希查找算法是一种高效的查找算法,它通过将关键字映射到哈希表中的位置来实现查找。下面是一个简单的Python实现: ```python # 定义哈希表类 class HashTable: def __init__(self): self.size = 11 self.slots = [None] * self.size self.data = [None] * self.size # 哈希函数 def hashfunction(self, key, size): return key % size # 插入数据 def put(self, key, data): hashvalue = self.hashfunction(key, len(self.slots)) if self.slots[hashvalue] == None: self.slots[hashvalue] = key self.data[hashvalue] = data else: if self.slots[hashvalue] == key: self.data[hashvalue] = data # 更新数据 else: nextslot = self.rehash(hashvalue, len(self.slots)) while self.slots[nextslot] != None and self.slots[nextslot] != key: nextslot = self.rehash(nextslot, len(self.slots)) if self.slots[nextslot] == None: self.slots[nextslot] = key self.data[nextslot] = data else: self.data[nextslot] = data # 更新数据 # 查找数据 def get(self, key): startslot = self.hashfunction(key, len(self.slots)) data = None stop = False found = False position = startslot while self.slots[position] != None and not found and not stop: if self.slots[position] == key: found = True data = self.data[position] else: position = self.rehash(position, len(self.slots)) if position == startslot: stop = True return data # 重新哈希 def rehash(self, oldhash, size): return (oldhash + 1) % size ``` 上述代码中,我们定义了一个哈希表类`HashTable`,并实现了哈希函数`hashfunction`、插入数据方法`put`、查找数据方法`get`和重新哈希方法`rehash`。我们可以通过以下代码来测试这个哈希表类: ```python H = HashTable() H.put(54, "cat") H.put(26, "dog") H.put(93, "lion") H.put(17, "tiger") H.put(77, "bird") H.put(31, "cow") H.put(44, "goat") H.put(55, "pig") H.put(20, "chicken") print(H.slots) print(H.data) print(H.get(20)) ``` 输出结果为: ``` [77, 44, 55, 20, 26, 93, None, None, 17, None, 31] ['bird', 'goat', 'pig', 'chicken', 'dog', 'lion', None, None, 'tiger', None, 'cow'] chicken ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

灰灰老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值