[数据结构]01字典树实现(ts)

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
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值