[@一個菜雞的成長之路]
Laraine’s channel
hello, everyone! welcome to my blog。😘😘
菜雞程序媛的進階之路-哈希
講這個專題主要是由於之前筆試,做過的一道題。😁😁
所以在這我會先上題目,後面做具體哈希講解。
題目描述:有n個英漢混雜詞彙的屏蔽詞典,輸入一行字符串,快速查找輸入字符串是否包含屏蔽詞,打印輸入字符串,將匹配到的屏蔽詞替換爲’#’
注意:這裏的n測試的時候可以簡單的用10/20個來代替
各位可以想一下,怎樣耗時最短,佔用內存最小。
先說一下我的思路啊。
1.將每個單詞的第一個字母,中文就是第一個字,做爲key,剩下的單詞或者漢字當value。
看到這裏肯定很多同學第一反應想到用map,你們以爲這麼簡單嗎!
哼,還是太單純啦。別慌,我先滿足一下你們,先來用map試一下啊
var mapStr = map[string]string{
"abfcc": "e",
"你": "好",
"s": "e",
"ba": "d",
}
func (this *LinkNode) Search(s string) string {
str := " "
for _, v := range s {
fmt.Printf("字符串:%v指針%v\n", string(v), this.Next[string(v)])
if mapStr[string(v)] == " " {
continue
}
str += string(v)
}
return str
}
func main() {
fmt.Print("%d", sizeLast("se kjk hello"))
}
看似很完美,但是問題來了,我們的map,裏面的key值是不允許重複的。我門詞彙表裏,肯定會出現首字母相同的情況,for example: apple app,這裏你們肯定會黑人問號
那該怎麼辦呢?
哈哈,稍安勿躁。這個時候我們的哈希鏈表就派上了用場。到這,肯定很多人不知道什麼是哈希,更別說哈希鏈表了。
哈希(hash):就是把任意長度的輸入通過散列算法變換成固定長度的輸出,該輸出就是散列值。(也叫所壓縮映射)
哈希鏈表::就是將散列值放入表中一個位置來進行訪問,典型的空間換時間。該數據結構相當於鏈表,但其中元素是散列排序的,因此也稱散列表。就是數組+鏈表的組合
數組,尋址容易,插入和刪除困難;而鏈表,尋址困難,插入和刪除容易。結合兩組優點,我們的哈希鏈表出現了,快速定位查找,增加刪除,**時間複雜度爲O(1)**上圖,拉鍊法即使兩組有點的融合。我們通過一定的計算方式將元素的部分特徵轉換成固定長度散列值作爲key存入空間,這個空間大小我們便成爲 “桶”,每一個桶是一個存儲單元,而後的value我們加在鏈表上。到這,我門又可以引出新一個問題,哈希如何擴容?
哈希擴容要滿足以下任意一條件:
1.當HashMap中元素總個數達到閾值時就會擴容。即:HashMap數組總容量 * 負載因子(loadFactor),loadFactor默認爲0.75
2.索引位的首節點,且該首節點不爲null(發生哈希碰撞)
當滿足了任一擴容條件,容量變爲擴大一倍,即HashMap數組總容量 * 負載因子(loadFactor)*2
我們生成散列值的方法目前有以下幾種:
1.除法散列法
上圖使用的就是這種散列法,公式: index = value % BucketCount(桶的容量,下面代碼會出現)
2.平方散列法
因爲計算key值是非常頻繁的操作,而乘法的運算要比除法來得省時,所以我們考慮把除法換成乘法和一個位移操作。公式:
index = (value * value) >> 28 (右移,除以2^28)
看到這,可能你會擔心value * value會溢出,但不重要,主要是獲取key,另外還有一種方法
3.斐波那契(Fibonacci)散列法
也是乘法運算,只是將value換成了一個固定的數字。公式:index = (value * num) >> 28
1)16位:num=40503。
2)32位:num=2654435769。
3)64位:num=11400714819323198485。
數字由來(自行百度)
然後既然是通過哈希值來訪問,當我們數據量到達一定地步的時候,我們運算中很容易出現一種情況,得到相同的哈希值。這種情況,我們稱之爲哈希碰撞,是不是很高級
解決辦法:
1.鏈地址:核心思想就是hashMap,將相同哈希值的數據已鏈表串起來(下面用到的一種)
2.開放地址:按照某種方法繼續探測其他存儲單元,直到找到空地址
講完了,我們開始解題。golang裏面是沒有hash結構的,需要自己來實現
type CacheData struct {
Key string
Value string
}
type LinkNode struct {
Data CacheData
NextNode *LinkNode
}
//桶容量
const BucketCount = 10
type HashMap struct {
Buckets [BucketCount]*LinkNode
}
func CreateLink() *LinkNode {
var linkNode = &LinkNode{CacheData{"", ""}, nil}
return linkNode
}
func (link *LinkNode) AddNode(data CacheData) int {
var count = 0
tail := link
for {
count += 1
if tail.NextNode == nil {
break
} else {
tail = tail.NextNode
}
}
var newNode = &LinkNode{data, nil}
tail.NextNode = newNode
return count + 1
}
func CreateHashMap() *HashMap {
myMap := &HashMap{}
for i := 0; i < BucketCount; i++ {
myMap.Buckets[i] = CreateLink()
}
return myMap
}
func HashCode(key string) int {
//生成哈希值,這裏爲了簡便,用的是簡單的方法生成key值
//具體生成方法可以查看golang map源碼
var sum = 0
for i := 0; i < len(key); i++ {
sum += int(key[i])
}
return (sum % BucketCount)
}
func (myMap *HashMap) AddKeyValue(key string, value string) {
var mapIndex = HashCode(key)
var link = myMap.Buckets[mapIndex]
if link.Data.Key == "" && link.NextNode == nil {
link.Data.Key = key
link.Data.Value = value
fmt.Printf("node key:%v add to buckets %d first node\n", key, mapIndex)
} else {
index := link.AddNode(CacheData{key, value})
fmt.Printf("node key:%v add to buckets %d %dth node\n", key, mapIndex, index)
}
}
func (myMap *HashMap) GetValueByKey(key string) string {
var mapIndex = HashCode(key)
var link = myMap.Buckets[mapIndex]
var value string
head := link
for {
if head.Data.Key == key {
value = head.Data.Value
break
} else if head.NextNode != nil {
head = head.NextNode
} else {
break
}
}
return value
}
func selectAndReplace(hash *HashMap, s1 string) string {
s := []rune(s1)
copyStr := ""
for i := 0; i < len(s); i++ {
value := hash.GetValueByKey(string(s[i]))
length := len([]rune(value))
if length == 0 || value != string(s[i+1:length+i+1]) {
copyStr += string(s[i])
continue
}
if value == string(s[i+1:length+i+1]) {
copyStr += "#"
i = i + length
}
}
return copyStr
}
func main() {
s1 := []string{"p", "好", "shfb", "apple", "官看", "恢復", "ijd", "kjf", "hvw", "幾哈", "kbkb"}
myMap := CreateHashMap()
for _, v := range s1 {
str := []rune(v)
myMap.AddKeyValue(string(str[0]), string(str[1:]))
}
s2 := "你dhf官看"
fmt.Printf("%v\n", selectAndReplace(myMap, s2))
}
到這,本道題就完成了。具體代碼還有待優化部分,各位可以看看。
有問題歡迎提出。
bye😘