Hash值的理解和python实现

1、你可以把哈希值简单地理解成是一段数据(某个文件,或者是字符串)的DNA,或者身份证;

2、通过一定的哈希算法(典型的有MD5,SHA-1等),将一段较长的数据映射为较短小的数据,这段小数据就是大数据的哈希值。他有这样一个特点,他是唯一的,一旦大数据发生了变化,哪怕是一个微小的变化,他的哈希值也会发生变化。另外一方面,既然是DNA,那就保证了没有两个数据的哈希值是完全相同的。

3、正是因为这样的特点,它常常用来判断两个文件是否相同。比如,从网络上下载某个文件,只要把这个文件原来的哈希值同下载后得到的文件的哈希值进行对比,如果相同,则表示两个文件完全一致,下载过程没有损坏文件。而如果不一致,则表明下载得到的文件跟原来的文件不同,文件在下载过程中受到了损坏。

 

python 

 

参考翻译自:《复杂性思考》 及对应的online版本:http://greenteapress.com/complexity/html/thinkcomplexity004.html

使用哈希表可以进行非常快速的查找操作,查找时间为常数,同时不需要元素排列有序

python的内建数据类型:字典,就是用哈希表实现的

 

为了解释哈希表的工作原理,我们来尝试在不使用字典的情况下实现哈希表结构。

我们需要定义一个包含 键->值 映射 的数据结构,同时实现以下两种操作:

add(k, v):

  Add a new item that maps from key k to value v.

  With a Python dictionary,d, this operation is written d[k] = v.

get(target)

       Look up and return the value that corresponds to key target.

  With a Python dictionary, d, this operation is written d[target] or d.get(target).

一种简单是实现方法是建立一个线性表,使用元组来实现 key-value 的映射关系

class LinearMap(object):
    """ 线性表结构 """
    def __init__(self):
        self.items = []
    
    def add(self, k, v):    # 往表中添加元素
        self.items.append((k,v))
    
    def get(self, k):       # 线性方式查找元素
        for key, val in self.items:
            if key==k:      # 键存在,返回值,否则抛出异常
                return val
        raise KeyError

我们可以在使用add添加元素时让items列表保持有序,而在使用get时采取二分查找方式,时间复杂度为O(log n)。 然而往列表中插入一个新元素实际上是一个线性操作,所以这种方法并非最好的方法。同时,我们仍然没有达到常数查找时间的要求。

 

我们可以做以下改进,将总查询表分割为若干段较小的列表,比如100个子段。通过hash函数求出某个键的哈希值,再通过计算,得到往哪个子段中添加或查找。相对于从头开始搜索列表,时间会极大的缩短。尽管get操作的增长依然是线性,但BetterMap类使得我们离哈希表更近一步:

class BetterMap(object):
    """ 利用LinearMap对象作为子表,建立更快的查询表 """
    def __init__(self,n=100):
        self.maps = []          # 总表格
        for i in range(n):      # 根据n的大小建立n个空的子表
            self.maps.append(LinearMap())
    
    def find_map(self,k):       # 通过hash函数计算索引值
        index = hash(k) % len(self.maps)
        return self.maps[index] # 返回索引子表的引用     

    # 寻找合适的子表(linearMap对象),进行添加和查找
    def add(self, k, v):
        m = self.find_map(k)        
        m.add(k,v)
    
    def get(self, k):
        m = self.find_map(k)
        return m.get(k)

测试

if __name__=="__main__":
    table = BetterMap()
    pricedata = [("Hohner257",257),
                 ("SW1664",280),
                 ("SCX64",1090),
                 ("SCX48",830),
                 ("Super64",2238),
                 ("CX12",1130),
                 ("Hohner270",620),
                 ("F64C",9720),
                 ("S48",1988)]
    
    for item, price in pricedata:
        table.add(k=item, v=price)
    
    print table.get("CX12")
    # >>> 1130
    print table.get("QIMEI1248")
    # >>> raise KeyError

由于每个键的hash值必然不同,所以对hash值取余的值基本也是不同的。

当n=100时, BetterMap的查找速度大约是LinearMap的100倍。

 

明显,BetterMap的查找速度受到参数n的限制,同时其中每个LinearMap的长度不固定,使得子段中的元素依然是线性查找。如果,我们能够限制每个子段的最大长度,这样在单个子段中查找的时间负责度就有一个固定上限,则LinearMap.get方法的时间复杂度就成为了一个常数。由此,我们仅仅需要跟踪元素的数量,每当某个LinearMap中的元素数量超过阈值时, 对整个hashtable进行重排,同时增加更多的LinearMap,这样子就可以保证查找操作为一个常数啦。

以下是hashtable的实现:

class HashMap(object):
    def __init__(self):
        # 初始化总表为,容量为2的表格(含两个子表)
        self.maps = BetterMap(2)
        self.num = 0        # 表中数据个数
    
    def get(self,k):        
        return self.maps.get(k)
    
    def add(self, k, v):
        # 若当前元素数量达到临界值(子表总数)时,进行重排操作
        # 对总表进行扩张,增加子表的个数为当前元素个数的两倍!
        if self.num == len(self.maps.maps): 
            self.resize()
        
        # 往重排过后的 self.map 添加新的元素
        self.maps.add(k, v)
        self.num += 1
        
    def resize(self):
        """ 重排操作,添加新表, 注意重排需要线性的时间 """
        # 先建立一个新的表,子表数 = 2 * 元素个数
        new_maps = BetterMap(self.num * 2)
        
        for m in self.maps.maps:  # 检索每个旧的子表
            for k,v in m.items:   # 将子表的元素复制到新子表
                new_maps.add(k, v)
        
        self.maps = new_maps      # 令当前的表为新表

重点关注 add 部分,该函数检查元素个数与BetterMap的大小,如果相等,则“平均每个LinearMap中的元素个数为1”,然后调用resize方法。

resize创建一个新表,大小为原来的两倍,然后对旧表中的元素“rehashes 再哈希”一 遍,放到新表中。

resize过程是线性的,听起来好像很不怎么好,因为我们要求的hashtable具有常数时间。但是,要知道我们并不需要经常进行重排操作,所以add操作在绝大部分时间中都是常数的,偶然出现线性。由于对n个元素进行add操作的总时间与n成比例,所以每次add的平均时间就是一个常数!

假设我们要添加32个元素,过程如下:

1. 由于初始长度为2,前两次add不需要重排,第1,2次 总时间为 2

2. 第3次add,重排为4,耗时2,第3次时间为 3

3. 第4次add,耗时1    到目前为止,总时间为 6

4. 第5次add,重排为 8,耗时4,第5次时间为5

5. 第6~8次   共耗时3      到目前为止,总时间为 6+5+3 = 14

6. 第9次add,重排16,  耗时8,第9次时间为9

7. 第10~16次,共耗时7, 到目前为止,总时间为 14+9+7 = 30

在32次add后,总时间为62的单位时间,由以上过程可以发现一个规律,在n个元素add之后,当n为2的幂,则当前总单位时间为 2n-2,所以平均add时间绝对小于2单位时间。

当n为2的幂时,为最合适的数量,当n变大之后,平均时间为稍微上升,但重要的是,我们达到了O(1)。

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Staking是一种共识算法,类似于Proof of Work(PoW)和Proof of Stake(PoS)等。在Staking中,节点通过锁定一定数量的代币(类似于PoS的抵押物)来获得记账权,从而参与到链上的交易验证和打包中。以下是一个简单的Python实现: ``` import hashlib import random class Block: def __init__(self, data, previous_block_hash): self.data = data self.previous_block_hash = previous_block_hash self.nonce = None self.hash = self.calculate_hash() def calculate_hash(self): sha = hashlib.sha256() sha.update(str(self.data).encode('utf-8') + str(self.previous_block_hash).encode('utf-8') + str(self.nonce).encode('utf-8')) return sha.hexdigest() def mine_block(self, difficulty): target = '0' * difficulty while self.hash[:difficulty] != target: self.nonce = random.randint(0, 1000000) self.hash = self.calculate_hash() print(f'Block mined: {self.hash}') class Blockchain: def __init__(self): self.chain = [self.create_genesis_block()] self.difficulty = 4 def create_genesis_block(self): return Block('Genesis Block', '0') def get_latest_block(self): return self.chain[-1] def add_block(self, new_block): new_block.previous_block_hash = self.get_latest_block().hash new_block.mine_block(self.difficulty) self.chain.append(new_block) def is_chain_valid(self): for i in range(1, len(self.chain)): current_block = self.chain[i] previous_block = self.chain[i - 1] if current_block.hash != current_block.calculate_hash(): return False if current_block.previous_block_hash != previous_block.hash: return False return True blockchain = Blockchain() blockchain.add_block(Block('Block 1', '')) blockchain.add_block(Block('Block 2', '')) blockchain.add_block(Block('Block 3', '')) blockchain.add_block(Block('Block 4', '')) ``` 在这个简单的示例中,我们实现了一个基本的区块链和挖矿算法。我们使用SHA-256哈希算法来计算区块的哈希,并使用随机数(称为nonce)来增加哈希的难度。我们还使用difficulty变量来控制挖矿的难度,它表示哈希必须以多少个0位开头才能被接受。在实际使用中,Staking算法可能会更加复杂,但这个示例可以帮助你理解Staking的基本原理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值