从拥有多个属性的报表集合(数据库)中,寻找具有特定属性且位于指定范围内的元素,这列问题被称为范围搜索。
请编写一个程序,对于二维平面上点的集合,例举出给定范围内的点。另外,给定的点集合无法进行点的添加和删除操作。
输入:
n
x0 y0
x1 y1
…
xn-1 yn-1
q
sx0 tx0 sy0 ty0
sx1 tx1 sy1 ty1
…
sxq-1 txq-1 sy1-1 tyq-1
其中第一行的n表示集合里点的数量。接下来的n行输入第 i 个点的2个整数坐标xi、yi。
接下来1行输入区域数q。再接下来q行各输入4个整数 s x i 、 t x i 、 s y i 、 t y i sx_i、tx_i、sy_i、ty_i sxi、txi、syi、tyi来代表各个区域。
输出:
对于每个区域,按编号升序输出点集合中满足 s x i ≤ x ≤ t x i sx_i≤x≤tx_i sxi≤x≤txi且 s y i ≤ y ≤ t y i sy_i≤y≤ty_i syi≤y≤tyi的点的编号,每个编号占1行,每个区域输出完毕后空1行(不存在满足条件的点时仅输出1个空行)
限制:
0 ≤ n ≤ 500000
0 ≤ q ≤ 20000
-1000000000 ≤ x, y, sx, tx, sy, ty ≤ 1000000000
sx ≤ tx
sy ≤ ty
各区域范围内的点不超过100个。
6
2 1
2 2
4 2
6 2
3 3
5 4
2
2 4 0 4
4 10 2 5
0
1
2
4
2
3
5
在获取点集合后不再进行插入和删除操作,也就是说,在进行查询之前我们已经获得了静态数据集合。一般说来,这类算法需要考虑插入与删除等操作,但我们先处理这个简易的范围搜索问题。
首先我们将问题简化,把点集合从二维空间缩小到一维的x轴上,考虑如何列举出x轴上给定区域(范围)内的点,即一维的范围搜索。
首先通过下述算法将给定点集合制成二叉树:
np = 0//初始化结点编号
make1DTree(0, n)
make1DTree(l, r)
if !(l < r)
return NIL
以x坐标为基准将p中从1到r的点(不包含r)按升序排序
mid = (l + r) / 2
t = np++ //设置二叉树的结点编号
T[t].location = mid //P中的位置
T[t].l = make1DTree(l, mid) //前半部分生成子树
T[t].r = make1DTree(mid + 1, r) //后半部分生成子树
return t
上述算法基于递归函数make1DTree(l, r),可以用点集合P中从 l l l到 r(不包含r)的元素生成子树。算法最初调用make1DTree(0, n)意在将整个点集合作为对象生成二叉树。
make1DTree(l, r)用于确定二叉树的1个结点并返回该结点的编号t。首先我们以x为基准将指定范围内的点按升序排序,然后计算序列中央元素的下标mid,以mid为界将范围内的点集合一分为二。这个mid的值就是结点t在P内的位置。
接下来递归调用make1DTree(l, mid)和make1DTree(mid + 1, t),分别生成结点t的左子树和右子树,这两个函数的返回值就是结点t的左子结点 l l l和右子结点 r。
要例举出指定范围内的点,只需对上述二叉搜索树应用如下算法:
find(v, sx, tx)
x = P[T[v].location].x
if sx <= x && x <= tx
print P[T[v].location]
if T[v].l != NIL && sx <= x
find(T[v].l, sx, tx)
if T[v].r != NIL && x <= tx
find(T[v].r, sx, tx)
搜索从二叉搜索树的根结点开始,检查当前结点所代表的点是否包含于指定范围(sx与tx之间),如果包含则输出该点(结点编号或坐标)。此外,如果该点大于等于下限sx则递归搜索左子树,小于等于上限tx则递归搜索右子树。
这个算法可以扩展至k为空间。我们只需要构建名为“KD树”的数据结构,就可以搜索指定区域内的点了。
kD树的生成方法多种多样,这类要介绍的是针对二维平面的基本方法。k维算法的基本思路与一维算法的一样,都是需对点进行排序,然后取中间值作为根结点来构建树。只不过,k维算法的排序基准会根据树的深度不同而变化。
处理一维的问题时,我们只以x的值为基准进行了排序,但点分布到二维空间后,就需对x轴和y轴分别排序。至于排序要以哪个轴为基准,则是按照树的深度进行循环。比如深度为偶数时以x轴为基准,为奇数时以y轴为基准,二者交替出现。
下面是用这一方法生成二叉搜索树并进行范围搜索的算法:
make2DTree(l, r, depth)
if !(l < r)
return NIL
mid = (l + r) / 2
t = np++
if depth % 2 == 0
以x坐标为基准,将P中从1至r(不包含r)的点进行升序排列
else
以y坐标为基准,将p中从1至r(不包含r)的点进行升序排列
T[t].location = mid
T.[t].l = make2DTree(l, mid, depth + 1)
T.[t].r = make2DTree(mid + 1, r, depth + 1)
return t
find(v, sx, tx, sy, ty, depth)
x = p[T[v].location].x;
y = p[T[v].location].y;
if sx <= x && x <= tx && sy <= y && y <= ty
print P[T[v].location]
if depth & 2 == 0
if T[v].l != NIL && sx <= x
find(T[v].l, sx, tx, sy, ty, depth + 1)
if T[v].r != NIL && x <= tx
find(T[v].r, sx, tx, sy, ty, depth + 1)
else
if T[v].l != NIL && sy <= y
find(T[v].l, sx, tx, sy, ty, depth + 1)
if T[v].r != NIL && y <= ty
find(T[v].r, sx, tx, sy, ty, depth + 1)
make2DTree是make1DTree的扩展,在访问结点时多检查了结点的深度depth,根据深度的奇偶性来变更排序基准(x、y轴)。find函数也同样根据depth来区分两种情况进行搜索。
设点的数量为n,算法在构建树结构时需要进行logn(树高)次 O ( n l o g n ) O(nlogn) O(nlogn)的排序,因此复杂度为 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)。另外,设指定范围内点的数量为k,KD树的维度为d,那么搜索的算法复杂度为 O ( n 1 − 1 / d + k ) O(n^{1-1/d}+k) O(n1−1/d+k)。
#include
#include
#include
using namespace std;
class Node{
public:
int location;
int p, l, r;
Node() {}
};
class Point{
public:
int id, x, y;
Point() {}
Point(int id, int x, int y): id(id), x(x), y(y) {}
bool operator < (const Point &p) const {
return id < p.id;
}
void print(){
printf("%d\n", id);//使用比cout更快的printf函数
}
};
static const int MAX = 1000000;
static const int NIL = -1;
int N;
Point P[MAX];
Node T[MAX];
int np;
bool lessX(const Point &p1, const Point &p2) { return p1.x < p2.x; }
bool lessY(const Point &p1, const Point &p2) { return p1.y < p2.y; }
int makeKDTree(int l, int r, int depth){
if(!(l < r) ) return NIL;
int mid = (l + r) / 2;
int t = np++;
if(depth % 2 == 0){
sort(P + 1, P + r, lessX);
} else {
sort(P + 1, P + r, lessY);
}
T[t].location = mid;
T[t].l = makeKDTree(l, mid, depth + 1);
T[t].r = makeKDTree(mid + 1, r, depth + 1);
return t;
}
void find(int v, int sx, int tx, int sy, int ty, int depth, vector<Point> &ans){
int x = P[T[v].location].x;
int y = P[T[v].location].y;
if(sx <= x && x <= tx && sy <= y && y <= ty){
ans.push_back(P[T[v].location]);
}
if(depth % 2 == 0){
if(T[v].l != NIL){
if(sx <= x) find(T[v].l, sx, tx, sy, ty, depth + 1, ans);
}
if(T[v].r != NIL){
if(x <= tx) find(T[v].r, sx, tx, sy, ty, depth + 1, ans);
}
} else {
if(T[v].l != NIL){
if(sy <= y) find(T[v].l, sx, tx, sy, ty, depth + 1, ans);
}
if(T[v].r != NIL){
if(y <= ty) find(T[v].r, sx, tx, sy, ty, depth + 1, ans);
}
}
}
int main(){
int x, y;
scanf("%d", &N);
for(int i = 0; i < N; i++){
scanf("%d %d", &x, &y);
P[i] = Point(i, x, y);
T[i].l = T[i].r = T[i].p = NIL;
}
np = 0;
int root = makeKDTree(0, N, 0);
int q;
scanf("%d", &q);
int sx, tx, sy, ty;
vector<Point> ans;
for(int i = 0; i < q; i++){
scanf("%d %d %d %d", &sx, &tx, &sy, &ty);
ans.clear();
find(root, sx, tx, sy, ty, 0, ans);
sort(ans.begin(), ans.end());
for(int j = 0; j < ans.size(); j++){
ans[j].print();
}
printf("\n");
}
return 0;
}