什么是 Quadtree
在计算机科学中,Quadtree(四叉树)是一种树形数据结构,用于将二维平面分割成多个矩形区域。每个节点可以有最多四个子节点,分别代表当前区域被分成的四个象限。如果当前区域内的所有对象都只包含在一个子节点中,则该节点为叶节点。使用 Quadtree 可以方便地以 O(log n) 的时间复杂度来查找、插入和删除平面上的对象。
TypeScript 简介
TypeScript 是一种由 Microsoft 开发的编程语言,是 JavaScript 的超集。TypeScript 的主要目标是提供更好的类型支持和代码可读性,以及更好的开发工具支持,比如编译器异常提示和代码自动补全。TypeScript 最终被编译为纯 JavaScript,因此可以在几乎所有现代浏览器和服务器环境中运行。
制作 Quadtree 的好处
将 Quadtree 用于网络客户端的开发中可以减少数据传输量,从而提高网络性能和响应速度。通常情况下,游戏中的大部分对象并没有彼此接触或者互相干扰,而 Quadtree 算法可以快速地区分每个对象所在的地区,只需要传输每个区域中的对象信息即可。
创建 Quadtree 类
首先,我们需要定义一个 Quadtree 的类。TypeScript 可以通过 class 关键字来定义类,具体代码如下:
// www.javascriptcn.com code example
class Quadtree {
x: number;
y: number;
width: number;
height: number;
objects: Object[];
constructor(x: number, y: number, width: number, height: number) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.objects = [];
}
// TODO: Add search, insert and delete methods
}这个类接收四个参数,分别为当前 Quadtree 的左上角坐标 x 和 y,以及宽度和高度。objects 属性则保存了当前区域中的所有对象。注意,这里我们并没有实现 Quadtree 的查找、插入和删除方法,这些方法会在下文中逐步补充。
实现 Quadtree 的划分与查找
接下来,我们需要实现 Quadtree 的划分与查找功能。Quadtree 本身就是一个递归的数据结构,我们可以先定义好一个 search 方法,用于搜索整个 Quadtree 中指定区域内的对象。
// www.javascriptcn.com code example
search(x1: number, y1: number, x2: number, y2: number): Object[] {
const results = [];
this._search(x1, y1, x2, y2, results);
return results;
}
private _search(x1: number, y1: number, x2: number, y2: number,
results: Object[]): void {
if (this.x + this.width < x1 || // Not in range
this.x > x2 ||
this.y + this.height < y1 || // Not in range
this.y > y2) {
return;
}
for (let obj of this.objects) {
results.push(obj);
}
if (this.width / 2 < 1 || this.height / 2 < 1) { // Reached leaf node
return;
}
if (this.children === undefined) { // Split into four children
this.children = [];
const halfWidth = this.width / 2;
const halfHeight = this.height / 2;
this.children.push(new Quadtree(this.x, this.y, halfWidth, halfHeight));
this.children.push(new Quadtree(this.x + halfWidth, this.y, halfWidth, halfHeight));
this.children.push(new Quadtree(this.x, this.y + halfHeight, halfWidth, halfHeight));
this.children.push(new Quadtree(this.x + halfWidth, this.y + halfHeight, halfWidth, halfHeight));
}
for (let child of this.children) {
child._search(x1, y1, x2, y2, results);
}
}这个方法接收四个参数,表示要查找的区域的左上角和右下角坐标。results 则是用于保存查找结果的数组。_search 函数则是 Quadtree 内部使用的递归实现,用于在当前节点以及其子节点中查找数据。具体实现如下:
首先,我们要判断当前节点是否包含在要查找的区域内,如果不在则直接返回。
其次,将当前节点中的所有对象添加到结果数组中。
然后,判断当前节点是否为叶节点(即没有子节点),如果是则返回。
最后,将当前节点的子节点依次递归调用 _search 方法即可。
实现 Quadtree 的插入与删除
下面,我们需要实现 Quadtree 的插入与删除方法。同样,我们可以先定义好一个 insert 方法,用于向 Quadtree 中插入一个对象。
// www.javascriptcn.com code example
insert(obj: Object, x: number, y: number): void {
if (this.x + this.width < x || // Not in range
this.x > x ||
this.y + this.height < y || // Not in range
this.y > y) {
return;
}
const halfWidth = this.width / 2;
const halfHeight = this.height / 2;
if (this.width / 2 < 1 || this.height / 2 < 1) { // Reached leaf node
this.objects.push(obj);
return;
}
if (this.children === undefined) { // Split into four children
this.children = [];
this.children.push(new Quadtree(this.x, this.y, halfWidth, halfHeight));
this.children.push(new Quadtree(this.x + halfWidth, this.y, halfWidth, halfHeight));
this.children.push(new Quadtree(this.x, this.y + halfHeight, halfWidth, halfHeight));
this.children.push(new Quadtree(this.x + halfWidth, this.y + halfHeight, halfWidth, halfHeight));
}
for (let child of this.children) {
child.insert(obj, x, y);
}
}这个方法接收三个参数,分别为要插入的对象和其在平面坐标系中的位置。具体实现如下:
首先,我们要判断当前节点是否包含在要插入的区域内,如果不在则直接返回。
其次,判断当前节点是否为叶节点,如果是则将对象直接添加到 objects 数组中。
然后,判断当前节点是否具有子节点,如果没有则将其在平面上分成四个象限,并依次递归调用子节点的 insert 方法。
最后,如果当前节点已经有子节点,则将对象递归地插入到其子节点中。
类似地,我们还可以实现一个 delete 方法来删除 Quadtree 中的指定对象。具体实现略。在实现时需要注意,如果当前节点中已经不存在任何对象,则需要将其子节点全部删除,以避免无效的内存占用。
示例代码
最后,我们用一段 TypeScript 代码来演示如何创建一个 Quadtree 对象,并向其中插入若干个对象:
// www.javascriptcn.com code example
const quadtree = new Quadtree(0, 0, 800, 600);
// Insert 10000 objects at random positions
for (let i = 0; i < 10000; i++) {
quadtree.insert({ id: i }, Math.random() * 800, Math.random() * 600);
}
// Search for all objects in the region (100, 100)-(200, 200)
const results = quadtree.search(100, 100, 200, 200);
console.log(results.length); // Should be around 1000在这个例子中,我们首先创建了一个四叉树对象 quadtree,范围为整个屏幕(800x600)。然后,我们在随机位置上插入了 10000 个对象。最后,我们搜索了一个指定的区域(100, 100)到(200,200),并输出了结果数组的长度。
通过这个例子可以看出,Quadtree 算法可以快速地定位到指定区域内的所有对象,并且不需要传输整个平面上的所有对象的信息,可以提高网络性能和响应速度。同时,使用 TypeScript 进行开发也可以提高代码的可读性和可维护性,避免了一些典型的 JavaScript 缺陷。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67d01effe46428fe9ecbf5a9