图算法整理完毕

This commit is contained in:
yinkanglong_lab
2021-04-05 20:18:13 +08:00
parent 8fe396c09a
commit d51a3d0374
7 changed files with 470 additions and 115 deletions

View File

@@ -2,6 +2,6 @@
> 每日计划包括三方面的内容:数据结构与算法、基础知识与项目经历、联邦学习与恶意软件
- [ ] 数据结构预算法——图算法和随机化算法整理完成
- [x] 数据结构预算法——图算法和随机化算法整理完成
- [ ] 基础知识与项目经历——项目经历整理完成
- [ ] 联邦学习与恶意软件——TensorFlow federated学习完成part2

View File

@@ -1,5 +1,9 @@
# 并查集
> 参考文献
> * [https://zhuanlan.zhihu.com/p/93647900/](https://zhuanlan.zhihu.com/p/93647900/)
> * [https://blog.csdn.net/qq_41754350/article/details/81271567](https://blog.csdn.net/qq_41754350/article/details/81271567)
## 1 概念
### 定义
并查集被很多OIer认为是最简洁而优雅的数据结构之一主要用于解决一些元素分组的问题。它管理一系列不相交的集合并支持两种操作
@@ -8,16 +12,117 @@
* 查询Find查询两个元素是否在同一个集合中。
## 2 题目——亲戚问题
## 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个询问的答案为“具有”或“不具有”亲戚关系。
### 初始化
* 初始化所有的节点的根节点是自己
* 假如有编号为1, 2, 3, ..., n的n个元素我们用一个数组fa[]来存储每个元素的父节点(因为每个元素有且只有一个父节点,所以这是可行的)。一开始,我们先将它们的父节点设为自己。
### 查询
* 我们用递归的写法实现对代表元素的查询:一层一层访问父节点,直至根节点(根节点的标志就是父节点是本身)。要判断两个元素是否属于同一个集合,只需要看它们的根节点是否相同即可。
### 合并
* 合并操作也是很简单的,先找到两个集合的代表元素,然后将前者的父节点设为后者即可。当然也可以将后者的父节点设为前者,这里暂时不重要。本文末尾会给出一个更合理的比较方法
### 路径压缩
* 防止多个节点形成递归查询链太长,在查找过程中队路径进行压缩。
* 即将每一个当前的节点直接指向其根节点。
## 3 代码实现
```C++
class SetUnion{
public:
vector<int> vec;
// 初始化并查集
SetUnion(int n){
vec=vector<int>(n);
for(int i=0;i<n;i++){
vec[i]=i;
}
}
// 没有路径压缩的递归查找
int find_r(int x){
if(x==vec[x])return x;
else{
return find_r(vec[x]);
}
}
// 合并两个非连通图。
void merge(int i,int j){
vec[find(i)]=find(j);
}
// 有路径压缩的递归查找
int find(int x){
if(x==vec[x]){
return x;
}
else{
vec[x]=find(vec[x]);
return vec[x];
}
}
};
```
## 4 题目——亲戚问题
### 题目背景
* 若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
### 题目描述
* 规定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个询问的答案为“具有”或“不具有”亲戚关系。
### 代码实现
```C++
#include <cstdio>
#define MAXN 5005
int fa[MAXN], rank[MAXN];
inline void init(int n)
{
for (int i = 1; i <= n; ++i)
{
fa[i] = i;
rank[i] = 1;
}
}
int find(int x)
{
return x == fa[x] ? x : (fa[x] = find(fa[x]));
}
inline void merge(int i, int j)
{
int x = find(i), y = find(j);
if (rank[x] <= rank[y])
fa[x] = y;
else
fa[y] = x;
if (rank[x] == rank[y] && x != y)
rank[y]++;
}
int main()
{
int n, m, p, x, y;
scanf("%d%d%d", &n, &m, &p);
init(n);
for (int i = 0; i < m; ++i)
{
scanf("%d%d", &x, &y);
merge(x, y);
}
for (int i = 0; i < p; ++i)
{
scanf("%d%d", &x, &y);
printf("%s\n", find(x) == find(y) ? "Yes" : "No");
}
return 0;
}
```

42
数据结构/5.4.cpp Normal file
View File

@@ -0,0 +1,42 @@
#include<vector>
#include<iostream>
using namespace std;
class SetUnion{
public:
vector<int> vec;
// 初始化并查集
SetUnion(int n){
vec=vector<int>(n);
for(int i=0;i<n;i++){
vec[i]=i;
}
}
// 没有路径压缩的递归查找
int find_r(int x){
if(x==vec[x])return x;
else{
return find_r(vec[x]);
}
}
// 合并两个非连通图。
void merge(int i,int j){
vec[find(i)]=find(j);
}
// 有路径压缩的递归查找
int find(int x){
if(x==vec[x]){
return x;
}
else{
vec[x]=find(vec[x]);
return vec[x];
}
}
};
int main(){
return 0;
}

View File

@@ -8,3 +8,10 @@
>* 图算法-Kruskal算法
# bellman ford 和 SPFA两个算法等到以后再学吧。我赌十块不需要。等到下次在复习的时候完善这两个算法已经没时间了
> 参考文献
> * [https://www.cnblogs.com/lfri/p/9521271.html](https://www.cnblogs.com/lfri/p/9521271.html)
> * [https://blog.csdn.net/qq_35644234/article/details/61614581](https://blog.csdn.net/qq_35644234/article/details/61614581)
> 这两个博客够了

View File

@@ -10,7 +10,10 @@
> 参考文献
> [https://www.cnblogs.com/ggzhangxiaochao/p/9070873.html](https://www.cnblogs.com/ggzhangxiaochao/p/9070873.html)
## 1 问题描述
* Kruskal算法是一种用来寻找**最小生成树的算法**由Joseph Kruskal在1956年发表。用来解决同样问题的还有Prim算法和Boruvka算法等。三种算法都是贪婪算法的应用。和Boruvka算法不同的地方是Kruskal算法在图中存在相同权值的边时也有效。
* Kruskal算法是一种用来寻找**最小生成树的算法**由Joseph Kruskal在1956年发表。
* 用来解决同样问题的还有Prim算法和Boruvka算法等。三种算法都是**贪婪算法**的应用。和Boruvka算法不同的地方是Kruskal算法在图中存在相同权值的边时也有效。
* 当点少但是关系复杂的时候使用prim算法进行点的贪心。
* 当点多但是关系很稀疏的时候使用kruskal算法那进行边的贪心
## 2 算法原理
@@ -48,63 +51,193 @@
## 5 算法实现
```C++
typedef struct
{
char vertex[VertexNum]; //顶点表
int edges[VertexNum][VertexNum]; //邻接矩阵,可看做边表
int n,e; //图中当前的顶点数和边数
}MGraph;
typedef struct node
{
int u; //边的起始顶点
int v; //边的终止顶点
int w; //边的权值
}Edge;
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
void kruskal(MGraph G)
{
int i,j,u1,v1,sn1,sn2,k;
int vset[VertexNum]; //辅助数组,判定两个顶点是否连通
int E[EdgeNum]; //存放所有的边
k=0; //E数组的下标从0开始
for (i=0;i<G.n;i++)
{
for (j=0;j<G.n;j++)
{
if (G.edges[i][j]!=0 && G.edges[i][j]!=INF)
{
E[k].u=i;
E[k].v=j;
E[k].w=G.edges[i][j];
k++;
}
}
}
heapsort(E,k,sizeof(E[0])); //堆排序,按权值从小到大排列
for (i=0;i<G.n;i++) //初始化辅助数组
{
vset[i]=i;
}
k=1; //生成的边数,最后要刚好为总边数
j=0; //E中的下标
while (k<G.n)
{
sn1=vset[E[j].u];
sn2=vset[E[j].v]; //得到两顶点属于的集合编号
if (sn1!=sn2) //不在同一集合编号内的话,把边加入最小生成树
{
printf("%d ---> %d, %d",E[j].u,E[j].v,E[j].w);
k++;
for (i=0;i<G.n;i++)
{
if (vset[i]==sn2)
{
vset[i]=sn1;
}
}
}
j++;
}
}
// 一下算法可以借助一些东西进行改进。
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(int begin);//最小生成树
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;
}
}
// 并查集的设计实现,用来检验是否连通
class SetUnion{
public:
vector<int> vec;
// 初始化并查集
SetUnion(int n){
vec=vector<int>(n);
for(int i=0;i<n;i++){
vec[i]=i;
}
}
// 没有路径压缩的递归查找
int find_r(int x){
if(x==vec[x])return x;
else{
return find_r(vec[x]);
}
}
// 合并两个非连通图。
void merge(int i,int j){
vec[find(i)]=find(j);
}
// 有路径压缩的递归查找
int find(int x){
if(x==vec[x]){
return x;
}
else{
vec[x]=find(vec[x]);
return vec[x];
}
}
};
// 构造边的对象
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;
}
};
// 贪心选择最小的边。且使他们不连通。
void Graph::Kruskal(){
// 构建结果集合
vector<Edge> result;
// 对边初始化并进行排序
vector<Edge> vec;
for(int i=0;i<vertex_num;i++){
for(int j=i+1;j<vertex_num;j++){
if(arc[i][j]<INT_MAX){
vec.push_back(Edge(i,j,arc[i][j]));
}
}
}
sort(vec.begin(),vec.end());
// 创建点的并查集
SetUnion su(vertex_num);
int k=0;
for(int i=0;i<vec.size();i++){
Edge e = vec[i];
if(su.find(e.start)!=su.find(e.end)){
result.push_back(e);
su.merge(e.start,e.end);
}
}
// 显示结果
for(int i=0;i<result.size();i++){
cout<<result[i].start<<"\t"<<result[i].end<<"\t"<<result[i].weight<<endl;
}
}
int main(){
Graph g;
// g.print();
// g.Dijkstra(3);
// g.Floyd();
// g.Prim(2);
g.Kruskal();
return 0;
}
```

View File

@@ -7,12 +7,12 @@
>* 图算法-Kruskal算法
> 参考文献
> [https://www.cnblogs.com/ggzhangxiaochao/p/9070873.html](https://www.cnblogs.com/ggzhangxiaochao/p/9070873.html)
> * [https://www.cnblogs.com/ggzhangxiaochao/p/9070873.html](https://www.cnblogs.com/ggzhangxiaochao/p/9070873.html)
## 1 问题分析
* 普里姆算法Prim算法图论中的一种算法可在加权连通图里搜索**最小生成树**。意即由此算法搜索到的边子集所构成的树中不但包括了连通图里的所有顶点英语Vertex (graph theory)),且其所有边的权值之和亦为最小。
* 主要是**顶点的贪心思想**。
* 主要思想是**基于顶点的贪心思想**。
## 2 算法原理

View File

@@ -1,30 +1,10 @@
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
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; //图的顶点个数
@@ -43,7 +23,7 @@ public:
//求最短路径
void Dijkstra(int begin);//单源最短路
void Floyd();//多源最短路
void Prim();//最小生成树
void Prim(int begin);//最小生成树
void Kruskal();//最小生成树
};
@@ -153,7 +133,7 @@ void Graph::Dijkstra(int begin){
}
}
// 三层for循环。中间节点、起始点、终止点。不断更新表格。
void Graph::Floyd(){
// 初始化结果集合,一个用来记录最短距离,一个用来记录最短路径。
vector<vector<int>> distance(arc);
@@ -188,38 +168,46 @@ void Graph::Floyd(){
// 经过分析不需要使用优先队列。与dijkstra算法一样。可以使用向量来表示是否选中。
// 与dijkstra算法十分相似。唯一的区别是距离更新的方式不同。dijkstra更新使用新的点做为中间节点计算起始点到该节点的距离更新最小距离。结果集中保存的是到某个点的最小距离。
// 而Prim算法是直接使用新的点和新添加到集合中的点进行更新。如果新添加到结果集合中的点周围有更短的距离则进行更新。而不是作为转接点。结果集中保存的是某个节点的前一个节点。构成一条边。
void Graph::Prim(){
// 为了跟Dijkstra统一这里改成相同的添加方式。
void Graph::Prim(int begin){
// 最小生成树开始的顶点。表示顶点是否被选中过.-1表示没有被选中。其他值表示它的上一个节点。
vector<int> result(vertex_num,-1);
// 用来记录到某个节点的距离的向量
result[begin]=begin;//表示从当前节点开始.前一个节点时自己。
// 用来记录到某个节点的距离的向量。使用0表示节点已经计算过添加到结果当中了。
vector<int> distance(vertex_num,INT_MAX);
// 用来记录到某个节点的前一个节点向量。dijsktra也可以添加一个前置节点向量用来保存路径。
vector<int> pre_point(vertex_num,-1);
// 首先选择第一个节点
int min_index=2;
int min_distance=INT_MAX;
result[min_index]=min_index;//表示从当前节点开始.前一个节点时自己。
// 初始化刚开始的边集合。从当前点出发的边集合
for(int i=0;i<vertex_num;i++){
distance[i]=arc[begin][i];
pre_point[i]=begin;
}
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;
int min_index =0;
int min_distance=INT_MAX;
// 挑选距离最小的节点
for(int i=0;i<vertex_num;i++){
if(result[i]<0 && distance[i]<min_distance){
if(distance[i]!=0 && distance[i]<min_distance){
min_distance=distance[i];
min_index=i;
}
}
cout<<min_index<<"\t"<<pre_point[min_index]<<endl;
// cout<<min_index<<"\t"<<pre_point[min_index]<<endl;
// 更新结果集合
result[min_index]=pre_point[min_index];
distance[min_index]=0;
// 以当前节点更新距离结合
for(int i=0;i<vertex_num;i++){
if(distance[i]!=0 && arc[min_index][i]<distance[i]){
distance[i]=arc[min_index][i];
pre_point[i]=min_index;
}
}
}
// 输出结果集合
@@ -229,14 +217,94 @@ void Graph::Prim(){
}
// 并查集的设计实现,用来检验是否连通
class SetUnion{
public:
vector<int> vec;
// 初始化并查集
SetUnion(int n){
vec=vector<int>(n);
for(int i=0;i<n;i++){
vec[i]=i;
}
}
// 没有路径压缩的递归查找
int find_r(int x){
if(x==vec[x])return x;
else{
return find_r(vec[x]);
}
}
// 合并两个非连通图。
void merge(int i,int j){
vec[find(i)]=find(j);
}
// 有路径压缩的递归查找
int find(int x){
if(x==vec[x]){
return x;
}
else{
vec[x]=find(vec[x]);
return vec[x];
}
}
};
// 构造边的对象
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;
}
};
// 贪心选择最小的边。且使他们不连通。
void Graph::Kruskal(){
// 构建结果集合
vector<Edge> result;
// 对边初始化并进行排序
vector<Edge> vec;
for(int i=0;i<vertex_num;i++){
for(int j=i+1;j<vertex_num;j++){
if(arc[i][j]<INT_MAX){
vec.push_back(Edge(i,j,arc[i][j]));
}
}
}
sort(vec.begin(),vec.end());
// 创建点的并查集
SetUnion su(vertex_num);
int k=0;
for(int i=0;i<vec.size();i++){
Edge e = vec[i];
if(su.find(e.start)!=su.find(e.end)){
result.push_back(e);
su.merge(e.start,e.end);
}
}
// 显示结果
for(int i=0;i<result.size();i++){
cout<<result[i].start<<"\t"<<result[i].end<<"\t"<<result[i].weight<<endl;
}
}
int main(){
Graph g;
// g.print();
// g.Dijkstra(3);
// g.Floyd();
// g.Prim();
// g.Prim(2);
// g.Kruskal();
return 0;
}