0.简介
本文介绍了字典树以及01字典树的内容,提供了ts的01字典树实现,执行map接口。需要注意,注意,注意,完成后测试的结果,其实效率不如Map<number,number>,只提供参考了解作用,不建议,不建议,不建议实际使用
1.前言(无关内容可跳过)
在做ts练手时,不清楚map的实现,查询是说用哈希表。考虑到只需要number的,我存储的对象key的结构非常适合用01字典树,所以打算写一个01字典树,接口Map<number,number>
2.字典树与01字典树
字典树通常用来查询字符。和我们去查英语字典一样,比如ground单词,首先会查询首字母,g,然后查r,然后再依次查询。这个字符在树中的表现为 (root为根节点)
root->g-> r->o->u->n->d
如果再插入go单词,那树的结构就会变为,第二行o为g的一个子节点
root->g->r->o->u->n->d
______->o
这样我们可以非常方便的在字典中插入和查询字符,而对于其他的对象,我们也可以通过Hash等方式转化成唯一对应的字符串来,做树的插入和执行处理。
01字典树是字典树的一种特例,每个节点只有0和1两个子节点,01101101这种二进制数字就可以很方便快捷的放入字典中
3.字典树实现(完整代码在6)
(1)树节点
对于树节点,我们需要保存一个value值,以及01指针,如果是不需要的保存value(如果不是实现Map而是set),可以将value定义为bool,用于标志是否有在当前结束的key
class Node {
Value: number | undefined = undefined
Child0: Node | undefined = undefined
Child1: Node | undefined = undefined
}
(2)树结构
树首先会有根节点、数量2个基础属性。clear set get delete has,5个基础的查询、删除、增加的函数。最后是Map接口需要实现的一些迭代器可做遍历处理
export class TreeMap implements Map<number, number> {
private root: Node = new Node()
private _size: number = 0
// clear set get delete has
// Iterator、forEach、...
}
(3)节点设置与获取
对于clear set get delete has这5个函数。本质其实是对树节点的一种操作和处理,所以我们首先实现基础树节点操作函数setNode和getNode。主要是靠对key的右移来不断确定,节点是0节点或者1节点,去创建或获取对应的节点,直到key全部左移结束。endCheck: number = ~1,就是为了检测是否所有key的所有位都已经遍历结束
private setNode(key: number): Node {
let cur: Node = this.root
if (key & 1) {
if (cur.Child1 == undefined) {
cur.Child1 = new Node()
}
cur = cur.Child1
} else {
if (cur.Child0 == undefined) {
cur.Child0 = new Node()
}
cur = cur.Child0
}
while (key & -2) {
// -2 = ~1
key = key >>> 1
// console.log("key:" + key)
// console.log("node1:" + (key & 1))
if (key & 1) {
if (cur.Child1 == undefined) {
cur.Child1 = new Node()
}
cur = cur.Child1
} else {
if (cur.Child0 == undefined) {
cur.Child0 = new Node()
}
cur = cur.Child0
}
}
return cur
}
private getNode(key: number): Node | undefined {
let cur: Node | undefined = this.root
if (key & 1) {
cur = cur.Child1
} else {
cur = cur.Child0
}
while (key & -2) {
// -2 = ~1
key = key >>> 1
if (cur == undefined) {
break
}
if (key & 1) {
cur = cur.Child1
} else {
cur = cur.Child0
}
}
return cur
}
然后再此基础上就能,很方便写出五个函数。get是获取节点,如果有就返回节点保存的数值。set就是设置节点以及数值。has就是获取节点,返回是否获取到。delete是获取节点并删除数值。clear就可以简单,直接将root的01字节点都置空,赋值undefined就可,这样数据就被清空了。如下
public clear(): void {
this.root.Child0 = undefined
this.root.Child1 = undefined
this._size = 0
}
public get(key: number): number | undefined {
return this.getNode(key)?.Value
}
public has(key: number): boolean {
return this.getNode(key)?.Value != undefined
}
public set(key: number, value: number): this {
this.setNode(key).Value = value
this._size++
return this
}
public delete(key: number): boolean {
let node = this.setNode(key)
if (node) {
this._size--
node.Value = undefined
return true
} else {
return false
}
}
(4)节点遍历
做完前面的操作,map基本也就做完了。本质map并不适合用于做遍历操作,如果有数据需要遍历还是使用数组合适。当然还是会有需要遍历的情况,这个时候其实就和其他的基本一样,用简单的bfs或者dfs就可以了(也只会简单的,苦笑)。
Iterator、forEach,这两种Foreach的话就是每次遍历都调用一下函数就好了。但对于Iterator迭代器来说,需要去继承原IterableIterator的一个接口,这个接口主要是next需要实现,throw和return等处理可以可以补继承。对于next其实是返回包含数据以及是否遍历完成的一个接口,如果还没遍历结束,done就是false,同时value就是数据,如果遍历结束,那么done就为true。就可以不断next来达到迭代的效果。
到这里可以完全实现一个Map接口的01字典树了,关于遍历的代码看就再看最后完整代码部分吧,还挺多的。
4.测试与效率对比
(1)测试
做完之后还是要测试一下是否可以用,写了一个脚本简单测试一下
console.log("# TestTreeEnable")
let tree = new TreeMap()
console.log("[set]")
tree.set(1, 1) // tree.set(-1, 10000)
tree.set(2, 10000)
tree.set(2000, 10000)
console.log("[get]")
console.log(tree.get(1))
console.log(tree.get(2))
console.log(tree.get(2000))
console.log(tree.get(-2000))
console.log("[has]")
console.log(tree.has(1))
console.log(tree.has(2))
console.log(tree.has(2000))
console.log(tree.has(-2000))
console.log("[delete]")
console.log(tree.delete(2000))
console.log(tree.has(2000))
tree.set(2000, 10000)
console.log("[forEach]")
tree.forEach((value: number, key: number, map: Map<number, number>) => {
console.log(value, key)
})
console.log("[entries]")
console.log(Array.from(tree.entries()))
console.log("[keys]")
console.log(Array.from(tree.keys()))
console.log("[values]")
console.log(Array.from(tree.values()))
console.log("[clear]")
tree.clear()
console.log(Array.from(tree.entries()))
console.log("[tree]")
console.log(tree)
执行结果,来说还是挺好的,完成了目标(其实改了挺多bug)
TestTree.js:11 # TestTreeEnable
TestTree.js:12 [set]
TestTree.js:16 [get]
TestTree.js:17 1
TestTree.js:18 10000
TestTree.js:19 10000
TestTree.js:20 undefined
TestTree.js:21 [has]
TestTree.js:22 true
TestTree.js:23 true
TestTree.js:24 true
TestTree.js:25 false
TestTree.js:26 [delete]
TestTree.js:27 true
TestTree.js:28 false
TestTree.js:30 [forEach]
TestTree.js:32 1 1
TestTree.js:32 10000 2
TestTree.js:32 10000 2000
TestTree.js:34 [entries]
TestTree.js:35 (3) [Array(2), Array(2), Array(2)]
TestTree.js:36 [keys]
TestTree.js:37 (3) [1, 2, 2000]
TestTree.js:38 [values]
TestTree.js:39 (3) [1, 10000, 10000]
TestTree.js:40 [clear]
TestTree.js:42 []
TestTree.js:43 [tree]
TestTree.js:44 TreeMap {root: Node, _size: 0, Symbol(Symbol.toStringTag): "TreeNode"}
(2)效率测试
测试的效率主要是看set,get,delete,和has的效率对比,所以写的脚本如下
console.log("# TestEfficiency")
let count = 20000
let cur = new Date().getTime()
let last = 0
for (let i = 0; i < count; i++) {
map.set(i, i)
}
last = cur
cur = new Date().getTime()
console.log("[set]time:" + (cur - last))
for (let i = 0; i < count; i++) {
map.get(i)
}
last = cur
cur = new Date().getTime()
console.log("[get]time:" + (cur - last))
for (let i = 0; i < count; i++) {
map.has(i)
}
last = cur
cur = new Date().getTime()
console.log("[has]time:" + (cur - last))
for (let i = 0; i < count; i++) {
map.delete(i)
}
last = cur
cur = new Date().getTime()
console.log("[delete]time:" + (cur - last))
首先是map<number,number>的效率
TestTree.js:48 # TestEfficiency
TestTree.js:57 [set]time:2
TestTree.js:63 [get]time:1
TestTree.js:69 [has]time:1
TestTree.js:75 [delete]time:2
然后是自己写的字典树的效率
TestTree.js:48 # TestEfficiency
TestTree.js:57 [set]time:8
TestTree.js:63 [get]time:5
TestTree.js:69 [has]time:3
TestTree.js:75 [delete]time:4
糟糕,是被比下去的感觉(苦笑),数量提升到2000000,自己写的字典各项时间差距在4-7倍左右。虽然不知道是map怎么去实现的,但效率确实很好,这样我这个算是白写了,只能说了解了一下字典树的处理了
5.结尾(结束啦感谢观看)
注意,仅供实践参考,在实际项目使用需谨慎。这种基础结构,写出错或者有效率问题,对开发影响很大。
6.完整代码
/** 效率不如原本的Map,内存开销未知,存在一个存的数不是正数的问题*/
export class TreeMap implements Map<number, number> {
private root: Node = new Node()
private _size: number = 0
public get size(): number {
return this._size
}
private setNode(key: number): Node {
let cur: Node = this.root
if (key & 1) {
if (cur.Child1 == undefined) {
cur.Child1 = new Node()
}
cur = cur.Child1
} else {
if (cur.Child0 == undefined) {
cur.Child0 = new Node()
}
cur = cur.Child0
}
while (key & -2) {
// -2 = ~1
key = key >>> 1
// console.log("key:" + key)
// console.log("node1:" + (key & 1))
if (key & 1) {
if (cur.Child1 == undefined) {
cur.Child1 = new Node()
}
cur = cur.Child1
} else {
if (cur.Child0 == undefined) {
cur.Child0 = new Node()
}
cur = cur.Child0
}
}
return cur
}
private getNode(key: number): Node | undefined {
let cur: Node | undefined = this.root
if (key & 1) {
cur = cur.Child1
} else {
cur = cur.Child0
}
while (key & -2) {
// -2 = ~1
key = key >>> 1
if (cur == undefined) {
break
}
if (key & 1) {
cur = cur.Child1
} else {
cur = cur.Child0
}
}
return cur
}
public clear(): void {
this.root.Child0 = undefined
this.root.Child1 = undefined
this._size = 0
}
public get(key: number): number | undefined {
return this.getNode(key)?.Value
}
public has(key: number): boolean {
return this.getNode(key)?.Value != undefined
}
public set(key: number, value: number): this {
this.setNode(key).Value = value
this._size++
return this
}
public delete(key: number): boolean {
let node = this.setNode(key)
if (node) {
this._size--
node.Value = undefined
return true
} else {
return false
}
}
public forEach(callbackfn: (value: number, key: number, map: Map<number, number>) => void, thisArg?: any): void {
let iterator = new NodeIterableIterator(this.root)
let cur = iterator.next()
if (thisArg == undefined) {
thisArg = this
}
while (!cur.done) {
callbackfn.call(thisArg, cur.value[1], cur.value[0], this)
cur = iterator.next()
// console.log(cur)
}
}
public entries(): IterableIterator<[number, number]> {
return new NodeIterableIterator(this.root)
}
public keys(): IterableIterator<number> {
return new KeyIterableIterator(this.root)
}
public values(): IterableIterator<number> {
return new ValueIterableIterator(this.root)
}
public [Symbol.iterator](): IterableIterator<[number, number]> {
return new NodeIterableIterator(this.root)
}
public [Symbol.toStringTag]: string = "TreeNode"
}
class Node {
Value: number | undefined = undefined
Child0: Node | undefined = undefined
Child1: Node | undefined = undefined
}
class NodeIterableIterator implements IterableIterator<[number, number]> {
private root: Node
private nodes: Array<Node>
private keys: Array<number>
private deeps: Array<number>
public constructor(root: Node) {
this.root = root
this.nodes = new Array<Node>()
this.keys = new Array<number>()
this.deeps = new Array<number>()
this.pushChild(this.root, 0, -1)
}
public [Symbol.iterator](): IterableIterator<[number, number]> {
return new NodeIterableIterator(this.root)
}
public next(): IteratorResult<[number, number]> {
let cur = this.nodes.pop()
let key = this.keys.pop() as number
let deep = this.deeps.pop() as number
let result: IteratorResult<[number, number]> | undefined
while (cur != undefined) {
this.pushChild(cur, key, deep)
if (cur.Value == undefined) {
cur = this.nodes.pop()
key = this.keys.pop() as number
deep = this.deeps.pop() as number
} else {
result = {
done: false,
value: [key, cur.Value],
}
break
}
}
if (result == undefined) {
result = {
done: true,
value: [0, 0],
}
}
return result
}
public return(value: any): IteratorResult<[number, number]> {
console.log("return:" + value)
let result: IteratorResult<[number, number]> = {
done: true,
value: [0, 0],
}
return result
}
private pushChild(node: Node, key: number, deep: number) {
deep++
if (node.Child0 != undefined) {
this.nodes.push(node.Child0)
this.deeps.push(deep)
this.keys.push(key)
}
if (node.Child1 != undefined) {
this.nodes.push(node.Child1)
this.deeps.push(deep)
this.keys.push(key + (1 << deep))
}
}
}
class ValueIterableIterator implements IterableIterator<number> {
// value 也可以通过Node做一下装饰器获得
// 但考虑到不需要key所以使用了放弃了,更加节省内存提高效率
private root: Node
private nodes: Array<Node>
public constructor(root: Node) {
this.root = root
this.nodes = new Array<Node>()
this.pushChild(this.root)
}
public [Symbol.iterator](): IterableIterator<number> {
return new ValueIterableIterator(this.root)
}
public next(): IteratorResult<number> {
let cur = this.nodes.pop()
let result: IteratorResult<number> | undefined
while (cur != undefined) {
this.pushChild(cur)
if (cur.Value != undefined) {
result = {
done: false,
value: cur.Value,
}
break
} else {
cur = this.nodes.pop()
}
}
if (result == undefined) {
result = {
done: true,
value: 0,
}
}
return result
}
private pushChild(node: Node) {
if (node.Child0 != undefined) {
this.nodes.push(node.Child0)
}
if (node.Child1 != undefined) {
this.nodes.push(node.Child1)
}
}
}
class KeyIterableIterator implements IterableIterator<number> {
// Key的功能和Node的基本一致所以就不更改了
// 做成一个装饰器,虽然会损失一点性能
private nodeIterator: NodeIterableIterator
private root: Node
public constructor(root: Node) {
this.root = root
this.nodeIterator = new NodeIterableIterator(root)
}
[Symbol.iterator](): IterableIterator<number> {
return new KeyIterableIterator(this.root)
}
next(): IteratorResult<number> {
let result: IteratorResult<number>
let cur = this.nodeIterator.next()
if (cur.done) {
result = {
done: true,
value: 0,
}
} else {
result = {
done: false,
value: cur.value[0],
}
}
return result
}
}