2026-01-14
go
0

目录

前缀树静态实现

前缀树静态实现

在学习gin的前缀树的过程中,很难理解其中的逻辑,后来看了部分源代码之后,突然领悟,还得从简单添加功能到复杂,所以,先写一个静态的前缀树,后面再添加功能到复杂

go
package urltree import ( "fmt" "log" "strings" "gitee.com/duzy2023/urltree/internel" ) // 首先创建一个静态匹配前缀树 type nodeType uint8 // 节点类型 const ( static nodeType = iota // 静态类型 ) type node struct { fullpath string // 全路径,只有最后一个节点才有 path string // 当前节点在整个路径中的字段 children []*node nType nodeType // 节点类型 indices string //子节点path的第一个字符 } // 获取新节点 func NewRoot() *node { n := &node{ fullpath: "/", children: make([]*node, 0), } return n } // 找到两个路径段最长相同前缀的下标 func longestCommonPrefix(apath, bpath string) int { i := 0 max_ := min(len(apath), len(bpath)) for i < max_ && apath[i] == bpath[i] { i++ } return i } // 添加静态路由 func (n *node) AddRoute(fullpath string) error { if !strings.HasPrefix(fullpath, "/") { return fmt.Errorf("path must start with '/' but get '%s'", fullpath) } // 由于后面会对fullpath进行处理,所以这里对fullpath进行备份 _fullpath := fullpath // 1. // 先判断是否是空节点 // 没有子节点并且path为空,那么就是空节点 if len(n.children) == 0 && len(n.path) == 0 { log.Printf("Empty Node") n.path = fullpath n.fullpath = _fullpath return nil } // 对于非空节点进行处理 parentFullPathIndex := 0 walk: for { // 2.计算最长公共前缀(LCP) i := longestCommonPrefix(fullpath, n.path) // 3.节点分裂 // 如果LCP小于当前节点的path长度,就需要分裂 if i < len(n.path) { child := node{ path: n.path[i:], nType: n.nType, indices: n.indices, children: n.children, fullpath: n.fullpath, } n.children = []*node{&child} n.indices = internel.BytesToString([]byte{n.path[i]}) n.path = fullpath[:i] n.fullpath = "" } // 4.处理剩余路径 if i < len(fullpath) { fullpath = fullpath[i:] nextChar := fullpath[0] // 5. 查找匹配的子节点 for j, idxChar := range n.indices { if byte(idxChar) == nextChar { parentFullPathIndex += len(n.path) n = n.children[j] continue walk } } // 6.没有匹配的子节点,创建新的节点 n.indices += internel.BytesToString([]byte{nextChar}) newChild := &node{ path: fullpath, fullpath: _fullpath, nType: static, } n.addChild(newChild) return nil } // 7.完全匹配 // 说明已经注册了一个完整路径,报错 if n.fullpath != "" { return fmt.Errorf("path '%s' is already registered", _fullpath) } // 如果n.fullpath == "",那么,就表明这个路由还没被注册 n.fullpath = _fullpath return nil } } func (n *node) addChild(child *node) { n.children = append(n.children, child) } // FindRoute 查找路由 (可选功能) // path: 待查找的路径 // returns: 找到的节点的 fullpath,如果没找到则返回空字符串 func (n *node) FindRoute(path string) string { parentPathIndex := 0 currentNode := n for { // 计算当前节点 path 与查找路径的 LCP i := longestCommonPrefix(path, currentNode.path) // 如果 LCP 不等于当前节点的 path,说明匹配失败 if i != len(currentNode.path) { return "" // 路径不存在 } // 如果 path 已经完全匹配(LCP 等于 path 长度) if i == len(path) { // 检查当前节点是否有 fullpath(代表它是一个注册的终点) return currentNode.fullpath // 返回注册时的完整路径 } // path 还有剩余,继续向下查找 path = path[i:] nextChar := path[0] // 在 indices 中查找下一个字符 foundIndex := -1 for j, idxChar := range currentNode.indices { if byte(idxChar) == nextChar { foundIndex = j break } } if foundIndex == -1 { return "" // 没有找到匹配的子节点,路径不存在 } // 更新 parentPathIndex 并跳转到子节点 parentPathIndex += len(currentNode.path) currentNode = currentNode.children[foundIndex] } } // PrintTree 用于调试:打印树结构 (辅助函数) func (n *node) PrintTree(prefix string, isLast bool) { if n == nil { return } connector := "├── " if isLast { connector = "└── " } fmt.Printf("%s%s path: '%s', fullpath: '%s'\n", prefix, connector, n.path, n.fullpath) childCount := len(n.children) for i, child := range n.children { isLastChild := i == childCount-1 newPrefix := prefix if !isLast { newPrefix += "│ " // 如果当前节点不是父节点的最后一个孩子,则竖线继续 } else { newPrefix += " " // 如果是最后一个孩子,则用空格填充 } child.PrintTree(newPrefix, isLastChild) } }

其中的FindTree和PrintTree是使用AI生成的,AI真好用