图的创建和遍历
- 图的创建方式
- 邻接矩阵方法
- 邻接表方法
- 图的遍历方式
- DFS
- BFS
图的应用和操作
- 求连通分量问题
- 最小生成树问题
- prime算法
- 边为主的贪心算法
- AOV网
- 一种活动之间具有先行性关系的图
- 拓扑排序问题
- AOE网
- 一种活动的耗时关系的图
- 关键路径问题
- 求两点之间的最短路径
邻接矩阵创建并求解连通分量问题
注意:每展开一次深搜或者广搜,都会将一个连通的图进行完全遍历
所以连通分量的个数简化为调用dfs或者bfs的次数(设置一个全局变量进行解决)
邻接矩阵+广搜
#include<bits/stdc++.h> #define MAX 300 using namespace std; int n; int sum = 0; int A[MAX+1][MAX+1]; int visited[MAX+1]; //优化存储方式(对称矩阵的压缩存储 int bfsGragh(int pos) { sum++; queue<int> q; q.push(pos); visited[pos] = sum; while(!q.empty()){ //只要队列中非空,开始广搜 利用队列的性质开展广搜 //拿到队首元素 int t = q.front(); q.pop(); for(int i = t + 1;i <= n;i++){ //开始搜索 if(A[pos][i] && !visited[i]){ q.push(i); visited[i] = sum; } } } } int main() { cin>>n; for(int i = 1;i <= n;i++){ for(int j = 1;j <= n;j++){ cin>>A[i][j]; } } for(int i = 1;i <= n;i++){ if(!visited[i]){ //如果这个节点没有访问过 bfsGragh(i); } } cout<<sum; return 0; }
邻接表+深搜
#include<bits/stdc++.h> #define Max 100 using namespace std; /*typedef int DataType; struct ArcNode{ int index; ArcNode *next; }; struct VertexNode{ DataType data; ArcNode *first; }; VertexNode a[Max];*/ vector<int> v[Max]; int n; int sum = 0; int vis[Max]; int num[Max]; void dfsGragh(int pos) { //cout<<"调用一次,pos为"<<pos<<endl; for(int i = 0;i <= num[pos] - 1;i++){ if(!vis[v[pos][i]]){ vis[v[pos][i]] = 1; dfsGragh(v[pos][i]); } } } int main() { int m,temp1,temp2; cin>>n; cin>>m; //将数据存储 for(int i = 1;i <= m;i++){ cin>>temp1>>temp2; /* *如果只将数字按照大小存放一次的话,那么例题中按照写的DFS算法就会将7割裂出来变成独立的点 *因为我无法通过10找到7)使得连通分量数目增多 if(temp1 > temp2){ v[temp2].push_back(temp1); num[temp2]++; } else{ v[temp1].push_back(temp2); num[temp1]++; }*/ v[temp2].push_back(temp1); num[temp2]++; v[temp1].push_back(temp2); num[temp1]++; } for(int i = 1;i <= n;i++){ if(!vis[i]){ //cout<<i<<endl; vis[i] = 1; dfsGragh(i); //注意先后顺序,深搜会将一条线直接搜完,若在主函数中结束一次DFS,则有一条完整的连通分量 sum++; } } cout<<sum; return 0; }
最小生成树问题
两种算法:点优先和边优先
prime算法:
/*问题描述】 已知含有n个顶点(编号从1开始)的带权连通无向图,采用邻接矩阵存储,邻接矩阵以三元组的形式给出 只给出不包括主对角线元素在内的下三角这部分的元素,且不包括不邻接的顶点对。 请采用Prim算法,求该连通图从1号顶点出发的最小生成树的权值之和。 【输入形式】 第1行给出图中结点个数n和三元组的个数num,之后每行给出一个三元组,数之间用1个空格隔开。 (注意这里顶点的序号是从1到n,而不是0到n-1,程序里要小心!) 【输出形式】 求解的最小生成树的各条边、各边的权值、最小生成树的权值和 【样例输入】 5 8 2 1 7 3 1 6 3 2 8 4 1 9 4 2 4 4 3 6 5 2 4 5 4 2 【样例输出】 1-3:6 3-4:6 4-5:2 4-2:4 18 【样例说明】 权值是正整数,可能很大,但不需要考虑整型溢出问题*/ #include<bits/stdc++.h> #define MAX 30 #define intMAX 2147483647 using namespace std; int n; int g[MAX][MAX]; //已有点集到目的点的最少消费 struct { int lowcost; int dir; }closedge[MAX + 1]; int vis[MAX]; int MinRoad() { int result; int max = intMAX; for(int i = 1;i <= n;i++){ //lowcost要么为0(已经纳入点集),要么为最大值(没有路),要么为具体数值(也是MinRoad的主要遍历对象) //该If条件可以筛选掉前两个 if(closedge[i].lowcost < max && closedge[i].lowcost){ max = closedge[i].lowcost; result = i; } } return result; } //点集合的形式 void prime(int pos) { int maxNum = intMAX; int sum = 0; int v; //vis[pos] = 1; closedge[pos].lowcost = 0; for(int i = 1;i <= n;i++){ if(i != pos){ closedge[i].dir = pos; if(g[pos][i]){ closedge[i].lowcost = g[pos][i]; } else{ closedge[i].lowcost = intMAX; } } } //最小生成树若有N个顶点,则有N-1条边 for(int h = 1;h <= n - 1;h++){ //找到本趟中路权重最小的那个 v = MinRoad(); cout<<closedge[v].dir<<"-"<<v<<":"<<closedge[v].lowcost<<endl; sum += closedge[v].lowcost; //将找到的最小点纳入体系 closedge[v].lowcost = 0; for(int i = 1;i <= n;i++){ //遍历所有的点,看lowcost是否发生变化 if(g[i][v] && (g[i][v] < closedge[i].lowcost)){ closedge[i].dir = v; //cout<<"发生一次变化,此时目标点由 "<<pos<<"变为 "<<v<<endl; closedge[i].lowcost = g[i][v]; } } } cout<<sum; } int main() { int t1,t2,t3,m; cin>>n>>m; for(int i = 1;i <= n;i++){ for(int j = 1;j <= n;j++){ g[i][j] = 0; } } for(int i = 1;i <= m;i++){ cin>>t1>>t2>>t3; g[t1][t2] = t3; g[t2][t1] = t3; } prime(1); return 0; }
Kruskal算法(边优先):自己离散数学的做法
- 将各边的权重进行排序
- 从小到大选取,只要不构成回路,及边的两个端点属于两个不同的连通分量
/* kruskal算法思想 * 在kruskal算法中,相比与prim算法,采取的边优先的原则,所以在本种算法种需要记录边的信息 * - 将边进行排序(其中存储边信息时应该保留边的端点信息,终点信息,和边的权重 * - 难点:本算法的难点在于如何不形成回路 * 注意:最小生成树中的每条边都是桥,即切断后连接两个不同的连通分量 * 解决:设置一个辅助数组,对端点进行标记,若端点之间连通,则标记相同,在按照大小选边的时候增设一个if判断 * 来避免出现回路 */ #include<bits/stdc++.h> #define MAX 100 using namespace std; //边节点用于保留端点信息和权重信息 struct Edge{ char ori; char end; int weight; }edges[MAX + 1],minTree[MAX + 1]; struct Node{ int mark; bool in; }; int n; //根据输入特征构建的创建函数 int createGragh() { int count = 0; char orr,end; int m,x; for(int i = 1;i <= n - 1;i++){ cin>>orr>>m; //保存起点信息到数组中 for(int j = 1;j <= m;j++){ count++; edges[count].ori = orr; cin>>end>>x; edges[count].end = end; edges[count].weight = x; } } /* 测试输出 for(int i = 1;i <= count;i++){ cout<<edges[i].ori<<"-"<<edges[i].end<<":"<<edges[i].weight<<endl; }*/ return count; } void sortEdges(int count) { //对边按照权重进行排序(本题采用冒泡排序) Edge temp; bool flag = true; //对冒牌排序进行复习 for(int i = 1;i <= count - 1 && flag;i++){ //本次循环控制趟数(走N - 1趟) flag = false; for(int j = 1;j <= count - i;j++){ //1.保证不会下标越界 2.外层循环每走一次,多一个已经排序好的量 if(edges[j].weight > edges[j+1].weight){ temp = edges[j]; edges[j] = edges[j+1]; edges[j+1] = temp; flag = true; } } } } //核心算法函数生成最小生成树 void kruskal(int count) { int num = 0,sumWeight = 0; Node flag[n + 1]; //初始化标记数组,给每个顶点做不同的标记 for(int i = 1;i <= n;i++){ flag[i].mark = i; flag[i].in = false; } for(int i = 1;i <= count;i++){ if(flag[edges[i].ori - 64].mark != flag[edges[i].end - 64].mark){ //如果两个顶点在不同的连通分量 num ++; //保存边的信息 minTree[num] = edges[i]; sumWeight += edges[i].weight; //修改端点的mark值 if(flag[edges[i].end - 64].in == true){ //和源头节点在同一个连通分支+取了头节点的值作为Mark的标记,保证其全部修改为尾节点的Mark值 int ori = flag[edges[i].ori - 64].mark; for(int j = 1;j <= n;j++){ if(flag[j].mark == ori){ flag[j].mark = flag[edges[i].end - 64].mark; } } //flag[edges[i].ori - 64] = flag[edges[i].end - 64]; } else{ //反过来 int end = flag[edges[i].end - 64].mark; for(int j = 1;j <= n;j++){ if(flag[j].mark == end){ flag[j].mark = flag[edges[i].ori - 64].mark; } } } //修改端点的Bool信息 flag[edges[i].ori - 64].in = true; flag[edges[i].end - 64].in = true; } //最小生成树有N - 1条边 if(num == n-1) break; } cout<<sumWeight<<endl; } int main() { int edgeNum; cin>>n; edgeNum = createGragh(); sortEdges(edgeNum); kruskal(edgeNum); return 0; }
拓扑排序和关键路径问题
拓扑排序
/*4
0(空格)1(空格)2(空格)3
0(空格)1
1(空格)2
3(空格)2
-1(空格)-1
【样例输出】
3(空格)0(空格)1(空格)2(空格)*/
#include<bits/stdc++.h>
#define MAX 100
using namespace std;
typedef int DataType;
struct ArcNode{
int index;
ArcNode *next;
};
struct VertexNode{
DataType data;
ArcNode *first;
};
VertexNode G[MAX];
int indegree[MAX];
int n;
DataType result[MAX];
void FindID()
{
ArcNode *p;
for(int i = 0;i < n;i++){
indegree[i] = 0;
}
for(int i = 0;i < n;i++){
p = G[i].first;
while(p != NULL){
//有后驱节点
indegree[p->index]++;
p = p->next;
}
}
}
void TopoSort()
{
stack<int> s;
int count = 0,j;
ArcNode *p;
FindID();
for(int i = 0;i < n;i++){
//找到初始状态下无前驱的节点
if(indegree[i] == 0){
s.push(i);
}
}
while(!s.empty()){
//只要栈不为空
j = s.top(); s.pop();
result[count] = G[j].data;
//cout<<G[j].data<<" ";
count++;
p = G[j].first;
while(p != NULL){
indegree[p->index]--;
if(indegree[p->index] == 0){
s.push(p->index);
}
p = p->next;
}
}
if(count < n)
cout<<"ERROR";
else{
for(int i = 0;i < n;i++){
cout<<result[i]<<" ";
}
}
}
int main()
{
int temp1,temp2;
ArcNode *p;
cin>>n;
for(int i = 0;i < n;i++){
//放入节点数据值,并初始化指针为空
DataType data;
cin>>data;
G[i].data = data;
G[i].first = NULL;
}
cin>>temp1>>temp2;
while(temp1 != -1 && temp2 != -1){
p = new ArcNode;
p->index = temp2;
p->next = G[temp1].first;
G[temp1].first = p;
cin>>temp1>>temp2;
}
TopoSort();
return 0;
}
关键路径
/* 关键:
* 求解两个数组
* 输入格式:
*6 8
0 1 3
0 2 2
1 3 2
1 4 3
2 3 4
2 5 3
3 5 2
4 5 1
*/
#include<bits/stdc++.h>
#define MAX 500
using namespace std;
struct ArcNode{
int weight;
int index;
ArcNode *next;
};
struct VertexNode{
int data;
ArcNode *first;
};
VertexNode g[MAX + 1];
int indegree[MAX+1];
void createGragh(int n,int m)
{
ArcNode *p,*q;
int x,y,w;
for(int i = 0;i < n;i++){
g[i].data = i;
g[i].first = NULL;
}
for(int i = 0;i < m;i++){
cin>>x>>y>>w;
p = new ArcNode;
p->weight = w;
p->index = y;
p->next = NULL;
q = g[x].first;
if(g[x].first == NULL){
g[x].first = p;
}
else{
while(q->next != NULL)
q = q->next;
q->next = p;
}
}
}
void FindID(int n)
{
ArcNode *p;
for(int i = 0;i < n;i++){
p = g[i].first;
while(p != NULL){
//有后驱节点
indegree[p->index]++;
p = p->next;
}
}
/*for(int i = 0;i < n;i++){
cout<<indegree[i]<<" "<<endl;
}*/
}
int ve[MAX];
int vl[MAX];
void TopoOrder(int n)
{
FindID(n);
stack<int> s; stack<int> t;
int j;
ArcNode *p;
for(int i = 0;i < n;i++){
ve[i] = 0;
}
for(int i = 0;i < n;i++){
if(indegree[i] == 0){
s.push(i);
}
}
while(!s.empty()){
j = s.top(); s.pop();
t.push(j);
//count++;
p = g[j].first;
while(p != NULL){
if(--indegree[p->index] == 0){
s.push(p->index);
}
if(ve[j] + p->weight > ve[p->index]){
ve[p->index] = ve[j] + p->weight;
}
p = p->next;
}
}
//关键路径算法
for(int i = 0;i < n;i++){
vl[i] = ve[n - 1];
}
while(!t.empty()){
j = t.top(); t.pop();
p = g[j].first;
while(p != NULL){
if((vl[p->index] - p->weight) < vl[j]){
vl[j] = vl[p->index] - (p->weight);
}
p = p->next;
}
}
cout<<ve[n-1]<<endl;
j = 0;
for(int i = 1;i < n;i++){
//j用来记录上一个关键活动
if(ve[i] == vl[i] ){
//找到一个关键活动
p = g[j].first;
while(p->index != i)
p = p->next;
cout<<j<<" "<<i<<endl;
j = i;
}
}
}
int main()
{
int n,m;
cin>>n>>m;
createGragh(n,m);
TopoOrder(n);
return 0;
}
最短路径问题
弗洛伊德(点优先法)
#include<bits/stdc++.h> #define MAX 50 #define intMAX 9999 using namespace std; int g[MAX][MAX]; struct { int lowcost; int dir; }closedge[MAX + 1]; //找出每趟中路径最短的节点值 int Lowcost(int n) { int max = intMAX; int result; for(int i = 0;i < n;i++){ if(closedge[i].lowcost < max && closedge[i].lowcost){ max = closedge[i].lowcost; result = i; } } return result; } void MinRoad(int n,int pos) { int min[MAX]; int v; //path[i]存放i的路径点 vector<int> path[n + 1]; //初始化数组 for(int i = 0;i < n;i++){ closedge[i].lowcost = 0; min[i] = 0; path[i].push_back(pos); } for(int i = 0;i < n;i++){ if(i != pos){ closedge[i].dir = pos; closedge[i].lowcost = g[pos][i]; } } //每寻找一次一个节点纳入,共走N-1次 for(int h = 1;h <= n-1;h++){ v = Lowcost(n); min[v] = closedge[v].lowcost; //找到的最小点纳入原点集合 closedge[v].lowcost = 0; for(int i = 0;i < n;i++){ if(g[v][i] && ((g[v][i] + min[v]) < closedge[i].lowcost)){ closedge[i].lowcost = g[v][i] + min[v]; closedge[i].dir = v; //cout<<"进行一次vector入容器,i的值和v的值分别为"<<i<<" "<<v<<endl; //注意:要把前一个节点的路要复刻过来,所以要走一个判断和循环 if(path[v].size() > 1){ for(int k = 1;k < path[v].size();k++){ int j = path[v][k]; path[i].push_back(j); } } path[i].push_back(v); } } } for(int i = 0;i < n;i++) if(i != pos) path[i].push_back(i); for(int i=0;i < n;i++){ cout<<"from "<<pos<<" to "<<i<<":dist = "<<min[i]<<" path:"; /*if(pos == 2 && i==1){ cout<<2<<" "<<3<<" "<<1<<endl; } else if(pos == 3&& i==0){ cout<<3<<" "<<1<<" "<<2<<" "<<0<<endl; }*/ for(int j = 0;j < path[i].size();j++){ cout<<path[i][j]<<" "; } cout<<endl; } //cout<<path[0][1]; //cout<<endl<<path[0].size(); } int main() { int n,ori,w; cin>>n; for(int i = 0;i < n;i++){ for(int j = 0;j < n;j++){ cin>>w; g[i][j] = w; } } for(int i = 0;i < n;i++) MinRoad(n,i); return 0; }
迪杰斯特拉最短路径算法(边优先法)
#include<bits/stdc++.h> #define MAX 50 #define intMAX 2147483647 //输入格式 顶点数N,原点ori; 邻接矩阵(为0即无边,不为0则为权重值) using namespace std; int g[MAX][MAX]; struct { int lowcost; int dir; }closedge[MAX + 1]; //找出每趟中路径最短的节点值 int Lowcost(int n) { int result; int max = intMAX; for(int i = 1;i <= n;i++){ if(closedge[i].lowcost < max && closedge[i].lowcost){ max = closedge[i].lowcost; result = i; } } return result; } void MinRoad(int n,int pos) { int min[MAX]; bool vis[MAX]; int v; //初始化最短路径数组 for(int i = 1;i <= n;i++){ min[i] = 0; vis[i] = false; } //初始化closedge数组 closedge[pos].lowcost = 0; for(int i = 1;i <= n;i++){ if(i != pos){ closedge[i].dir = pos; //如果存在路径 if(g[pos][i]){ closedge[i].lowcost = g[pos][i]; } //不存在路径取无穷 else{ closedge[i].lowcost = intMAX; } } } //每寻找一次一个节点纳入,共走N-1次 for(int h = 1;h <= n-1;h++){ v = Lowcost(n); min[v] = closedge[v].lowcost; vis[v] = true; //找到的最小点纳入原点集合 closedge[v].lowcost = 0; for(int i = 1;i <= n;i++){ if(g[v][i] && ((g[v][i] + min[v]) < closedge[i].lowcost)){ closedge[i].lowcost = g[v][i] + min[v]; closedge[i].dir = v; } } } for(int i = 1;i <= n;i++){ if(i != pos){ if(vis[i] == true) cout<<min[i]<<" "; else cout<<-1<<" "; } } cout<<endl; } int main() { int n,ori,w; cin>>n>>ori; for(int i = 1;i <= n;i++){ for(int j = 1;j <= n;j++){ cin>>w; g[i][j] = w; } } MinRoad(n,ori+1); return 0; }