Radix树
Radix树,即基数树,也称压缩字典树,是一种提供key-value存储查找的数据结构。radix tree罕用于疾速查找的场景中,例如:redis中存储slot对应的key信息、内核中应用radix tree治理数据结构、大多数http的router通过radix治理路由。Radix树在Trie Tree(字典树)的原理上优化过去的。因而在介绍Radix树的特点之首先简略介绍一下Trie Tree。
Trie树
Trie Tree,即字典树。Trie Tree的原理是将每个key拆分成一个个字节,而后对应到每个分支上,分支所在的节点对应为从根节点到以后节点的拼接出的key的值。下图由四个字符串:haha、hehe、god、goto,造成的一个Trie树的示例:
利用上图中结构的Trie Tree,咱们能够很不便的检索一个单词是否呈现在原来的字符串汇合中。例如,咱们检索单词:hehe,那么咱们从根节点开始,而后一一单词进行比照,路径结点:h-e-h-e,就定位到要检索的字符串了。从中能够看出,Trie Tree查找效率是十分高的,达到了O(K),其中K是字符串的长度。
Radix树特点
尽管Trie Tree具备比拟高的查问效率,然而从上图能够看到,有许多结点只有一个子结点。这种状况是不必要的,岂但影响了查问效率(减少了树的高度),次要是节约了存储空间。齐全能够将这些结点合并为一个结点,这就是Radix树的由来。Radix树将只有一个子节点的两头节点将被压缩,使之具备更加正当的内存应用和查问的效率。下图是采纳Radix树后的示例:
与hash表的比照
- 查问效率
Radix树的插入、查问、删除操作的工夫复杂度都为O(k)。hash表的安查问效率:hash函数计算效率+O(1),当然与若hash函数若选取不合理,则会造成抵触,会造成hash表的查问效率升高。 总总体上来说两者查问性能根本相等(hash函数的效率基本上等于O(k))。 - 空间效率
从空间应用来说,hash占优,因为Radix树的每个节点的节点负载较高,Radix树的结点须要有指向子结点的指针。 - 其余方面
hash表的毛病有:数据量增长的状况下可能须要rehash。Radix树不会存在rehash的状况,然而若数据数据密集,则会造成Radix树进化为Trie树,会升高空间应用效率以及查问效率。 - 取舍倡议
如果数据稠密,Radix树能够更好的节俭空间,这种状况下应用Radix树会更佳。如果数据量小但数据密集,则倡议抉择hash表。
Radix树的Go语言版本
本文应用Go语言来实现Radix树,目前代码曾经上传到github中,下载地址
在该代码中应用二叉树来实现Radix树,绝对与四叉树的版本,本版本在存储空间的使用率方面会更好一点,但会就义肯定的查问效率。
若谋求查问效率,请应用多叉树来实现。
应用二叉树来解决字符串时,将字符串看作bit数组,而后一一bit来比照两这个bit数组,将bit数组后面雷同的局部保留到父结点,
将两个bit数组的前面的局部别离保留到left子结点以及right子结点中。例如:
god: 0110-0111 0110-1111 0110-0100goto: 0110-0111 0110-1111 0111-0100 0110-1111
从上例能够看到,二者在地20位处不相等,因而:
- 0110-0111 0110-1111 011:被划分到父节点。
- 0-0100:被划分到left子结点。
- 1-0100 0110-1111:被划分到right子结点。
定义
首先给出Radix树的定义:
type RaxNode struct { bit_len int bit_val []byte left *RaxNode right *RaxNode val interface{}}type Radix struct { root [256]*RaxNode}func NewRaxNode() *RaxNode { nd := &RaxNode{} nd.bit_len = 0 nd.bit_val = nil nd.left = nil nd.right = nil nd.val = nil return nd}
将数据增加到Radix树
//增加元素func (this *Radix)Set(key string, val interface{}) { data := []byte(key) root := this.root[data[0]] if root == nil { //没有根节点,则创立一个根节点 root = NewRaxNode() root.val = val root.bit_val = make([]byte, len(data)) copy(root.bit_val, data) root.bit_len = len(data) * 8 this.root[data[0]] = root return } else if root.val == nil && root.left == nil && root.right == nil { //只有一个根节点,并且是一个空的根节点,则间接赋值 root.val = val root.bit_val = make([]byte, len(data)) copy(root.bit_val, data) root.bit_len = len(data) * 8 this.root[data[0]] = root return } cur := root blen := len(data) * 8 for bpos := 0; bpos < blen && cur != nil; { bpos, cur = cur.pathSplit(data, bpos, val) }}func (this *RaxNode)pathSplit(key []byte, key_pos int, val interface{}) (int, *RaxNode) { //与path对应的key数据(去掉曾经解决的公共字节) data := key[key_pos/8:] //key以bit为单位长度(蕴含开始字节的字节内bit位的偏移量) bit_end_key := len(data) * 8 //path以bit为单位长度(蕴含开始字节的字节内bit位的偏移量) bit_end_path := key_pos % 8 + int(this.bit_len) //以后的bit偏移量,须要越过开始字节的字节内bit位的偏移量 bpos := key_pos % 8 for ; bpos < bit_end_key && bpos < bit_end_path; { ci := bpos / 8 byte_path := this.bit_val[ci] byte_data := data[ci] //起始字节的外部偏移量 beg := 0 if ci == 0 { beg = key_pos % 8 } //终止字节的外部偏移量 end := 8 if ci == bit_end_path / 8 { end = bit_end_path % 8 if end == 0 { end = 8 } } if beg != 0 || end != 8 { //不残缺字节的比拟,若不等则跳出循环 //若相等,则增长bpos,并持续比拟下一个字节 num := GetPrefixBitLength2(byte_data, byte_path, beg, end) bpos += num if num < end - beg { break } } else if byte_data != byte_path { //残缺字节比拟,num为雷同的bit的个数,bpos减少num后跳出循环 num := GetPrefixBitLength2(byte_data, byte_path, 0, 8) bpos += num break } else { //残缺字节比拟,相等,则持续比拟下一个字节 bpos += 8 } } //以后字节的地位 char_index := bpos / 8 //以后字节的bit偏移量 bit_offset := bpos % 8 //残余的path长度 bit_last_path := bit_end_path - bpos //残余的key长度 bit_last_data := bit_end_key - bpos //key的数据有残余 //若path有子结点,则持续解决子结点 //若path没有子结点,则创立一个key子结点 var nd_data *RaxNode = nil var bval_data byte if bit_last_data > 0 { //若path有子结点,则退出本函数,并在子结点中进行解决 byte_data := data[char_index] bval_data = GET_BIT(byte_data, bit_offset) if bit_last_path == 0 { if bval_data == 0 && this.left != nil { return key_pos + int(this.bit_len), this.left } else if bval_data == 1 && this.right != nil { return key_pos + int(this.bit_len), this.right } } //为残余的key创立子结点 nd_data = NewRaxNode() nd_data.left = nil nd_data.right = nil nd_data.val = val nd_data.bit_len = bit_last_data nd_data.bit_val = make([]byte, len(data[char_index:])) copy(nd_data.bit_val, data[char_index:]) //若bit_offset!=0,阐明不是残缺字节, //将字节决裂,并将字节中的非公共局部分离出来,保留到子结点中 if bit_offset != 0 { byte_tmp := CLEAR_BITS_LOW(byte_data, bit_offset) nd_data.bit_val[0] = byte_tmp } } //path的数据有残余 //创立子节点:nd_path结点 //并将数据离开,公共局部保留this结点,其余保留到nd_path结点 var nd_path *RaxNode = nil var bval_path byte if bit_last_path > 0 { byte_path := this.bit_val[char_index] bval_path = GET_BIT(byte_path, bit_offset) //为残余的path创立子结点 nd_path = NewRaxNode() nd_path.left = this.left nd_path.right = this.right nd_path.val = this.val nd_path.bit_len = bit_last_path nd_path.bit_val = make([]byte, len(this.bit_val[char_index:])) copy(nd_path.bit_val, this.bit_val[char_index:]) //将byte_path字节中的非公共局部分离出来,保留到子结点中 if bit_offset != 0 { byte_tmp := CLEAR_BITS_LOW(byte_path, bit_offset) nd_path.bit_val[0] = byte_tmp } //批改以后结点,作为nd_path结点、nd_data结点的父结点 //多申请一个子节,用于存储可能呈现的不残缺字节 bit_val_old := this.bit_val this.left = nil this.right = nil this.val = nil this.bit_len = this.bit_len - bit_last_path //=bpos - (key_pos % 8) this.bit_val = make([]byte, len(bit_val_old[0:char_index])+1) copy(this.bit_val, bit_val_old[0:char_index]) this.bit_val = this.bit_val[0:len(this.bit_val)-1] //将byte_path字节中的公共局部分离出来,保留到父结点 if bit_offset != 0 { byte_tmp := CLEAR_BITS_HIGH(byte_path, 8-bit_offset) this.bit_val = append(this.bit_val, byte_tmp) } } //若path蕴含key,则将val赋值给this结点 if bit_last_data == 0 { this.val = val } if nd_data != nil { if bval_data == 0 { this.left = nd_data } else { this.right = nd_data } } if nd_path != nil { if bval_path == 0 { this.left = nd_path } else { this.right = nd_path } } return len(key) * 8, nil}
Radix树的查问
func (this *Radix)Get(key string) interface{}{ data := []byte(key) blen := len(data) * 8 cur := this.root[data[0]] var iseq bool for bpos := 0; bpos < blen && cur != nil; { iseq, bpos = cur.pathCompare(data, bpos) if iseq == false { return nil } if bpos >= blen { return cur.val } byte_data := data[bpos/8] bit_pos := GET_BIT(byte_data, bpos%8) if bit_pos == 0 { cur = cur.left } else { cur = cur.right } } return nil}func (this *RaxNode)pathCompare(data []byte, bbeg int) (bool, int) { bend := bbeg + int(this.bit_len) if bend > len(data) * 8 { return false, len(data) * 8 } //起始和终止字节的地位 cbeg := bbeg / 8; cend := bend / 8 //起始和终止字节的偏移量 obeg := bbeg % 8; oend := bend % 8 for bb := bbeg; bb < bend; { //获取两个数组的以后字节地位 dci := bb / 8 nci := dci - cbeg //获取数据的以后字节以及循环步长 step := 8 byte_data := data[dci] if dci == cbeg && obeg > 0 { //清零不残缺字节的低位 byte_data = CLEAR_BITS_LOW(byte_data, obeg) step -= obeg } if dci == cend && oend > 0 { //清零不残缺字节的高位 byte_data = CLEAR_BITS_HIGH(byte_data, 8-oend) step -= 8-oend } //获取结点的以后字节,并与数据的以后字节比拟 byte_node := this.bit_val[nci] if byte_data != byte_node { return false, len(data)*8 } bb += step } return true, bend}
Radix树的删除
func (this *Radix)Delete(key string) { data := []byte(key) blen := len(data) * 8 cur := this.root[data[0]] var iseq bool var parent *RaxNode = nil for bpos := 0; bpos < blen && cur != nil; { iseq, bpos = cur.pathCompare(data, bpos) if iseq == false { return } if bpos >= blen { //将以后结点批改为空结点 //若parent是根节点,不能删除 cur.val = nil if parent == nil { return } //以后结点是叶子结点,先将以后结点删除,并将以后结点指向父结点 if cur.left == nil && cur.right == nil { if parent.left == cur { parent.left = nil } else if parent.right == cur { parent.right = nil } bpos -= int(cur.bit_len) cur = parent } //尝试将以后结点与以后结点的子节点进行合并 cur.pathMerge(bpos) return } byte_data := data[bpos/8] bit_pos := GET_BIT(byte_data, bpos%8) if bit_pos == 0 { parent = cur cur = cur.left } else { parent = cur cur = cur.right } }}func (this *RaxNode)pathMerge(bpos int) bool { //若以后结点存在值,则不能合并 if this.val != nil { return false } //若以后结点有2个子结点,则不能合并 if this.left != nil && this.right != nil { return false } //若以后结点没有子结点,则不能合并 if this.left != nil && this.right != nil { return false } //获取以后结点的子结点 child := this.left if this.right != nil { child = this.right } //判断以后结点最初一个字节是否是残缺的字节 //若不是残缺字节,须要与子结点的第一个字节进行合并 if bpos % 8 != 0 { char_len := len(this.bit_val) char_last := this.bit_val[char_len-1] char_0000 := child.bit_val[0] child.bit_val = child.bit_val[1:] this.bit_val[char_len-1] = char_last | char_0000 } //合并以后结点以及子结点 this.val = child.val this.bit_val = append(this.bit_val, child.bit_val...) this.bit_len += child.bit_len this.left = child.left this.right = child.right return true}