數據結構——線性結構(哈希表)

1、直接尋址表

  若是某應用要用到一個動態集合,其中每一個元素都是全域U={0,1….,m}中的一個關鍵字 爲表示動態集合,使用數組。稱爲直接尋址表,記爲T[m],其中每一個位置稱爲一個槽slot,對應於全域中的一個關鍵字。槽k指向集合中一個關鍵字爲k的元素。若是該集合中沒有關鍵字爲k的元素,則T[k]=NIL; node

  

一、直接尋址技術優勢

  當關鍵字的全域U比較小時,直接尋址是一種簡單而有效的方法。python

二、直接尋址技術缺點

  當域U很大時,須要消耗大量內存,很不實際;算法

  若是域U很大而實際出現的key不多,則大量空間被浪費;數組

  沒法處理關鍵字不是數字的狀況。安全

三、將直接尋址表改進爲哈希表

  直接尋址表:key爲k的元素放到k的位置上。服務器

  改進方法:app

  (1)構建大小爲m的尋址表T;函數

  (2)key爲k的元素放到h(k)位置上;spa

  (3)h(k)是一個函數,其將域U映射到表T[0,1,...,m-1]。對象

2、哈希表

  在直接尋址表上加了一個哈希函數就成了哈希表。

  哈希表(Hash Table,又稱爲散列表),是一種線性表的存儲結構。哈希表由一個直接尋址表和一個哈希函數組成。哈希函數h(k)將k做爲自變量,返回元素的存儲下標。

  假設有一個長度爲7的哈希表,哈希函數h(k)=k%7。元素集合{14,22,3,5}的存儲方式以下圖:

  

  14%7=0,所以14存在index=0的位置;22%7=1,所以22存在index=1的位置;3%7=3,所以3存在index=3的位置;5%7=5,所以5存在index=5的位置。

一、常見哈希函數

  • 除法哈希法:h(k) = k mod m      # mod就是%      # 除留餘數法
  • 乘法哈希法:h(k) = floor(m*(A*key%1))   0<A<1    # 對1取模也就是取它的小數部分,   floor是向下去找
  • 全域哈希法:ha,b(k) = ((a*key + b) mod p) mod m    a,b=1,2,...,p-1

3、哈希衝突 

  因爲哈希表的大小是有限的,而要存儲的值的總數量是無限的,所以對於任何哈希函數,都會出現兩個不一樣元素映射到同一個位置上的狀況 ,這種狀況叫作哈希衝突。

  好比h(k)=k%7, h(0)=h(7)=h(14)=...

一、解決哈希衝突——開放尋址法

  開放尋址法:若是哈希函數返回的位置已經有值,則能夠向後探查新的位置來存儲這個值。

  線性探查:若是位置i被佔用,則探查i+1,i+2,......

  二次探查:若是位置i被佔用,則探查i+12,i-12,i+22,i-22,......

  二度哈希:有n個哈希函數,當使用第一個哈希函數h1發送衝突時,則嘗試使用h2,h3,......

二、解決哈希衝突——拉鍊法

  哈希表每一個位置都鏈接一個鏈表,當衝突發生時,衝突的元素將被加到該位置鏈表的最後。

  

4、哈希表的實現

# -*- coding:utf-8 -*-
__author__ = 'Qiushi Huang'


class LinkList:
    """鏈表類"""
    class Node:
        """鏈表中的節點"""
        def __init__(self, item=None):
            self.item = item
            self.next = None

    class LinkListIterator:
        """迭代器類"""
        def __init__(self, node):
            self.node = node

        def __next__(self):
            if self.node:  # 若是node不爲空
                cur_node = self.node
                self.node = cur_node.next   # 更新node爲下一個
                return cur_node.item   # 輸出前一個node
            else:
                raise StopIteration

        def __iter__(self):
            return self

    def __init__(self, iterable=None):
        """
        構造函數
        :param iterable: 傳遞的列表
        """
        self.head = None
        self.tail = None
        if iterable:
            self.extend(iterable)

    def append(self, obj):
        """
        插入節點
        :param obj:要插入的對象
        :return:
        """
        s = LinkList.Node(obj)
        if not self.head:
            self.head = s
            self.tail = s
        else:
            self.tail.next = s
            self.tail = s

    def extend(self, iterable):
        for obj in iterable:
            self.append(obj)

    def find(self, obj):
        for n in self:
            if n == obj:
                return True
        else:
            return False

    def __iter__(self):
        """
        若是一個類想被用於for ... in循環,相似list或tuple那樣,就必須實現一個__iter__()方法
        該方法返回一個迭代對象,而後,Python的for循環就會不斷調用該迭代對象的__next__()方法拿到循環的下一個值,
        直到遇到StopIteration錯誤時退出循環。
        :return: 
        """
        return self.LinkListIterator(self.head)

    def __repr__(self):  # 轉換爲字符串
        return "<<" + ", ".join(map(str, self)) + ">>"


# 哈希表作成相似集合的結構
class HashTable:
    def __init__(self, size=101):
        self.size = size
        self.T = [LinkList() for i in range(self.size)]   # 未傳值時,LinkList()是一個空鏈表

    def h(self, k):   # 哈希函數
        return k % self.size

    def insert(self, k):  # 插入函數
        i = self.h(k)
        if self.find(k):
            print("Duplicated Insert")
        else:
            self.T[i].append(k)

    def find(self, k):    # 查找函數
        i = self.h(k)
        return self.T[i].find(k)

ht = HashTable()

ht.insert(0)
ht.insert(1)
ht.insert(0)  # Duplicated Insert
ht.insert(3)
ht.insert(102)   # 102%101=1

print(",".join(map(str, ht.T)))
print(ht.find(102))
"""
<<0>>,<<1, 102>>,<<>>,<<3>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,
<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,
<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,
<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,
<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>
True
"""

5、哈希表的應用

一、集合與字典

  • 字典與集合都是經過哈希表來實現的,e.g.  a={'name':'Alex', 'age':18, 'gender':'Man'} 

  • 使用哈希表存儲字典,經過哈希函數將字典的映射爲下標。假設h('name')=3,h('age')=1,h('gender')=4,則哈希表存儲爲[None, 18, None, 'Alex', 'Man']
  • 若是發生哈希衝突,則經過拉鍊法或開放尋址法解決

二、md5算法

  MD5(Message-Digest Algorithm 5)曾經是密碼學中經常使用的哈希函數,能夠把任意長度的數據映射爲128位的哈希值。

(1)MD5曾經包含以下特徵

  1)一樣的消息,其MD5值一定相同;

  2)能夠快速計算出任意給定消息的MD5值;

  3)除非暴力枚舉全部可能的消息,不然不可能從哈希值反推出消息自己;

  4)兩條消息之間即便只有微小的差異,其對應的MD5值也應該是徹底不一樣、徹底不相關的;

  5)不能在有意義的時間內人工地構造兩個不一樣的消息,使其具備相同的MD5值。

(2)應用舉例——文件的哈希值

  算出文件的哈希值,若兩個文件的哈希值相同,則可認爲這兩個文件是相同的。所以:

  1)用戶能夠利用它來驗證下載的文件是否完整。

  2)雲存儲服務商能夠利用它來判斷用戶要上傳的文件是否已經存在於服務器上,從而實現秒傳的功能,同時避免存儲過多相同的文件副本。

三、SHA2算法

  歷史上MD5和SHA-1曾經是使用最爲普遍的cryptographic hash function,可是隨着密碼學的發展,這兩個哈希函數的安全性相繼受到了各類挑戰。

  所以如今安全性較重要的場合推薦使用SHA-2等新的更安全的哈希函數。

  SHA-2包含了一系列的哈希函數:SHA-224,SHA-256,SHA-384,SHA-512,SHA-512/224,SHA-512/256,其對應的哈希值長度分別爲224,256,384or512位。

  SHA-2具備與MD5相似的性質(參見MD5算法的特徵)。

(1)應用舉例

  例如,在比特幣系統中,全部參與者須要共同解決以下問題:對於一個給定的字符串U,給定的目標哈希值H,須要計算出一個字符串V,使得U+V的哈希值與H的差小於一個給定值D。此時,只能經過暴力枚舉V來進行猜想。首先計算出結果的人可得到必定獎金。而某人首先計算成功的機率與其擁有的計算量成正比,因此其得到的獎金的指望值與其擁有的計算量成正比。

相關文章
相關標籤/搜索