字首樹演算法實現路由匹配原理解析

2022-07-11 08:21:10 字數 3097 閱讀 9331

路由功能是web框架中乙個很重要的功能,它將不同的請求**給不同的函式(handler)處理,很容易能想到,我們可以用乙個字典儲存它們之間的對應關係,字典的key存放path,value存放handler。當乙個請求過來後,使用routers.get(path, none)就可以找到對應的handler。

利用字典實現路由可以參考我的這篇文章:動手實現web框架 。

使用字典有乙個問題,不支援動態路由。如果路由像這樣呢?

/hello/:name/profile
name前面是萬用字元:,表示這是個動態的值。乙個解決辦法是使用字首樹trie。

leetcode中有這個演算法,點這裡 檢視。
字首樹字首樹,首先是一棵樹。不同的是樹中乙個節點的所有子孫都有相同的字首。字首樹將單詞中的每個字母依次插入樹中,插入前首先確認該單詞是否存在,不存在才建立新節點,如果乙個單詞已經全部插入,則將末尾單詞設定為標誌位。

type node struct 

type trie struct

以單詞leetcode,leetd和code為例,首先一次插入leetcode中的每個單詞,然後插入leetd的時候,leet在樹中已經存在,跳過往下,現在要插入字母d,不存在,所以新建節點插入樹中,並將該節點的isword置位true,表明到了單詞末尾。

最終插入結果為:

}那麼,當我們要搜尋單詞leetd的時候,從根節點開始查詢,如果找到某條路徑是leetd,並且末尾的d是單詞標誌位,則表示搜尋成功。

func (this *trie) search(word string) bool 

cur = cur.next[c]

}return cur.isword

}

明白了字首樹的原理,我們來看看路由匹配是如何利用字首樹來實現的。

go語言中gin框架的路由實現就是利用字首樹,可以看看它的源**是如何實現的。

考慮一下,路由或者說路徑的特點,是以/分隔的單詞組成的,那我們將/的每一部分掛靠在字首樹上就可以了。如下圖所示:

還有一點需要考慮,我們在用web框架定義路由的時候,常見的做法是根據不同的http方法來定義。比如:

// 以go語言gin框架為例

g := gin.new()

g.get("/hello", hello)

g.post("/form", form)

對於同乙個路徑,可能有多個方法支援。所以我們要以不同http方法為樹根建立字首樹。當乙個get請求過來的時候,就從get樹上搜尋,post請求就從post樹上搜尋。

除了為不同的http方法定義樹之外,還要給那些是萬用字元的節點增加乙個標誌位。所以,我們的路由字首樹結構看起來像這樣:

type node struct 

type router struct

依照上面的字首樹演算法的實現,照葫蘆畫瓢,我們可以寫出插入路由和搜尋路由的方法:

// addroute 繫結路由到handler

func (r *router) addroute(method, path string, handler handlerfunc)

}root := r.root[method]

key := method + "-" + path

// 將parts插入到路由樹

for _, part := range parts

}root = root.children[part]

}root.path = path

// 繫結路由和handler

r.route[key] = handler

}// getroute 獲取路由樹節點以及路由變數

func (r *router) getroute(method, path string) (node *node, params map[string]string)

searchparts := parsepath(path)

// get method trie

var ok bool

if node, ok = r.root[method]; !ok

// 在該方法的路由樹上查詢該路徑

for i, part := range searchparts

if child.part[0] == ':'

temp = child.part}}

// 遇到萬用字元*,直接返回

if temp[0] == '*'

node = node.children[temp]

}return

}

上面的**是我自己實現的乙個web框架 gaga 中路由字首樹相關的**,有需要的可以去看看源**。另外,歡迎star呀。

其中的addroute用來將路由插入到對應method的路由樹中,如果節點是萬用字元,將其設定為iswild, 同時繫結路由和handler方法。

getroute方法首先查詢路由方法對應的路由字首樹,然後在樹中查詢是否存在該路徑。

字首樹trie演算法不光可以用在路由的實現上,搜尋引擎中自動補全的實現,拼寫檢查等等都是用trie實現的。trie樹查詢的時間和空間複雜度都是線性的,效率很高,很適合路由這種場景使用。

路由的實現上,go語言中httprouter這個庫除了使用字首樹之外,還加入了優先順序,有興趣的可以看看它的原始碼了解下。

參考: 

KMP演算法原理解析

這種演算法不太容易理解,網上有很多解釋,但讀起來都很費勁。直到讀到jake boxer的文章,我才真正理解這種演算法。下面,我用自己的語言,試圖寫一篇比較好懂的kmp演算法解釋。首先,字串 bbc abcdab abcdabcdabde 的第乙個字元與搜尋詞 abcdabd 的第乙個字元,進行比較。...

EM演算法原理解析

em演算法,即最大期望演算法 expectation maximization algorithm,又譯期望最大化演算法 是一種迭代演算法,用於含有隱變數 latent variable 的概率引數模型的最大似然估計或極大後驗概率估計。em演算法應用於高斯混合模型 gmm 聚類 隱式馬爾科夫演算法 ...

KMP 演算法原理解析與 C 實現

kmp 演算法包含兩個部分 利用 pattern 構建 next 陣列 利用 next 資料去匹配長字串 使用 kmp 演算法的好處是,對於被匹配的長字串,我們始終不需要往回走。next 陣列中的每乙個元素,比如說 next i 代表的是在 pattern 中第 i 個位置之前存在的字首和字尾相等的...