图的应用算法————数据结构//复习复习复习

一、最小生成树

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值