图的最短路的几种算法


dijkstra朴素算法思想:
1: 用临接矩阵来存储,因为是稠密度的,我们算法复杂度就是O(n^2)
(我们在进行存便之间的关系的时候,已经排除掉重边的干扰了,存的都是最小值)
2: 每一次找出我们存活点当中的距离最小值
3: 利用这个最小值去更新所有点的其它点的最小值
4: 更新完成之后,这个点的使命已经完成,将其杀死;
5: 以上的动作重复n-1 次;就可以了
dijkstra推优化的算法思想:
1: 利用邻接表来进行存储,因为是稀密度的,我们的算法复杂度是o (mlogn)
2: 我们将上面第二步步骤设置未利用堆区维护我们的最小值
3: 每次取出来最小值之后,看看这个点是已经被杀死的点,如果是则重新取出来,如果不是就进行第四步
4: 可以先杀死这个点,也可以跟新完成后再杀死。我们利用这个点,去跟新所有和他相连接的点,如果距离变小了,我们就加入队列里面。
5: 这儿可以看出和朴素版本的区别就是两点:一个是取值,另外一个就是只更新和自己有关的点,而不是朴素算法所有边都来一遍。
bellman_ford算法:
1. 这种算法一般用于有次数限制的方法,且可以有负权边。 时间复杂度就是0(mn)
2: 我们存储的方式变为利用一个结构数组保存我们的信息;
3: 假设k为我们的更新次数,那么我们以下的步骤就循环k次
4: 每次遍历一下我们结构数组,每次对dist进行一次松弛操作。
5: 但是一个1/k中,开始一轮遍历,可能出现了在这次遍历中循环更新以后边,所以我们就利用一个last来存上一次的dist
在last的基础上进行更新。
spfa算法:
1: 针对bellman_fork算法,我们其实没有必要访问所有的边,只有已知边长度已经减少了我们才会更新这条边后面的边。
这就有点bfs的感觉了,利用队列来做。
2. 首先把1号点加进去,并把这个点杀死,然后开始队列的取出-------存入操作
3: 首先,取出这个点,并将其复活。知道为啥是这一步吗?对比dij算法,我们有负权边,可以后退,而dij全是正数,没有后退的余地。
4: 然后遍历取出来的点,如果我们的相连的点减少了。我们呢判断是否在队列里面,如果不再就加入,并将其杀死,以便后面取出来
跟新其它和它相邻的点。
5: 所以这儿的st[N]有两种功能,判断否已经在队列里面。(隐藏了一种杀死复活的感觉。)
Floyd算法
1: 这个算法没有什么好说的,首先明白注意以下几个点就可以了
2: 由于多元 dist就应该是二元数组 初始化问题就必须进行手动进行
3: 其实这就是一个三维的动态规划问题 记住公式:d[i][j]=min(d[i][j],d[i][k]+d[k][j]);ijk都是从1先开始循环到m;m为点数;

dijkstra朴素算法思想


int g[N][N];  // 存储每条边
int dist[N];  // 存储1号点到每个点的最短距离
bool st[N];   // 存储每个点的最短路是否已经确定

// 求1号点到n号点的最短路,如果不存在则返回-1
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    for (int i = 0; i < n - 1; i ++ )
    {
        int t = -1;     // 在还未确定最短路的点中,寻找距离最小的点
		//在st==false的点当中找到 dist最小的点  //遍历了n次找到我们的点  //如果用堆来做 那么就是o(1)
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;

        // 用t更新其他点的距离  ---遍历了m次边        //如果用堆来做,那么就是修改m次,每次longn;
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], dist[t] + g[t][j]);
		//做完以上过程  那么这个t就已经可以确定了
        st[t] = true;
    }
	//如果最后n的最短距离还是无穷大 那么我们就是没有最短距离 
    if (dist[n] == 0x3f3f3f3f) return -1;   因为这儿没有负权路,所用dist[n] 不可能只是稍微减少一点点;
    return dist[n];
}
//------------------------end------------------------------------//

来个完整 程序

using namespace std;
const int N = 50;

int g[N][N];  // 存储每条边
int dist[N];  // 存储1号点到每个点的最短距离
bool st[N];   // 存储每个点的最短路是否已经确定

// 求1号点到n号点的最短路,如果不存在则返回-1
int n;
int dijkstra()
{
	memset(dist, 0x3f, sizeof dist);  //将每一个dist置为无限大
	dist[1] = 0;
	for (int i = 0; i +1< n ; i++)  //总共轮询n-1次,第一次是根据0点去更新其他的点,但是到了n点就不用更新了;
	{
		int t = -1;     
		for (int j = 1; j <= n; j++)  //每次找出还活着的点中的最小点
			if (!st[j] && (t == -1 || dist[t] > dist[j]))
				t = j;
		for (int j = 1; j <= n; j++) //然后根据最小点的更新每一个点
			dist[j] = min(dist[j], dist[t] + g[t][j]);
		st[t] = true;  //杀死这个点
	}
	if (dist[n] == 0x3f3f3f3f) return -1;   //因为没有负边,所以可以这样判断;
		return dist[n];
}

int main()
{
	cin >> n;
	memset(g, 0x3f, sizeof g);  //每个距离置为无穷大,理解memset的作用,根据每一个字节进行初始化工作
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			cout << g[i][j] << ' ';
		}
		cout << endl;
	}
	int m = 14;  //写下有多少条边数 
	while (m--) {
		int a, b,c;
		cin >> a >> b >> c;
		g[a][b]=g[b][a] = min(g[a][b], c);  //因为是双向边,所以需要两边都存入
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			cout << g[i][j] << ' ';
		}
		cout << endl;
	}
	cout << dijkstra()<<endl;
	for (int i = 1; i <= n; i++) cout << dist[i] << ' ';  //输出每一个距离自己看看哈
	return 0;
}

dijkstra堆优化版本

//-----------------------dijkstra堆优化版本--------------------------//
  可以手写堆                 也可以使用优先队列 
 可以修改我们的值            不能够修改我们的值
typedef pair<int, int> PII;

int n;      // 点的数量
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N];        // 存储所有点到1号点的距离
bool st[N];     // 存储每个点的最短距离是否已确定

// 求1号点到n号点的最短距离,如果不存在,则返回-1
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, 1});      // first存储距离,second存储节点编号

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.second, distance = t.first;

        if (st[ver]) continue;
        st[ver] = true;

        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > distance + w[i])
            {
                dist[j] = distance + w[i];
                heap.push({dist[j], j});
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}
//----------------------------end---------------------------------//

来个完整的程序

using namespace std;

const int N = 55;  //点数,一般开大一点
const int M = 2 * N;  //边数,因为是双向边,所以也整大一点
int dist[N];  // 存储1号点到每个点的最短距离
bool st[N];   // 存储每个点的最短路是否已经确定
int e[M], h[N], ne[M], idx,we[M];  //基本的邻接表的数组
typedef pair<int, int> PII;
int n;

void add(int a, int b, int w) {  //开始添加边的信息

	e[idx] = b, we[idx] = w, ne[idx] = h[a], h[a] = idx++;

}

int dijkstra()
{
	memset(dist, 0x3f, sizeof dist);
	dist[1] = 0;  //开始的点
	priority_queue<PII, vector<PII>, greater<PII>> heap;  //定义一个小根堆,注意greater的用法
	heap.push({ 0,1 });  
	while (heap.size()) {
		auto t = heap.top();  //每次都取出最小的点
		heap.pop();
		int ver = t.second, distance = t.first;
		if (st[ver]) continue;  //如果这个点已经被取过了 那么直接跳过
		st[ver] = true;
		for (int i = h[ver]; i != -1; i = ne[i]) {  //每次只更新与我相关的点
			int j = e[i];
			if (dist[j] > we[i] + distance) {
				dist[j] = we[i] + distance;  //更新这些点的距离
				heap.push({ dist[j],j });  //并且压到堆里面去,如果它是最小的,肯定是堆的头了
			}
		}
	}
	if (dist[n] == 0x3f3f3f3f) return -1;
	else return dist[n];
}

int main()
{
	cin >> n;
	memset(h, -1, sizeof h);
	int m;
	cin >> m;
	while (m--) {
		int a, b, c;
		cin >> a >> b >> c;
		add(a, b, c); add(b, a, c); //双向边的操作
	}
	cout << dijkstra() << endl;
	for (int i = 1; i <= n; i++) cout << dist[i] << ' '; //输出检查一下

	return 0;
}

bellman-ford算法

可以用来求负环  但一般不用这种算法
(记住加入last数组来备份我们的dist)
int n, m;       // n表示点数,m表示边数
int dist[N];        // dist[x]存储1到x的最短路距离

struct Edge     // 边,a表示出点,b表示入点,w表示边的权重
{
    int a, b, w;
}edges[M];

// 求1到n的最短路距离,如果无法从1走到n,则返回-1。
int bellman_ford()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    // 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。
    for (int i = 0; i < n; i ++ )
    {
        for (int j = 0; j < m; j ++ )
        {
            int a = edges[j].a, b = edges[j].b, w = edges[j].w;
            if (dist[b] > dist[a] + w)
                dist[b] = dist[a] + w;
        }
    }

    if (dist[n] > 0x3f3f3f3f / 2) return -1;
    return dist[n];
}
//-------------------------------end-------------------------------//

spfa算法

int n;      // 总点数
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N];        // 存储每个点到1号点的最短距离
bool st[N];     // 存储每个点是否在队列中

// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
int spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    queue<int> q;
    q.push(1);
    st[1] = true;

    while (q.size())
    {
        auto t = q.front();
        q.pop();

        st[t] = false;

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                if (!st[j])     // 如果队列中已存在j,则不需要将j重复插入
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

floyd算法

void floyd()
{
    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值