2021-8-3算法学习(算法笔记409-423)

一、最小生成树——prim算法

prim算法和之前的Dijkstra算法几乎一样
主要差别在于:1. d[ ]数组的含义不同:在prim算法中指的是一点到集合S的距离
2. 看情况要加一个ans来存放最小生成树的边权之和

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100;
const int INF = 100000;
int n, m;
bool vis[maxn] = {false};
int G[maxn][maxn];
int d[maxn];

int prim() //默认0号为初始点,且返回最小生成树的边权之和
{
    fill(d, d + maxn, INF);
    d[0] = 0;
    int ans = 0;
    for (int i = 0; i < n; i++)
    {
        int u = -1, min = INF;
        for (int j = 0; j < n; j++)
        {
            if (d[j] < min && vis[j] == false)
            {
                u = j;
                min = d[j];
            }
        }
        if (u == -1)
            return;
        vis[u] = true;
        ans += d[u];
        for (int v = 0; v < n; v++)
        {
            if (vis[v] == false && G[u][v] != INF && G[u][v] < d[v])
            {
                d[v] = G[u][v];
            }
        }

        return ans;
    }
}

struct Node
{
    int v;
    int dis;
};

vector<Node> Adj[maxn];

int prim2()
{
    fill(d, d + maxn, INF);
    d[0] = 0;
    int ans = 0;

    for (int i = 0; i < n; i++)
    {
        int u = -1, min = INF;
        for (int j = 0; j < n; j++)
        {
            if (vis[j] == false && d[j] < min)
            {
                u = j;
                min = d[j];
            }
        }

        if (u == -1)
            return;
        vis[u] = true;
        ans += d[u];
        for (int j = 0; j < Adj[u].size(); j++)
        {
            int v = Adj[u][j].v;
            int dis = Adj[u][j].dis;
            if (vis[v] == false && dis < d[v])
            {
                d[v] = dis;
            }
        }
    }
    return ans;
}

二、最小生成树——kruskal算法

伪代码如下:

int kruskal()
{
	令最小生成树的边权之和为ans、最小生成树的当前边数Num_Edge;
	将所有边按边权从小到大排序
	for(从小到大枚举所有边)
	{
		if(当前测试边的两个端点在不同的连通块中)
		{
			将该测试边加入最小生成树中;
			ans+=测试边的边权;
			num_edge++;
			当边数num_edge等于顶点数-1时 结束循环;
		}
	}
	return ans;
}


当前测试边的两个端点在不同的连通块中:可以使用并查集

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100;
const int INF = 1000000;
int father[maxn];
struct Edge
{
    int u, v;
    int cost;
} E[maxn];

bool cmp(Edge a, Edge b)
{
    return a.cost < b.cost;
}

int findFather(int x) //路径压缩!!
{
    int a = x;
    while (x != father[x])
    {
        x = father[x];
    }

    while (a != father[a])
    {
        int z = a;
        a = father[a];
        father[z] = x;
    }
    return x;
}

int kruskal(int n, int m)
{
    int ans = 0, numEdge = 0;
    for (int i = 1; i <= n; i++)
    {
        father[i] = i;
    }
    sort(E, E + m, cmp);

    for (int i = 0; i < m; i++)
    {
        int faU = findFather(E[i].u);
        int faV = findFather(E[i].v);
        if (faU != faV)
        {
            father[faU] = faV;
            ans += E[i].cost;
            numEdge++;
            if (numEdge == n - 1)
                break;
        }
    }

    if(numEdge!=n-1) return -1;
    else return ans;
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++)
    {
        scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].cost);
    }
    int ans=kruskal(n,m);
    printf("%d",ans);
}

三、拓扑排序

有向无环图 DAG:如果一个有向图的任意顶点都无法通过一些有向边回到自身,那么称这个有向图为有向无环图

拓扑排序的步骤:

  1. 定义一个队列Q,并把所有入度为0的结点加入队列
  2. 取出队首结点,输出。然后删除所有从它出发的边,并令这些边到达的顶点入度减一
  3. 反复进行2操作,直到队列为空。如果队列为空且入过队的结点数目恰好为N,说明拓扑排序成功,图G为有向无环图(设置num为加入拓扑序列的节点个数)

AOV网络(Activity on vertex)如果有合理的拓扑序,则必定是有向无环图
利用这个性质,可以用拓扑排序来判断一个图是否为有向无环图。

#include<bits/stdc++.h>
using namespace std;
const int maxn=100;
vector<int> Adj[maxn];
int inDegree[maxn];
int n,m;
bool topLogicalSort()
{
    queue<int> q;
    int num=0;

    for(int i=0;i<n;i++)
    {
        if(inDegree[i]==0)
        {
            q.push(i);
            num++;
        }
    }

     while(!q.empty())
    {
        int u=q.front();
        q.pop();
        num++;
        for(int i=0;i<Adj[u].size();i++)
        {
            int v=Adj[u][i];
            inDegree[v]--;

            if(inDegree[v]==0) q.push(v);
        }
    }

    if(num==n) return true;
    else return false;
}

四、关键路径(AOE网)

1. 概念

AOV网

AOE网:用来表示一个工程的进行过程,而工程常常可以分为若干个子工程

AOE网中的最长路径被称为关键路径,而把关键路径上的活动称为关键活动(why? 一个工程的完成时间取决于最长的路径!)

2. AOV网转AOE网

将AOV网中每个顶点都拆成两个顶点,分别表示活动的起点和终点,而两个顶点之间用有向边链接,该有向边表示原顶点的活动,边权给定。

3. 求最长路径

求最长路径(最长简单路径):
没有正环的图,可以把所有边权乘以-1,然后用Bellman-Ford算法或者SPFA算法求最短路径长度,再将所得结果取反
有正环的图,不存在最长路径

如果求有向无环图的最长路径长度,则以下关键路径求法更快:

#include<bits/stdc++.h>
using namespace std;
const int maxn=100;//最大顶点数
int ve[maxn],vl[maxn];
int inDrgree[maxn];
int n,m;
struct Node
{
    int u,v;
    int w;
    int e,l; //事件的最早开始时间和最迟开始时间
};

vector<Node> G[maxn];
stack<int> topOrder;

bool toplogicalSort()
{
    fill(ve,ve+n,0);
    fill(vl,vl+n,ve[n-1]);
    queue<int> q;
    for(int i=0;i<n;i++)
    {
        if(inDrgree[i]==0)
            q.push(i);
    }

    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        topOrder.push(u);
        for(int i=0;i<G[u].size();i++)
        {
            int v=G[u][i].v;
            inDrgree[v]--;
            if(inDrgree[v]==0) q.push(v);
        

            if(ve[v]<ve[u]+G[u][v].w)
            {
                ve[v]=ve[u]+G[u][v].w;
            }
        }
    }

    if(topOrder.size()==n) return true;
    else return false;

}

//已知汇点编号为 n-1 返回ve[n-1]
int CriticalPath()
{
    memset(ve,0,sizeof(ve));
    if(toplogicalSort()==false)
    {
        return -1;
    }

    fill(vl,vl+n,ve[n-1]);
    while(!topOrder.empty())
    {
        int u=topOrder.top();
        topOrder.pop();
        for(int i=0;i<G[u].size();i++)
        {
            int v=G[u][i].v;
            if(vl[v]-G[u][v].w<vl[u])
            {
                vl[u]=vl[v]-G[u][v].w;
            }
        }
    }

    for(int u=0;u<n;u++)
    {
        for(int i=0;i<G[u].size();i++)
        {
            int v=G[u][v].v;
            int w=G[u][v].w;

            int e=ve[u],l=vl[u]-w;

            if(e==l)
            {
                printf("%d->%d\n",u,v);
            }
        }
    }

    return ve[n-1]; //返回关键路径长度
}

//未知汇点编号:则ve最大的一定是最后一个事件 那么改一下初始化

// int maxLength=0;
// for(int i=0;i<n;i++)
// {
//     if(ve[i]>maxLength)
//     {
//         maxLength=ve[i];
//     }
// }
// fill(vl,vl+n,maxLength);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值