有一種數據結構,叫作散列表,還有一些稱之爲「字典」(dict)、「映射」(map)、「哈希」(hash)。數組
這種數據結構有個特色,通常狀況下,能在O(1)時間內根據關鍵字找到要查詢的信息(進行一次或者不多次比較),這是由於散列表的底層通常會使用數組實現,利用「散列函數」或者稱爲「hash函數」,能夠計算出該元素應該存到哪一個位置,可以讓全部的元素均勻分佈在數組中,下一次查找的時候,根據散列函數計算出對應的位置,就能找到元素。數據結構
可是存在一個問題,沒有完美的hash函數!!!!也就是說目前不能肯定某個元素的位置,且不重複!也就是說,散列可能會存在衝突,好比某個元素經過散列函數計算,肯定應該存在下標5的位置,下一次再來一個元素,計算後發現仍是存在下標爲5的位置,此時就出現了衝突。函數
出現hash衝突後,有多種方式能夠解決衝突,好比開放地址法、鏈地址法(拉鍊法),本文主要介紹開放地址法的一種——線性探測法。測試
根據維基百科對線性探測法的介紹,摘抄以下:spa
線性探測是一種開放尋址的策略。在這些策略裏,散列表的每一個單元都存儲一對鍵值對。當散列函數對一個給定值產生一個鍵,而且這個鍵指向散列表中某個已經被另外一個鍵值對所佔用的單元時,線性探測用於解決此時產生的衝突:查找散列表中離衝突單元最近的空閒單元,而且把新的鍵插入這個空閒單元。一樣的,查找也同插入一模一樣:從散列函數給出的散列值對應的單元開始查找,直到找到與鍵對應的值或者是找到空單元。code
講的通俗一點,就是發現蹲坑的時候發現坑已經被佔了,就找後面一個坑,若是後面一個坑空閒,則佔用這個空閒的坑;若是後面一個坑也被佔了,則一直日後面的坑進行遍歷,直到找到空閒的坑,不然就一直憋着。blog
目前有一個長度爲8的數組,選擇的hash函數是 e.key%8,這個8是指數組的長度(容量),當數組長度發生變化,hash函數也應該變化。hash
新加入一個元素e1,key爲5,hash函數計算出應該存到位置5,由於5%8=5,而後看位置5有沒有被佔用,發現沒有被佔用,則將e1存入位置5:it
又加入一個新元素e2,key爲13,hash函數計算出也應該存到位置5,由於13%8=5,可是發現位置5已經被佔用了,此時就位置6有沒有被佔用,此時發現位置6沒有被佔用,則e2就存到位置6了class
此時又來一個e3,key爲21,發現還要存到位置5,可是位置5已經被佔了,日後看位置6,發現位置6也被佔了,再看位置7,位置7空着,因此e3就存到位置7了
此時來了一個e4,key爲29,發現仍是存到位置4,而且位置五、六、7已經都被佔用了,此時只能從頭考慮位置0了,發現位置0未被佔用,則將e4存到位置0
因此說,上面的數組實際上是一個環形數組。
如今要查詢key爲5的元素,經過計算,對應的位置爲5,查看位置5的key,發現位置5的key與要查找的key相等,則查找成功,返回e1;
若是要查詢一個key爲13的元素,經過計算key爲13,對應位置5,可是位置5的key爲5,與13不匹配,此時日後看位置6,發現位置6的key爲13,與要查找的key相等,此時查找成功,返回e2便可;
若是要查詢key爲37的元素,經過計算對應位置5,可是位置5的key爲5,與13不匹配,日後看位置6的key爲13也和37不匹配,一直到位置0,發現e4的key爲29,仍舊不匹配要找的37,接着看位置1,發現位置1沒有元素,證實數組中沒有存key爲37的元素,查找失敗。
以上面插入e4到位置0後進行介紹
此時要刪除一個元素key爲13,此時,經過hash計算出位置應該在位置5,由於13%8=5,發現位置5的key爲5,不等於要刪除的13;
日後看位置6,發現位置6的key爲13,和要刪除的key相同,測試將位置6的元素刪除:
若是此時不進行其餘修改操做,而是進行查找操做,好比查找key爲21的元素,應該對應位置5,可是位置5已經有元素了,且不是要找的元素,此時會日後看下一個位置,發現位置6爲空,沒有元素,因此這次查詢失敗!!!
可是,此次查詢是失敗的!不是說查詢的方式有問題,而是說數組的元素存放有問題,由於key爲21的元素在數組中,可是卻並無被查詢出來。
爲了解決這個問題,咱們在刪除元素後,要將其後面的元素進行從新肯定位置,也就是rehash,過程以下:
刪除位置6的元素,因此看位置6後面的元素,7->0->5,每一個元素都須要計算hash,肯定新位置。
好比位置7的key爲21,發現應該調整到位置5,發現位置5已經有了元素,看位置6,發現位置6空着,則將元素e3放入位置6
接着看位置0的元素是否須要調整,在進行計算而且通過上面的流程後,e4應該調整到位置7
須要注意的是,調整位置0後,因爲位置1沒有元素,則能夠中止調整,由於沒有元素,則表示後面的第一個非空位置存的元素(好比e1)確定沒有衝突。
當數組中全部位置都填滿了,此時再插入元素,就無處安放了,此時有兩種作法:1.拒絕插入;2.擴容。
通常來講,並非當數組沒有空位時才擴容,而是數組元素達到必定閾值後就進行擴容,可是須要注意的是數組擴容要作的不僅是數組擴容,還須要將舊數組中的元素拷貝到新數組中。
當數組擴容後(假設是翻倍),則數組長度變爲16,下標從0~15,以下圖所示:
在拷貝的過程當中,須要從新計算每一個元素的hash值,也就是肯定每一個元素在新數組中的位置,其實從左往右遍歷舊數組當中的元素,依次插入到新數組中,有衝突就按照原來的方式解決衝突便可。
擴容後,新的散列函數爲e.key%16:
首先是看位置0,有元素,元素e4的key爲29,則新位置爲13,由於29%16=13,發現新數組的位置13空着,因而e4元素就放入位置13;
而後從左往右遍歷到e1,發現元素e1的key爲5,5%16=5,因此新位置爲5,恰好位置5也空着,因此e1放入位置5;
而後輪到e2,計算的key應該保存到13,13%16=13,應該放到位置13上,可是位置13上已經有了元素,日後看位置14,發現位置14空着,因而e2就放入位置14
.....全部元素都完成拷貝後,數組的擴容才真的完成,以下圖所示: