互质的集合
题目14.1链接 Disjoint Set: Union Find Tree
这道题的思路主要采用了并查集的思想。相关讲解链接在一个很有意思的并查集详解。
- 第一种方法我采用了链接里的简单的数据结构,就采用pre[i]数组记录i的上级,当i=pre[i]是,i即为根节点。
#include <iostream>
#include <cstdio>
using namespace std;
const int Max = 10005;
int pre[Max];
int n, q, com, x, y;
int findRoot(int x)
{
int r = x;
while(pre[r]!=r)//找根节点
{
r = pre[r];
}
int j = x, i;
while(pre[j]!=r)//路径压缩,使得路径上的点均为二级结构
{
i = j;
pre[j] = r;
j = pre[i];
}
return r;
}
void join(int x, int y)
{
int fx = findRoot(x), fy = findRoot(y);
if(fx!=fy)//随机合并
{
pre[fx] = fy;
}
}
int main()
{
scanf("%d %d", &n, &q);
for(int i=0; i<n; i++)//初始化
{
pre[i] = i;
}
for(int i=0; i<q; i++)
{
scanf("%d %d %d", &com, &x, &y);
if(com==0)
{
join(x, y);
}
else
{
if(findRoot(x)==findRoot(y))
printf("1\n");
else
printf("0\n");
}
}
return 0;
}
- 第二种是书上的方法,直接定义了一个数据结构DisjointSet,其是一种用互质集合(一个元素不同时包含于多个集合的集合)对数据进行分类管理的数据结构。
其中每一个集合相当于一个生成树,没有回路,否则无法确定代表元素,即代表元素就是根。
这种思想可以用在Kruskal建立最小生成树里。这种数据结构有以下的操作:
操作函数 | 含义 |
---|---|
makeSet(x) | 创建仅包含元素x的新集合 |
findSet(x) | 求包含x元素的集合的“代表”元素 |
unite(x, y) | 合并指定的元素x、y |
same(x,y) | 判断x和y是否在同一集合,是则返回1,不是则返回0 |
以下的代码中定义的DisjointSet类可以作为模板库,记录下来方便以后直接使用。其复杂度低于O(log(N))。
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
int n, q, com, x, y;
class DisjointSet
{
public:
vector<int> rank, p;//rank记录集合树高,rank[x],则x为集合根,p指向父节点
DisjointSet(){}
DisjointSet(int size)
{
rank.resize(size, 0);
p.resize(size, 0);
for(int i=0; i<size; i++)
{
makeSet(i);
}
}
void makeSet(int x)//创建仅包含元素x的新集合
{
p[x] = x;
rank[x] = 0;
}
bool same(int x, int y)//判断x和y是否在同一集合
{
return findSet(x) == findSet(y);
}
void unite(int x, int y)//合并指定的元素及其集合
{
link(findSet(x), findSet(y));
}
void link(int x, int y)//合并函数
{
if(rank[x]>rank[y])//根据树的高度来确定合并策略
{
p[y] = x;
}
else
{
p[x] = y;
if(rank[x]==rank[y])
{
rank[y]++;
}
}
}
int findSet(int x)//寻找x的集合的根
{
if(x!=p[x])
{
p[x] = findSet(p[x]);
}
return p[x];
}
};
int main()
{
scanf("%d %d", &n, &q);
DisjointSet ds = DisjointSet(n);
for(int i=0; i<q; i++)
{
scanf("%d %d %d", &com, &x, &y);
if(com==0)
{
ds.unite(x, y);
}
else
{
if(ds.same(x, y))
printf("1\n");
else
printf("0\n");
}
}
return 0;
}
KD Tree
k-d树(k-dimensional树的简称),是一种分割k维数据空间的数据结构。主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索)。
KD Tree是BST(二叉搜索树)在多维空间的扩展,其实拓展到高维的时候也是类似的,只不过要考虑多个维度,最显而易见的想法就是对于每一层,我用不同的维度的数值作为key来进行比较。例如说,对于一个二维数据,我第一层用第一维,第二层用第二维,第三层第一维,第四层第二维……这样,一棵树可以比较均匀的建立。
- 范围搜索
题目14.2链接Range Search (kD Tree)
这道题是个二维范围搜索,以下是书上的KD Tree模板,留作后用,find是范围查找, makeKDTree是构造KD Tree。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
class Node//树的结点结构
{
public:
int location;//该树的结点在多维结点中对应的下标
int p, l, r;//父节点,左子结点,右子结点
Node(){};
};
class Point//多维结点类
{
public:
int id, x, y;//id保存结点的原始下标,x,y为多维key值
Point(){}
Point(int id, int x, int y): id(id), x(x), y(y) {}//可以使用Point(id, x, y)直接建立一个结构体对象
bool operator < (const Point &p) const{//定义一个升序的排列函数
return id<p.id;
}
void print(){//输出函数
printf("%d\n", id);
}
};
static const int MAX = 1000000;
static const int NIL = -1;
int N, np;
Point P[MAX];//存多维结点
Node T[MAX];//构造树结点
vector<Point> ans;//存储结果
//以下是两个升序函数,一个关于x,一个关于y
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)//构造KD Tree
{
if(l==r) return NIL;
int mid = (l+r)/2;
int t = np++;
if(depth%2==0)//轮流按x,y排序
sort(P+l, P+r, lessX);
else
sort(P+l, 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 && tx>=x && sy<=y && ty>=y)//在范围内则存起来
{
ans.push_back(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, ans);
}
if(T[v].r!=NIL && tx>=x)//有右子树
{
find(T[v].r, sx, tx, sy, ty, depth+1, ans);
}
}
else
{
if(T[v].l!=NIL && sy<=y)
{
find(T[v].l, sx, tx, sy, ty, depth+1, ans);
}
if(T[v].r!=NIL && ty>=y)
{
find(T[v].r, sx, tx, sy, ty, depth+1, ans);
}
}
}
int main()
{
int x, y, q;
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 sx, sy, tx, ty;
scanf("%d", &q);
for(int i=0; i<q; i++)
{
scanf("%d %d %d %d", &sx, &tx, &sy, &ty);
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");
ans.clear();
}
return 0;
}
- KNN搜索
题目链接The Closest M Points
先贴上别人的代码,以后有空自己研究一下。
#include<bits/stdc++.h>
#define sq(x) (x)*(x)
#define N (55555)
using namespace std;
int idx,k,n,m,q;
struct Node
{
int x[5];
bool operator < (const Node &u) const
{
return x[idx] < u.x[idx];
}
} P[N];
typedef pair<double,Node> PDN;
priority_queue<PDN> que;
struct KD_Tree
{
int sz[N<<2]; Node p[N<<2];
void build(int i,int l,int r,int dep)
{
if (l>r) return;
int mid=(l+r)>>1;
idx=dep%k;sz[i]=r-l;
sz[i<<1]=sz[i<<1|1]=-1;
nth_element(P+l,P+mid,P+r+1);
p[i]=P[mid];
build(i<<1,l,mid-1,dep+1);
build(i<<1|1,mid+1,r,dep+1);
}
void query(int i,int m,int dep,Node a)
{
if (sz[i]==-1) return;
PDN tmp=PDN(0,p[i]);
for(int j=0;j<k;j++)
tmp.first+=sq(tmp.second.x[j]-a.x[j]);
int lc=i<<1,rc=i<<1|1,dim=dep%k,flag=0;
if (a.x[dim]>=p[i].x[dim]) swap(lc,rc);
if (~sz[lc]) query(lc,m,dep+1,a);
if (que.size()<m) que.push(tmp),flag=1;
else
{
if (tmp.first<que.top().first) que.pop(),que.push(tmp);
if (sq(a.x[dim]-p[i].x[dim])<que.top().first) flag=1;
}
if (~sz[rc]&&flag) query(rc,m,dep+1,a);
}
} KDT;
int main()
{
while(~scanf("%d%d",&n,&k))
{
for(int i=0;i<n;i++)
for(int j=0;j<k;j++)
scanf("%d",&P[i].x[j]);
KDT.build(1,0,n-1,0);
scanf("%d",&q);
while(q--)
{
Node now;
for(int i=0;i<k;i++)
scanf("%d",&now.x[i]);
scanf("%d",&m); int t=0;
KDT.query(1,m,0,now); Node pp[21];
for(;!que.empty();que.pop())
pp[++t]=que.top().second;
printf("the closest %d points are:\n",t);
for(int i=m;i>0;i--)
{
printf("%d",pp[i].x[0]);
for(int j=1;j<k;j++)
printf(" %d",pp[i].x[j]);
puts("");
}
}
}
return 0;
}