提到KD Tree就想到陌上花开,提到陌上花开就想到CDQ分治,提到CDQ分治就发现我不会CDQ了……那就先复习一下CDQ吧
三维空间内n个元素,每个元素表示为( a i a_i ai, b i b_i bi, c i c_i ci),求每个点有多少个点在它的左下后方,即( a j ≤ a i a_j \leq a _i aj≤ai, b j ≤ b i b_j \leq b_i bj≤bi, c j ≤ c i c_j \leq c _i cj≤ci )。
有两种方法:
Function CDQ (S, F)
# 分割序列S为A、B
mid = len(S) / 2
A = S[1...mid]
B = S[mid+1...len(S)]
# 分别处理 A、B序列
CDQ(A)
CDQ(B)
# 按照y维度分别对A、B进行排序
F = sort(A, cmpy, F)
F = sort(B, cmpy, F)
# 设定指针a
a = 1
# 枚举B中每个b
for(b=1 to len(B))
while (a <= len(A) and A[a].y <= B[b].y)
# 插入树状数组,其中cnt表示重复数
BiTree.add(A[a].z, A[a].cnt)
i++
End
# 当前树状数组中的值均满足x和y维度上小于b,只需要再查询树状数组中有多少z维度上小于b的就好了
F[B[b].id] += BiTree.query(B[b].z)
End
for (a=1 to len(A))
BiTree.add(A[a].z, -A[a].cnt)
End
return F
End
Function main(S)
# 按照x排序
sort(S, cmpx)
# 合并重复数
S = merge(S)
F = CDQ(S)
for (i=1 to len(S))
ans[F[S[i].id] + S[i].cnt - 1] += S[i].cnt #注意此处需要考虑重复元素
return F
End
#include
#include
#include
#include
#include
#include
using namespace std;
struct apple{
int x,y,z,c,id;
apple(){}
apple(int a,int b,int m,int n,int p)
{
x=a;y=b;z=m;c=n;id=p;
}
}node[100010],no[100010];
int tree[200010],f[100010],ff[100010];
int m;
bool cmpx(apple a,apple b)
{
return a.x0;i-=lowbit(i))
ans+=tree[i];
return ans;
}
void add(int x,int z)
{
for(int i=x;i<=m;i+=lowbit(i))
tree[i]+=z;
}
void cdq(int l,int r)
{
if(l==r)
return;
int midd=(l+r)>>1;
cdq(l,midd);
cdq(midd+1,r);
sort(no+l,no+midd+1,cmpy);
sort(no+midd+1,no+r+1,cmpy);
int i,j,ans;
i=l;
for(j=midd+1;j<=r;j++)
{
while(i<=midd&&no[i].y<=no[j].y)
{
add(no[i].z,no[i].c);
i++;
}
f[no[j].id]+=query(no[j].z);
}
for(j=l;j1&&node[i].x==node[i-1].x&&node[i].y==node[i-1].y&&node[i].z==node[i-1].z)
no[nn].c++;
else
{
nn++;
no[nn]=apple(node[i].x,node[i].y,node[i].z,1,nn);
}
}
cdq(1,nn);
for(i=1;i<=nn;i++)
ff[f[no[i].id]+no[i].c-1]+=no[i].c;
for(i=0;i
相关题解: 题解 P3810 【模板】三维偏序 Shadows的博客
P4148 简单题
先来一道模板题。给一个N*N棋盘,两种操作:
强制在线,需要异或前面的答案。
如果不是强制在线,可以将时间作为第三维,进行CDQ分治。
但是强制在线只能KD Tree。
与动态线段树的build相同,只不过每次考察的是不同维度。注意每一个点有两组值,分别表示的是原始这个点的坐标,和这个点以下子树做代表的一个矩形的左上和右下。注意向上update,更新父节点的矩形。
如果失衡,需要把树还原成一个序列,然后重新建树
首先,直观的思想是将z作为时间,然后利用x和y维度建KD Tree。但是有一个问题,相等的点无法处理。我们举一个情况:
a(1, 2, 3)和b(3, 5, 3)的时候,是可以成功判断出 a ≤ b a\leq b a≤b的,但是如果a 在b的后面,我们发现就会漏掉这个答案。
解决办法是,在遍历到某一个点的时候,在包含此前所有点的大KD tree之外,将所有和它在z上相等的点捞出来,单独建一棵小KD Tree,同时查询两颗KD tree。然后将z相同的点都处理好后,将这一批z相同的点统一加入到大KD tree中。
题解 P3810 【模板】三维偏序(陌上花开)[demonovo 的博客]
二维KD Tree
建树: n l o g n nlogn nlogn
增删: l o g n logn logn
超方体查询: n \sqrt n n
kd-tree(KDT) 时间复杂度证明 q779
K Nearest Neighbors,最近的K个邻居
非参 惰性
对于一个样本,取其最近的K个(K一般为奇数)点,以这K个点中的多数点的类别为这个样本的预测。一般以欧几里得距离作为衡量标准。K值的选择尤为重要。
KNN算法(一) KNN算法原理 风凌天下
以下代码来自 KNN和KdTree算法实现 zoukankan
def predict(self, X):
knn_list = []
# 先取出k的点
for i in range(self.n):
# linalg = linear + algebra
# linalg.norm相关介绍:https://blog.csdn.net/silent1cat/article/details/120811844
dist = np.linalg.norm(X - self.X_train[i], ord=self.p)
knn_list.append((dist, self.y_train[i]))
# 考虑剩下的点,是否有比已经选中的k个点更近的,如果有,则更新这k个点的序列
for i in range(self.n, len(self.X_train)):
# 取出当前k个点中距离最大的一个的位置
max_index = knn_list.index(max(knn_list, key=lambda x: x[0]))
# 计算当前点的距离
dist = np.linalg.norm(X - self.X_train[i], ord=self.p)
# 如果当前点比最大的一个点近,则替换掉最大的点
if knn_list[max_index][0] > dist:
knn_list[max_index] = (dist, self.y_train[i])
# 统计
knn = [k[-1] for k in knn_list]
return Counter(knn).most_common()[0][0]
以下代码来自 KNN和KdTree算法实现 zoukankan
# 建立kdtree
def create(self, dataSet, label, depth=0):
if len(dataSet) > 0:
m, n = np.shape(dataSet)
self.n = n
# 当前层的维度
axis = depth % self.n
# 按照当前维度排序
dataSetcopy = sorted(dataSet, key=lambda x: x[axis])
# 取中点
mid = int(m / 2)
node = Node(dataSetcopy[mid], label[mid], depth)
# 如果是最顶层,需要记录树根
if depth == 0:
self.KdTree = node
# 递归构建左右子树
node.lchild = self.create(dataSetcopy[:mid], label, depth+1)
node.rchild = self.create(dataSetcopy[mid+1:], label, depth+1)
return node
return None
# 搜索kdtree的前count个近的点
def search(self, x, count = 1):
nearest = [] # 存储最近的k个点按照距离递减
# 初始化k个占位点
for i in range(count):
nearest.append([-1, None])
self.nearest = np.array(nearest)
def recurve(node):
if node is not None:
# 计算当前维度
axis = node.depth % self.n
# 计算测试点和当前点在当前维度上的差
daxis = x[axis] - node.data[axis]
# 如果小于进左子树,大于进右子树
if daxis < 0:
recurve(node.lchild)
else:
recurve(node.rchild)
# 计算预测点x到当前点的距离dist
dist = np.sqrt(np.sum(np.square(x - node.data)))
for i, d in enumerate(self.nearest):
# 如果有比现在最近的n个点更近的点,更新最近的点
if d[0] < 0 or dist < d[0]:
# 插入第i个位置的点
self.nearest = np.insert(self.nearest, i, [dist, node], axis=0)
# 删除最后一个多出来的点
self.nearest = self.nearest[:-1]
break
# 统计距离为-1的个数n
n = list(self.nearest[:, 0]).count(-1)
'''
self.nearest[-n-1, 0]是当前nearest中已经有的最近点中,距离最大的点。
self.nearest[-n-1, 0] > abs(daxis)代表以x为圆心,self.nearest[-n-1, 0]为半径的圆与axis
相交,说明在左右子树里面有比self.nearest[-n-1, 0]更近的点
'''
if self.nearest[-n-1, 0] > abs(daxis):
if daxis < 0:
recurve(node.rchild)
else:
recurve(node.lchild)
recurve(self.KdTree)
# nodeList是最近n个点的
nodeList = self.nearest[:, 1]
# knn是n个点的标签
knn = [node.label for node in nodeList]
return self.nearest[:, 1], Counter(knn).most_common()[0][0]
【机器学习】K-means 阿泽
随机生成k个中心a
while(终止条件)
for(i=1 to n)
计算i到每个中心距离,并以最近的作为i的聚类中心
End
for (j=1 to k)
用以中心j为聚类中心的点的中心位置取代中心j
End
End
KNN的核心算法kd-tree和ball-tree 吴智深
locality-sensetive hashing
哈希:减少冲突
局部敏感哈希:利用哈希冲突加速检索
LSH 的设计思想:如果两点距离较近,它们的哈希值大概率相同;反之则大概率不同。
设小半径 r 1 r_1 r1,大半径 r 2 r_2 r2,近似概率 p 1 p_1 p1,疏远概率 p 2 p_2 p2
若 p ∈ B ( q , r 1 ) p \isin B (q, r_1) p∈B(q,r1),则$P_{r_H}[h(q) = h§] \geq p_1 $
若 p ∉ B ( q , r 2 ) p \notin B (q, r_2) p∈/B(q,r2),则$P_{r_H}[h(q) = h§] \leq p_2 $
局部敏感哈希(LSH)越前浩波
Locality-Sensitive Hashing: a Primer
论文Product quantization for nearest neighbor search Herve Jegou, Matthijs Douze, Cordelia Schmid
理解 product quantization 算法 vividfree