图论专题小结:最短路算法

1.Dijkstra算法(适用于边权为正的图)

(1)概述:该算法可以计算正权图的单源最短路,即从一个单个源点出发,到所有结点的最短路。伪代码如下:

         清除所有点的标号

         设d[0]=0,其他d[i]=INF

         循环n次

        {

             在所有未标号的结点中,选出d值最小的结点x

             给结点x标记

             对于从x出发的所有边(x,y),更新d[y]=min{d[y],d[x]+w(x,y)}

        }

下面给出优化到O(M*logN)的代码

#define N 10000
#define INF 1000000
struct Edge
{
	int from,to,dist;
	Edge(int u, int v, int d) :from(u), to(v), dist(d){}
};
struct HeapNode//将d值,编号捆绑为一个结构体
{
	int d, u;
	HeapNode(int dd, int uu) :d(dd), u(uu){};
	bool operator <(const HeapNode&rhs)const
	{
		return d>rhs.d;
	}
};
struct Dijkstra
{
	int n, m;
	vectoredges;
	vectorG[N];
	bool done[N];//是否已经永久标记
	int d[N]; //s到各个点的距离
	int p[N]; //最短路中的上一条边

	void init()//初始化
	{
		this->n = n;
		for (int i = 0; i < n; i++)
			G[i].clear();
		edges.clear();
	}
	void AddEdge(int from, int to, int dist)//添加两个端点,距离
	{
		edges.push_back(Edge(from, to, dist));
		m = edges.size();
		G[from].push_back(m - 1);//这条边的序号是m-1
	}
	void dijkstra(int s)//s是源点,注意s表示的是边序号
	{
		priority_queueQ;
		for (int i = 0; i < n; i++)
			d[i] = INF;
		d[s] = 0;//源点的d值为0
		memset(done, 0, sizeof(done));
		Q.push(HeapNode(0,s));
		while (!Q.empty())//利用BFS的思路
		{
			HeapNode x = Q.top(); Q.pop();
			int u = x.u;//边的u端点
			if (done[u])continue;//已经取过,扔掉
			done[u] = true;
			for (int i = 0; i < G[u].size(); i++)//考虑从u出发的所有边
			{
				Edge& e = edges[G[u][i]];//e是第i条和u相连的边
				if (d[e.to]>d[u] + e.dist)//松弛操作
				{
					d[e.to] = d[u] + e.dist;
					p[e.to] = G[u][i];//记录e.to这个端点的父边
					Q.push(HeapNode(d[e.to], e.to));
				}
			}
		}
	}
};


2.Bellman-Ford算法(适用于边权可正可负的图)

(1)概述:当负权存在时,最短路不一定存在。但有这样一个事实:如果最短路存在,一定存在一个不含环的最短路。(分零环,正环,负环三种讨论即可证明)。

既然如此,那么最短路最多只经过(起点不算)n-1个结点,可以通过n-1次松弛操作得到。下面利用了FIFO队列代替这n-1次循环检查。因为这样一个结点可以多次进入队列。这样的好处是最坏情况下时间复杂度才是O(N*M)。

#define N 10000
#define INF 1000000
struct Edge
{
	int from, to, dist;//dist可正可负
	Edge(int u, int v, int d) :from(u), to(v), dist(d){}
};
struct HeapNode//将d值,编号捆绑为一个结构体
{
	int d, u;
	HeapNode(int dd, int uu) :d(dd), u(uu){};
	bool operator <(const HeapNode&rhs)const
	{
		return d>rhs.d;
	}
};
struct Bellman_Ford
{
	int n, m;
	vectoredges;
	vectorG[N];
	bool done[N];//是否已经永久标记
	int d[N]; //s到各个点的距离
	int p[N]; //最短路中的上一条边
	int inq[N],cnt[N];

	void init()//初始化
	{
		this->n = n;
		for (int i = 0; i < n; i++)
			G[i].clear();
		edges.clear();
	}
	void AddEdge(int from, int to, int dist)//添加两个端点,距离
	{
		edges.push_back(Edge(from, to, dist));
		m = edges.size();
		G[from].push_back(m - 1);//这条边的序号是m-1
	}

	bool bellman_ford(int s)
	{
		queueQ;
		memset(inq, 0, sizeof(inq));//inq数组标记是否进入队列
		memset(cnt, 0, sizeof(cnt));//cnt数组标记到结点入队列的次数
		for (int i = 0; i < n; i++)
			d[i] = INF;
		d[s] = 0;
		inq[s] = true;
		Q.push(s);

		while (!Q.empty())
		{
			int u = Q.front(); Q.pop();
			inq[u] = false;
			for (int i = 0; i < G[u].size(); i++)
			{
				Edge&e = edges[G[u][i]];
				if (d[u]d[u] + e.dist)//松弛操作
				{
					d[e.to] = d[u] + e.dist;
					p[e.to] = G[u][i];
					if (!inq[e.to])
					{
						Q.push(e.to);
						inq[e.to] = true;
						if (++cnt[e.to] > n)//入队列次数超过n次,说明有负圈存在,及时退出
							return false;
					}
				}
			}
		}
		return true;
	}
};

(2)简易版本的Bellman-Ford算法(只适用于不含负圈的图)

#define N 1000
#define INF 100000000
struct Edge{ int from, to, cost; };
Edge es[N];
int d[N];    //最短距离
int V, E;
void shortest_path(int s)
{
	for (int i = 0; i < V; i++)//初始化为无穷大
		d[i] = INF;
	d[s] = 0;
	while (1)//循环次数不超过V-1次
	{
		bool update = false;
		for (int i = 0; i < E; i++)//考察每一条边
		{
			Edge e = es[i];
			if (d[e.from] != INF&&d[e.to]>d[e.from] + e.cost)
			{
				d[e.to] = d[e.from] + e.cost;
				update = true;
			}
		}
		if (!update)break;//只要不含有负圈,更新的次数就是有限的
	}
}


3.Floyd算法(适用于快速求解每两个点之间的最短路)

(1)概述:如果需要求每两个点之间的最短路(权值可正可负),可以用下面的Floyd-Warshall算法。

(i)适用于带权图

#define N 1000
#define INF 10000001 //预先估计一下最短路上限
int d[N][N];//记录u,v之间的权值
int n;

void Floyd()//带权图
{
	for (int k = 0; k < n;k++)
	for (int i = 0; i < n;i++)
	for (int j = 0; j < n; j++)
		d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

(ii)适用于有向无权图(DAG)

#define N 1000
int d[N][N];//记录u,v之间的权值
int n;

void Floyd()//适用于DAG,只判断是否连通
{
	for (int k = 0; k < n; k++)
	for (int i = 0; i < n; i++)
	for (int j = 0; j < n; j++)
		d[i][j] = d[i][j] || (d[i][k] && d[k][j]);
}


你可能感兴趣的:(算法归纳与总结)