一、最小生成树
Prim算法
(时间戳可以简化为标记)
#include <cstdio>
#include <queue>
#include <vector>
#define MAX 300000
using namespace std;
typedef struct MyStruct1
{//记录图中的结点和它到邻居的权
int num = 0;
vector<int> l;
vector<int> w;
int d = 0;
}Node;
typedef struct MyStruct2
{//记录前后结点和它们的权
int first = 0;
int weight = 0;
int second = 0;
}Side;
Node node[MAX];//邻接表
int t = 1;//时间戳
int sum = 0;//计算总权重
int SUM = 0;//计算局部生成树的结点数
struct cmp
{//更改队列比较方法
bool operator()(Side a, Side b)
{
return a.weight > b.weight;
}
};
priority_queue<Side, vector<Side>, cmp> q;//用来储存局部生成树的边
void add(int num);//将边加入优先队列队列
void Prim();//从优先队列中拿最短的边
int main()
{
int node_n, side_n;//节点数 边数
scanf("%d%d", &node_n, &side_n);
for (int i = 1; i <= node_n; i++)
node[i].num = i;//初始化结点
int first = 0, second = 0, weight = 0;
for (int i = 1; i <= side_n; i++)
{
scanf("%d%d%d", &first, &second, &weight);//边 边 权
node[first].l.push_back(second);//插入正向边
node[first].w.push_back(weight);
node[second].l.push_back(first);//插入反向边
node[second].w.push_back(weight);
}
add(1);//将结点1的边加入队列 因为必有一条边去往任意结点
while (SUM != node_n - 1)//判断局部生成树的节点数
Prim();//从优先队列中拿最短的边
printf("%d\n", sum);//输出总权重
return 0;
}
void Prim()
{
if (!q.empty())
{
Side topside;
topside = q.top();
q.pop();//取出最小权重边
if (node[topside.second].d == 0)
{//若后面的结点未被访问
sum += topside.weight;
SUM++;
add(topside.second);//将后面结点的邻居加入优先队列
}
}
}
void add(int num)
{
node[num].d = t++;//标记为已访问
for (unsigned i = 0; i < node[num].l.size(); i++)
{//将结点num的邻居加入优先队列
if (node[node[num].l[i]].d == 0)
{//若后面的结点未被访问
Side side;
side.first = num;
side.second = node[num].l[i];
side.weight = node[num].w[i];
q.push(side);
}
}
}
Kruskal
(边的维护用优先队列 其底层实现为堆)
#include <cstdio>
#include <queue>
#define MAX 300000
using namespace std;
typedef struct MyStruct2
{//记录前后结点和它们的权
int first = 0;
int weight = 0;
int second = 0;
}Side;
int sum = 0;//计算总权重
int SUM = 0;//计算局部生成树的结点数
int parent[MAX];
struct cmp
{//更改队列比较方法
bool operator()(Side a, Side b)
{
return a.weight > b.weight;
}
};
priority_queue<Side, std::vector<Side>, cmp> q;//用来储存局部生成树的边
void Kruskal();
int get_boss(int v);
int main()
{
int node_n, side_n;//节点数 边数
scanf("%d%d", &node_n, &side_n);
for (int i = 1; i <= node_n; i++)
parent[i] = i;//初始化
int first = 0, second = 0, weight = 0;
for (int i = 1; i <= side_n; i++)
{
scanf("%d%d%d", &first, &second, &weight);//边 边 权
Side side;
side.first = first;
side.second = second;
side.weight = weight;
q.push(side);
}
while (SUM != node_n - 1)//判断局部生成树的节点数
Kruskal();
printf("%d\n", sum);//输出总权重
return 0;
}
void Kruskal()
{
if (!q.empty())
{
Side topside;
topside = q.top();
q.pop();//取出最小权重边
int a = get_boss(topside.first);
int b = get_boss(topside.second);
if (a != b)
{//判断边的两结点关系
parent[a] = b;
sum += topside.weight;
SUM++;
}
}
}
int get_boss(int v)
{
return parent[v] == v ? v : get_boss(parent[v]);
}
二、最短路径
Dijkstra
(路径输出不在主复习行列)
#include <cstdio>
#include <queue>
#include <stack>
#include <vector>
#define MAX 300000
using namespace std;
typedef struct MyStruct1
{//记录图中的结点和它到邻居的权
int num = 0;
unsigned distance = 0;//源点到该点的距离
int btn = 0;//前继
vector<int> l;//结点的邻居
vector<unsigned> w;//结点的邻居权重
int d = 0;//标记
}Node;
typedef struct MyStruct2
{//记录前后结点和它们的权
int first = 0;
unsigned weight = 0;
int second = 0;
}Side;
Node node[MAX];//邻接表
int t = 1;//时间戳
int SUM = 0;//计算局部生成树的结点数
struct cmp
{//更改队列比较方法
bool operator()(Side a, Side b)
{
return a.weight > b.weight;
}
};
priority_queue<Side, vector<Side>, cmp> q;//用来储存局部生成树的边
void add(int num);//将边加入优先队列队列
void Dijkstra();//从优先队列中拿最短的边
void print_way(int origin, int destination);//输出最短路径
int main()
{
int node_n, side_n;//节点数 边数
scanf("%d%d", &node_n, &side_n);
int origin = 1, destination;
for (int i = 1; i <= node_n; i++)
node[i].num = i;//初始化结点
int first = 0, second = 0, weight = 0;
for (int i = 1; i <= side_n; i++)
{
scanf("%d%d%d", &first, &second, &weight);//边 边 权
node[first].l.push_back(second);//插入正向边
node[first].w.push_back(weight);
node[second].l.push_back(first);//插入反向边
node[second].w.push_back(weight);
}
add(origin);//将起点的邻居加入队列
while (SUM != node_n - 1)//判断局部生成树的节点数while (node[destination].d == 0)//
Dijkstra();//从优先队列中拿最短的边
for (int i = 1; i <= node_n; i++)
printf("结点%2d到达结点%d的距离:%5d\n", node[i].num, origin, node[i].distance);
//printf("%d\n", node[destination].distance);
printf("\n");
for (int i = 1; i <= node_n; i++)
{
printf("%d", origin);
print_way(origin, i);
printf("\n");
}
return 0;
}
void print_way(int origin, int destination)
{
std::stack<int> S;
while (destination != origin)
{
S.push(destination);
destination = node[destination].btn;
}
while (!S.empty())
{
printf("-->%d", S.top());
S.pop();
}
}
void Dijkstra()
{
if (!q.empty())
{
Side topside;
topside = q.top();
q.pop();//取出最小权重边
if (node[topside.second].d == 0)
{//若后面的结点未被访问
node[topside.second].distance = topside.weight;
node[topside.second].btn = topside.first;
SUM++;
add(topside.second);//将后面结点的邻居加入优先队列
}
}
}
void add(int num)
{
node[num].d = t++;//标记为已访问
for (unsigned i = 0; i < node[num].l.size(); i++)
{//将结点num的邻居加入优先队列
if (node[node[num].l[i]].d == 0)
{//若后面的结点未被访问
Side side;
side.first = num;
side.second = node[num].l[i];
side.weight = node[num].w[i] + node[num].distance;
q.push(side);
}
}
}
Floyd
(优化版本利于理解算法 不在复习主行列)
#include <cstdio>
#include <algorithm>
#define MAX 300
#define maxlen 7001
using namespace std;
unsigned length[MAX][MAX];
void Floyd(unsigned length[][MAX], int node_n);
void Floyd_Push(unsigned length[][MAX], int node_n);
void RollFloyd(unsigned a[][MAX], int node_n);
int main()
{
int node_n, side_n;
scanf("%d%d", &node_n, &side_n);
int origin, destination;
scanf("%d%d", &origin, &destination);
for (int i = 1; i <= node_n; i++)//初始化距离值
for (int k = 1; k <= node_n; k++)
if (i != k)
length[i][k] = maxlen;
for (int j = 1; j <= side_n; j++)
{
int first, second;
unsigned weight;
scanf("%d%d%d", &first, &second, &weight);
length[first - 1][second - 1] = weight;
length[second - 1][first - 1] = weight;
}
Floyd(length, node_n);
printf("%d\n", length[origin][destination]);
return 0;
}
void Floyd(unsigned length[][MAX], int node_n)
{//纯暴力
for (int i = 0; i < node_n; i++)
for (int j = 0; j < node_n; j++)
for (int k = 0; k < node_n; k++)
length[j][k] = min(length[j][k], length[j][i] + length[i][k]);
}
void Floyd_Push(unsigned length[][MAX], int node_n)
{//对暴力进行优化Floyd
for (int i = 0; i < node_n; i++)
for (int j = 0; j < node_n; j++)
for (int k = 0; k < node_n; k++)
{
if (length[j][i] == maxlen || length[i][k] == maxlen) continue;
if (length[j][k] == maxlen || length[j][k] < length[j][i] + length[i][k])
length[j][k] = length[j][i] + length[i][k];
}
}
void RollFloyd(unsigned length[][MAX], int node_n)
{//利用滚动数组进行优化
unsigned f[MAX][MAX][MAX];
for (int i = 0; i < node_n; i++)
{
for (int j = 0; j < node_n; j++)
{
for (int k = 0; k < node_n; k++)
if (i != j) f[k][i][j] = maxlen;
f[0][i][j] = length[i][j];
}
}
for (int k = 1; k <= node_n; k++)
for (int i = 0; i < node_n; i++)
for (int j = 0; j < node_n; j++)
f[k][i][j] = min(f[k - 1][i][j], f[k - 1][i][k - 1] + f[k - 1][k - 1][j]);
for (int i = 0; i < node_n; i++)
for (int j = 0; j < node_n; j++)
length[i][j] = f[node_n][i][j];
}
三、拓扑排序
9 12
0 1 6
0 2 4
0 3 5
1 4 1
2 4 1
3 5 2
5 4 0
4 6 9
4 7 7
5 7 4
6 8 2
7 8 4
#include <cstdio>
#include <algorithm>
#define MAX 105
using namespace std;
int mp[MAX][MAX];//图
int in[MAX];//前继数
int q[MAX];//0前继结点
int dis[MAX];//距离记录
int maxt=0;//最大 最早发生时间
int main()
{
int n, m;
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
mp[i][j] = -1;
for (int i = 0; i < m; i++)
{//更新图
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
mp[a][b] = c;
in[b]++;//记录节点的前继数量
}
int head = 0, tail = 0;
for (int i = 0; i < n; i++)
if (!in[i])
q[tail++] = i;//获得一开始无前继的结点
while (head < tail)
{//预留tail做有环判断
int d = q[head++];//查看入度为0的结点
maxt = max(maxt, dis[d]);
for (int i = 0; i < n; i++)
{//遍历查找入度为0的孩子结点
if (mp[d][i] != -1)
{
dis[i] = max(dis[i], dis[d] + mp[d][i]);//已存dis和 正在查看的路所计算的dis
if (!(--in[i]))//减少孩子结点的入度 并判断入度是否为0
q[tail++] = i;
}
}
}
if (tail < n)
printf("Impossible");
else
printf("%d\n", maxt);
return 0;
}
四、关键路径
7 8
1 2 4
1 3 3
2 4 5
3 4 3
4 5 1
4 6 6
5 7 5
6 7 2
#include <cstdio>
#include <algorithm>
#define inf 0x3f3f3f3f
using namespace std;
int n, m, head, tail;
int mp[105][105], maxt;
int in[105], out[105], ve[105], vl[105], q[105];
int getve()
{ //计算 所有结点 前继结点ve+所连接的权 的最大值
head = tail = 0;//(h21 h17h29 h33)
for (int i = 1; i <= n; i++)
{//遍历查找入度为0的结点并置入数组为遍历(h21)做准备
if (!in[i])
q[tail++] = i;
}
while (head < tail)
{
int d = q[head++];//查看入度为0的结点
maxt = max(maxt, ve[d]);//记录整个项目的最大ve 为计算最晚准备(h42)
for (int i = 1; i <= n; i++)
{//遍历查找入度为0的孩子结点
if (mp[d][i])
{//最早发生时间 ve
ve[i] = max(ve[i], ve[d] + mp[d][i]);//已存ve和 正在查看的路所计算的ve
if (!(--in[i]))//减少孩子结点的入度 并判断入度是否为0
q[tail++] = i;
}
}
}
return tail == n;//tail记录了加入的结点数量
}
void getvl()
{ //计算 所有结点 本结点vl-前继结点权重 的最小值
head = tail = 0;
for (int i = 1; i <= n; i++)
{//遍历查找出度为0的结点并置入数组为遍历(h46)做准备
if (!out[i])
q[tail++] = i;
vl[i] = maxt; //初始化为最早开始的最大时间(h51)
}
while (head < tail)
{
int d = q[head++];//查看出度为0的结点
for (int i = 1; i <= n; i++)
{//遍历查找出度为0的父结点
if (mp[i][d])
{//最迟发生时间 vl
vl[i] = min(vl[i], vl[d] - mp[i][d]);//已存vl和 正在查看的路所计算的vl
if (!(--out[i]))//减少父结点的入度 并判断出度是否为0
q[tail++] = i;
}
}
}
}
void solve()
{
for (int i = 1; i <= n; i++)
{
if (ve[i] != vl[i])
continue;
for (int j = n; j >= 1; j--)
{ //逆序
if (mp[i][j] && ve[j] == vl[j] && vl[j] == ve[i] + mp[i][j])
{
printf("%d->%d\n", i, j);
}
}
}
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
mp[a][b] = c;//记录边权
out[a]++;//记录a的出度
in[b]++;//记录b的入度
}
if (!getve())
printf("0");
else
{
printf("%d\n", maxt);
getvl();
solve();
}
return 0;
}