并查集用于处理一些不交集的合并和查询问题,有两个主要的功能,一是判断任意的两个元素是否处在同一个集合,二是按照要求合并不同的集合。
并查集的基本操作是查找(Find)和合并(Uion)。
具体的代码如下:
int father[n]; //记录父亲节点
int height[n]; //记录当前高度(用于合并)
void initial(){ //初始化,节点的父亲节点都为自己,高度都为 0 .
for(int i = 0;i < n;++i){
father[i] = i;
height[i] = 0;
}
return;
}
int Find(int n){ //查找结点。
if(n != father[n]){
father[n] = Find(father[x]); //路径压缩,如果自己不是根节点,那么找到它父亲节点的根节点当自己的根节点。
}
return father[n];
}
void Union(int x,int y){
x = father[x];
y = father[y];
if(x != y){
if(height[x] > height[y]){
father[y] = x;
/*不用给height[x]++,因为 x 的 height 本来就大,并小的不会影响它。*/
}else if(height[x] < height[y]){
father[x] = y;
}else{
father[x] = y;
hright[y]++; //指定一个规则即可,也可以将 y 并入 x。
}
}
return;
}
以上的代码为最优代码,即保证并查集的树状结构高度最小,保证查找操作的效率最高,在实际的使用中可以不用这么复杂,比如在 Find 的时候,取消路径压缩;在Union 时取消对高度的判断,直接并入任意一个节点,但是这样在查找最终的祖先节点的时候要加一个循环结构。
并查集一般可以查找图的联通分量数,由此可以引申出来判断图是否联通(联通分量大于 1 不连通),还需要几条边可以让图成为连通图(联通分量数减 1 ),判断图是否为一棵树(1. 联通分量数为 1 ,2. 入度为 0 的节点有且只有 1 个,入度为 1 的节点有且只有 总结点数目 - 1 个)
e.g. 1. 连通图(判断是否联通)2.畅通工程(联通分量的个数)3. Is It a tree?(判断是否为树)
最小生成树的生成算法主要有 Kruskal(以下简称 K ) 和 Prim 算法,因为 K 算法编写起来比较简单,而且对于稀疏图(边数比较少)的运行效率较高,所以采用 K 算法来生成最小生成树。
K 法 最小生成树的主要原理是从边的集合中,依次选择权值最短的边,判断此边的两端顶点是否处在同一个并查集中,如果不在同一个并查集中,则加入该边合并两端顶点,如果在同一个并查集中,那么就舍弃该边。边的集合遍历完后,如果所有的顶点都在一个并查集中,那么最小生成树已经生成,如果不在同一个并查集中,那么最小生成树不存在。
具体的代码如下:
struct edge{
int start;
int end;
int lenght;
}
bool sort_edge(edge a,edge b){
/*按照边长度从小到大排序*/
}
int height[n];
int father[n];
edge Edge[n][n]; //存储边。
void initival(){
/*初始化,同上方代码*/
}
int Find(int x){
/*返回父亲节点*/
}
int Union(int x,int y){
/*合并节点*/
}
void Kruskal(int n,int edgeNumber){
initival(n);
sort(Edge,Edge + edgeNumber,sort_edge);
for(int i = 0;i < edgrNumber;++i){
edge current = edge[i];
if(Find(current.start) != Find(current.end)){\
/*需要这条边,按照题目要求自定义操作*/
}
}
int num = 0;
for(int i = 0;i < n;++i){
if(i = father[i]) num++; //判断联通分量个数
}
if(num == 1){
/*有最小生成树*/
}else //否则不存在。
}
解决一切跟最小生成树有关的问题。
e.g. 1. 还是畅通工程 2. 继续畅通工程
迪杰斯特拉(Dijkstra)算法可以用来解决单源最短路径问题,在运行过程中将所有的源点 V 分为集合 S 和 T,S 中包含已经确定的点,初始只包含源点,T中包含未被确定的点。从 T 中选出源点到当前点最近的那个点 u ,将 u 加入 S 中,然后对所有从 u 出发的点进行松弛操作,直到集合 T 为空,S 为 V 为止。
从 T 中选择最近的顶点 u 时,并不需要遍历所有的顶点,可以用一个优先级队列来实现。
具体的代码如下:
struct Edge{
int to;
int length;
Edge(int t,int l):to(t),length(l){}
};
struct Point(){
int number;
int distance;
Point(int n,int d):number(n),distance(d){}
friend operator < (Point a,Point b){ //重载 < 运算符,实现优先队列排序。
a.distance > b.distance;
}
};
vector<Edge> graph[maxn]; //邻接表实现的图。
int dis[maxn]; //存储从 S 到各点的最短路径。
void Dijkstra(int s){
priority_queue<Point> q;
dis[s] = 0; //源点到自身的距离为 0 。
q.push(Point(s,dis[s])); //压入队列。
while(!q.empty()){
int u = q.top().number; //获取与源点距离最近的点。
q.pop();
for(int i = 0;i < graph[u].size;++i){ //松弛所有以 u 为起点的路径。
int v = graph[u][i].to;
int d = graph[u][i].length;
if(dis[v] > dis[v] + d){
dis[v] = dis[u] + d;
q.push(Point(v,dis[v]));
}
}
}
return;
}
所有需要用最短路径解决的问题。图中点与点之间的最短路径,最小代价等。
e.g. 1. 最短路径问题
从图中选取入度为 0 的节点;删除该顶点并删除所有以它为起点的边;重复以上过程直到所有的点都被删除或者图中没有入度为 0 的点,说明不存在拓扑序列。
使用一个数组来储存各个顶点的入度,当入度为 0 时,将该顶点存入栈(或者队列)中。
具体的代码如下:
vector<int> graph[maxn]; //邻接表存储图。
int indegree[maxn];
bool topo(int n){
queue<int> node;
for(int i = 0;i < n;++i){
if(indegree[i] == 0){ //寻找第一个入度为 0 的点,放入队列中。
node.push[i];
}
}
int num = 0;
while(!node.empty()){
int u = node.front();
node.pop();
for(int i = 0;i < graph[u].size();++i){
int v = graph[u][i];
indegree[v]--;
if(ingree[v] == 0) node.push(v);
}
}
return num == n;
}
e.g. 1. 确定比赛名次等。
简单思路
动态规划
搜索专题