并查集

This commit is contained in:
estomm
2021-04-05 17:06:38 +08:00
parent 9233a697e6
commit 8fe396c09a
23 changed files with 285 additions and 120 deletions

View File

@@ -20,13 +20,32 @@ int main(){
// }
// vector<int> vec;
vector<int> vec;
priority_queue<int> pir(vec,[](const int a,const int b){
if(a>b){
return true;
}
else{
return false;
}})
// 构造边的对象
struct Edge
{
int start;
int end;
int weight;
Edge(int s,int e,int w){
start=s;
end=e;
weight=w;
}
// 重写<运算符
bool operator<(const Edge& a)const{
return a.weight < weight;
}
};
priority_queue<Edge> pri;
Edge e1(1,2,3);
Edge e2(3,2,1);
Edge e3(2,1,4);
pri.push(e1);
pri.push(e2);
pri.push(e3);
while(!pri.empty()){
cout<<pri.top().weight<<endl;
pri.pop();
}
return 0;
}

View File

@@ -1,5 +1,6 @@
# 哈希表
> 包含集合和映射。即unordered_set和unordreed_map两种数据结构的实现方式。
## 1 哈希表简介

View File

View File

View File

@@ -0,0 +1,23 @@
# 并查集
## 1 概念
### 定义
并查集被很多OIer认为是最简洁而优雅的数据结构之一主要用于解决一些元素分组的问题。它管理一系列不相交的集合并支持两种操作
* 合并Union把两个不相交的集合合并为一个集合。
* 查询Find查询两个元素是否在同一个集合中。
## 2 题目——亲戚问题
> ### 题目背景
> * 若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
> ### 题目描述
> * 规定x和y是亲戚y和z是亲戚那么x和z也是亲戚。如果x,y是亲戚那么x的亲戚都是y的亲戚y的亲戚也都是x的亲戚。
> ### 输入格式
> * 第一行三个整数n,m,pn<=5000,m<=5000,p<=5000分别表示有n个人m个亲戚关系询问p对亲戚关系。
> * 以下m行每行两个数MiMj1<=MiMj<=N表示Mi和Mj具有亲戚关系。
> * 接下来p行每行两个数PiPj询问Pi和Pj是否具有亲戚关系。
> ### 输出格式
> * P行每行一个YesNo。表示第i个询问的答案为“具有”或“不具有”亲戚关系。

View File

@@ -10,7 +10,8 @@
>参考文献
> [https://www.cnblogs.com/msymm/p/9769915.html](https://www.cnblogs.com/msymm/p/9769915.html)
> * [https://www.cnblogs.com/msymm/p/9769915.html](https://www.cnblogs.com/msymm/p/9769915.html)
> * [https://blog.csdn.net/jeffleo/article/details/53349825](https://blog.csdn.net/jeffleo/article/details/53349825)
## 1 问题分析

View File

@@ -26,22 +26,19 @@
## 3 算法流程
1. 首先第一步我们有一张图Graph有若干点和边
<p><img src="https://pic002.cnblogs.com/images/2012/426620/2012073015215729.jpg" alt="" width="200" height="168"></p>
![](image/2021-04-05-16-21-06.png)
2. 将所有的边的长度排序用排序的结果作为我们选择边的依据。这里再次体现了贪心算法的思想。资源排序对局部最优的资源进行选择排序完成后我们率先选择了边AD。
<p><img src="https://pic002.cnblogs.com/images/2012/426620/2012073015234045.jpg" alt="" width="200" height="168"></p>
![](image/2021-04-05-16-21-18.png)
3. 在剩下的变中寻找。我们找到了CE。这里边的权重也是5
<p><img src="https://pic002.cnblogs.com/images/2012/426620/2012073015313195.jpg" alt="" width="200" height="168"></p>
![](image/2021-04-05-16-21-32.png)
4. 依次类推我们找到了6,7,7即DFABBE。
<p><img src="https://pic002.cnblogs.com/images/2012/426620/2012073015332154.jpg" alt="" width="200" height="168"></p>
![](image/2021-04-05-16-21-42.png)
5. 下面继续选择, BC或者EF尽管现在长度为8的边是最小的未选择的边。但是现在他们已经连通了对于BC可以通过CE,EB来连接类似的EF可以通过EB,BA,AD,DF来接连。所以不需要选择他们。类似的BD也已经连通了这里上图的连通线用红色表示了。最后就剩下EG和FG了。当然我们选择了EG。
<p><img src="https://pic002.cnblogs.com/images/2012/426620/2012073015361536.jpg" alt="" width="200" height="168"></p>
![](image/2021-04-05-16-21-58.png)
## 4 算法效率

View File

@@ -31,81 +31,22 @@
## 3 算法过程
<table class="wikitable" border="1" cellspacing="2" cellpadding="5">
<tbody>
<tr><th>图例</th><th>说明</th><th>不可选</th><th>可选</th><th>已选V<sub>new</sub></th></tr>
<tr>
<td>&nbsp;
<p><img src="https://pic002.cnblogs.com/images/2012/426620/2012073015154494.png" alt="" width="200" height="168"></p>
</td>
<td>此为原始的加权连通图。每条边一侧的数字代表其权值。</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>
<p><img src="https://pic002.cnblogs.com/images/2012/426620/2012073015175038.png" alt="" width="200" height="168"></p>
</td>
<td>顶点<strong>D</strong>被任意选为起始点。顶点<strong>A</strong>、<strong>B</strong>、<strong>E</strong>和<strong>F</strong>通过单条边与<strong>D</strong>相连。<strong>A</strong>是距离<strong>D</strong>最近的顶点,因此将<strong>A</strong>及对应边<strong>AD</strong>以高亮表示。</td>
<td>C, G</td>
<td>A, B, E, F</td>
<td>D</td>
</tr>
<tr>
<td>&nbsp;
<p><img src="https://pic002.cnblogs.com/images/2012/426620/2012073016090032.png" alt="" width="200" height="168"></p>
</td>
<td>下一个顶点为距离<strong>D</strong>或<strong>A</strong>最近的顶点。<strong>B</strong>距<strong>D</strong>为9距<strong>A</strong>为7<strong>E</strong>为15<strong>F</strong>为6。因此<strong>F</strong>距<strong>D</strong>或<strong>A</strong>最近,因此将顶点<strong>F</strong>与相应边<strong>DF</strong>以高亮表示。</td>
<td>C, G</td>
<td>B, E, F</td>
<td>A, D</td>
</tr>
<tr>
<td><img src="https://pic002.cnblogs.com/images/2012/426620/2012073016130394.png" alt="" width="200" height="168"></td>
<td>算法继续重复上面的步骤。距离<strong>A</strong>为7的顶点<strong>B</strong>被高亮表示。</td>
<td>C</td>
<td>B, E, G</td>
<td>A, D, F</td>
</tr>
<tr>
<td>&nbsp;
<p><img src="https://pic002.cnblogs.com/images/2012/426620/2012073016143177.png" alt="" width="200" height="168"></p>
</td>
<td>在当前情况下,可以在<strong>C</strong>、<strong>E</strong>与<strong>G</strong>间进行选择。<strong>C</strong>距<strong>B</strong>为8<strong>E</strong>距<strong>B</strong>为7<strong>G</strong>距<strong>F</strong>为11。<strong>E</strong>最近,因此将顶点<strong>E</strong>与相应边<strong>BE</strong>高亮表示。</td>
<td>无</td>
<td>C, E, G</td>
<td>A, D, F, B</td>
</tr>
<tr>
<td>&nbsp;
<p><img src="https://pic002.cnblogs.com/images/2012/426620/2012073016154616.png" alt="" width="200" height="168"></p>
</td>
<td>这里,可供选择的顶点只有<strong>C</strong>和<strong>G</strong>。<strong>C</strong>距<strong>E</strong>为5<strong>G</strong>距<strong>E</strong>为9故选取<strong>C</strong>,并与边<strong>EC</strong>一同高亮表示。</td>
<td>无</td>
<td>C, G</td>
<td>A, D, F, B, E</td>
</tr>
<tr>
<td>
<p><img src="https://pic002.cnblogs.com/images/2012/426620/2012073016114494.png" alt="" width="200" height="168"></p>
</td>
<td>顶点<strong>G</strong>是唯一剩下的顶点,它距<strong>F</strong>为11距<strong>E</strong>为9<strong>E</strong>最近,故高亮表示<strong>G</strong>及相应边<strong>EG</strong>。</td>
<td>无</td>
<td>G</td>
<td>A, D, F, B, E, C</td>
</tr>
<tr>
<td>
<p><img src="https://pic002.cnblogs.com/images/2012/426620/2012073016100874.png" alt="" width="200" height="168"></p>
</td>
<td>现在所有顶点均已被选取图中绿色部分即为连通图的最小生成树。在此例中最小生成树的权值之和为39。</td>
<td>无</td>
<td>无</td>
<td>A, D, F, B, E, C, G</td>
</tr>
</tbody>
</table>
1. 此为原始的加权连通图。每条边一侧的数字代表其权值。结果集合{},可选集合{}。
![](image/2021-04-05-16-27-10.png)
2. 顶点D被任意选为起始点。顶点A、B、E和F通过单条边与D相连。A是距离D最近的顶点因此将A及对应边AD以高亮表示。结果集合为{D}.可选集合为{A,B,E,F}
![](image/2021-04-05-16-27-47.png)
3. 下一个顶点为距离D或A最近的顶点。B距D为9距A为7E为15F为6。因此F距D或A最近因此将顶点F与相应边DF以高亮表示。结果集合{A,D},可选集合{B,E,F}
![](image/2021-04-05-16-30-15.png)
4. 算法继续重复上面的步骤。距离A为7的顶点B被高亮表示。结果集合为{A,D,F},可选集合为{B,E,G}
![](image/2021-04-05-16-31-52.png)
5. 在当前情况下可以在C、E与G间进行选择。C距B为8E距B为7G距F为11。E最近因此将顶点E与相应边BE高亮表示。结果集合为{A,D,F,B},可选集合为{C,E,G}
![](image/2021-04-05-16-33-26.png)
6. 这里可供选择的顶点只有C和G。C距E为5G距E为9故选取C并与边EC一同高亮表示。结果集合为{A,D,F,B,E},可选集合为{C,G}
![](image/2021-04-05-16-34-22.png)
7. 顶点G是唯一剩下的顶点它距F为11距E为9E最近故高亮表示G及相应边EG。结果集合为{A,D,F,B,E,C},可选集合为{G}
![](image/2021-04-05-16-35-06.png)
8. 现在所有顶点均已被选取图中绿色部分即为连通图的最小生成树。在此例中最小生成树的权值之和为39。结果集合为{A,D,F,B,E,C,G},可选集合为{}
![](image/2021-04-05-16-35-28.png)
## 4 算法效率
顶点数V边数E。时间复杂度
@@ -117,5 +58,170 @@
## 5 算法实现
```C++
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
// 构造边的对象
struct Edge
{
int start;
int end;
int weight;
Edge(int s,int e,int w){
start=s;
end=e;
weight=w;
}
// 重写<运算符
bool operator<(const Edge& a)const{
return a.weight < weight;
}
};
// struct cmp{
// bool operator()(Edge a,Edge b){
// return b.weight > a.weight;
// }
// };
class Graph{
private:
int vertex_num; //图的顶点个数
int edge;
// 如果顶点的表示是离散的应该加一层映射
vector<vector<int>> arc; //邻接矩阵
public:
// 测试用的默认构造函数
Graph();
//构造函数,从命令行输入
Graph(int vertex_num_,int edge_);
//打印所有的边
void print_edge();
// 打印整个邻接矩阵
void print_arc();
//求最短路径
void Dijkstra(int begin);//单源最短路
void Floyd();//多源最短路
void Prim();//最小生成树
void Kruskal();//最小生成树
};
Graph::Graph(){
this->vertex_num = 7;
this->edge=12;
this->arc = vector<vector<int>>(vertex_num,vector<int>(vertex_num,INT_MAX));
// cout<<vertex_num<<endl;
// 初始化一个邻接矩阵.dijskra算法的图。
arc[0][1]=12;
arc[1][0]=12;
arc[0][6]=14;
arc[6][0]=14;
arc[0][5]=16;
arc[5][0]=16;
arc[1][2]=10;
arc[2][1]=10;
arc[1][5]=7;
arc[5][1]=7;
arc[2][3]=3;
arc[3][2]=3;
arc[2][4]=5;
arc[4][2]=5;
arc[2][5]=6;
arc[5][2]=6;
arc[3][4]=4;
arc[4][3]=4;
arc[4][5]=2;
arc[5][4]=2;
arc[4][6]=8;
arc[6][4]=8;
arc[5][6]=9;
arc[6][5]=9;
// 对角线自己到自己是0
for(int i=0;i<vertex_num;i++){
arc[i][i]=0;
}
}
// 从命令行输入一个邻接矩阵
Graph::Graph(int vertex_num_,int edge_){
this->vertex_num=vertex_num_;
this->edge=edge_;
this->arc=vector<vector<int>>(vertex_num,vector<int>(vertex_num,INT_MAX));
int beg=0,end=0,weight=0;
for(int i=0;i<edge_;i++){
cin>>beg>>end>>weight;
arc[beg][end]=weight;
// 如果是无向图则需要添加反向的路径
arc[end][beg]=weight;
}
}
void Graph::print_edge(){
for(int i=0;i<vertex_num;i++){
for(int j=0;j<vertex_num;j++){
if(arc[i][j]<INT_MAX)
cout<<"begin:"<<i<<"\tend:"<<j<<"\tweight:"<<arc[i][j]<<endl;
}
}
}
void Graph::print_arc(){
for(int i=0;i<vertex_num;i++){
for(int j=0;j<vertex_num;j++){
cout<<arc[i][j]<<"\t";
}
cout<<endl;
}
}
// 使用优先队列,挑选结果集合
// 经过分析不需要使用优先队列。与dijkstra算法一样。可以使用向量来表示是否选中。
void Graph::Prim(){
// 最小生成树开始的顶点。表示顶点是否被选中过.-1表示没有被选中。其他值表示它的上一个节点。
vector<int> result(vertex_num,-1);
// 用来记录到某个节点的距离的向量
vector<int> distance(vertex_num,INT_MAX);
// 用来记录到某个节点的前一个节点向量
vector<int> pre_point(vertex_num,-1);
// 首先选择第一个节点
int min_index=2;
int min_distance=INT_MAX;
result[min_index]=min_index;//表示从当前节点开始.前一个节点时自己。
for(int k=0;k<vertex_num-1;k++){
// 以当前节点更新距离结合
for(int i=0;i<vertex_num;i++){
if(result[i]<0 && arc[min_index][i]<distance[i]){
distance[i]=arc[min_index][i];
pre_point[i]=min_index;
}
}
min_distance=INT_MAX;
// 挑选距离最小的节点
for(int i=0;i<vertex_num;i++){
if(result[i]<0 && distance[i]<min_distance){
min_distance=distance[i];
min_index=i;
}
}
cout<<min_index<<"\t"<<pre_point[min_index]<<endl;
// 更新结果集合
result[min_index]=pre_point[min_index];
}
// 输出结果集合
for(int i=0;i<vertex_num;i++){
cout<<i<<"\t"<<result[i]<<"\t"<<arc[result[i]][i]<<endl;
}
}
int main(){
Graph g;
// g.print();
// g.Dijkstra(3);
// g.Floyd();
// g.Prim();
return 0;
}
```

View File

@@ -12,13 +12,18 @@ struct Edge
Edge(int s,int e,int w){
start=s;
end=e;
weight=2;
weight=w;
}
// 重写<运算符
bool operator<(const Edge& a)const{
return a.weight < weight;
}
};
// struct cmp{
// bool operator()(Edge a,Edge b){
// return b.weight > a.weight;
// }
// };
class Graph{
private:
@@ -111,7 +116,7 @@ void Graph::print_arc(){
}
}
void Graph::Dijkstra(int begin){
// 初始化结果集,到自己是0
// 初始化结果集,到自己是0
vector<int> result(vertex_num,INT_MAX);
result[begin]=0;
// 初始化距离集合,每次从中挑选
@@ -180,45 +185,58 @@ void Graph::Floyd(){
cout<<endl;
}
}
// 使用优先队列,挑选结果集合
// 经过分析不需要使用优先队列。与dijkstra算法一样。可以使用向量来表示是否选中。
// 与dijkstra算法十分相似。唯一的区别是距离更新的方式不同。dijkstra更新使用新的点做为中间节点计算起始点到该节点的距离更新最小距离。结果集中保存的是到某个点的最小距离。
// 而Prim算法是直接使用新的点和新添加到集合中的点进行更新。如果新添加到结果集合中的点周围有更短的距离则进行更新。而不是作为转接点。结果集中保存的是某个节点的前一个节点。构成一条边。
void Graph::Prim(){
// 最小生成树开始的顶点。
int start = 2;
// 初始化结果集合,边的集合。
vector<Edge> result;
// 初始化优先队列,用来挑选满足要求的最小的边
priority_queue<Edge,vector<Edge>> pri_edge;
// 最小生成树开始的顶点。表示顶点是否被选中过.-1表示没有被选中。其他值表示它的上一个节点。
vector<int> result(vertex_num,-1);
// 用来记录到某个节点的距离的向量
vector<int> distance(vertex_num,INT_MAX);
// 用来记录到某个节点的前一个节点向量。dijsktra也可以添加一个前置节点向量用来保存路径。
vector<int> pre_point(vertex_num,-1);
for(int k=0;k<vertex_num;k++){
// 添加边
// 首先选择第一个节点
int min_index=2;
int min_distance=INT_MAX;
result[min_index]=min_index;//表示从当前节点开始.前一个节点时自己。
for(int k=0;k<vertex_num-1;k++){
// 以当前节点更新距离结合
for(int i=0;i<vertex_num;i++){
// 检查i是不是已经被选中
for(auto e:result){
if(i==e.start || i==e.end){
continue;
}
}
if(i!=start && arc[start][i]<INT_MAX){
pri_edge.push(Edge(start,i,arc[start][i]));
if(result[i]<0 && arc[min_index][i]<distance[i]){
distance[i]=arc[min_index][i];
pre_point[i]=min_index;
}
}
// 挑选边.并将end点作为下一个边的扩展。继续添加边
Edge e = pri_edge.top();
pri_edge.pop();
result.push_back(e);
start = e.end;
min_distance=INT_MAX;
// 挑选距离最小的节点
for(int i=0;i<vertex_num;i++){
if(result[i]<0 && distance[i]<min_distance){
min_distance=distance[i];
min_index=i;
}
}
cout<<min_index<<"\t"<<pre_point[min_index]<<endl;
// 更新结果集合
result[min_index]=pre_point[min_index];
}
// 显示结果
for(auto e:result){
cout<<e.start<<"\t"<<e.end<<"\t"<<e.weight<<endl;
// 输出结果集合
for(int i=0;i<vertex_num;i++){
cout<<i<<"\t"<<result[i]<<"\t"<<arc[result[i]][i]<<endl;
}
}
void Graph::Kruskal(){
}
int main(){
Graph g;
// g.print();
// g.Dijkstra(3);
// g.Floyd();
g.Prim();
// g.Prim();
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB