C++之最短路径算法(Dijkstra)

7 篇文章 0 订阅

 前面我们讲过权值为1时的最短路径的算法,也就是无权最短路径算法。但是如果时赋权图呢?我们该怎么办呢?

解决单源(只有一个开始顶点)最短路径的算法为Dijkstra,而该算法的核心思想是贪婪算法。贪婪算法的思想简单来讲就是“目光短浅,眼前利益为主”,也就是分阶段求解问题,在每个阶段去寻找最优解。Dijkstra算法也是按阶段进行,在每个阶段选择离开始顶点最近的顶点(未访问),我们对每一个顶点(vertex)保留其临时的dis,这个距离是开始顶点v到所访问的顶点的实际的路径长,我们每次要对dis值要进行更新。与无权最短路径算法一样,未访问的顶点的dis为∞。无权最短路径算法每次往前递推加一对dis进行更新。而Dijkastra最短路径算法对新访问的顶点离开始顶点的路径与前一个的顶点的路径加前一个顶点到新访问的顶点的权值进行比较,从而对dis进行更新。让我们看看Dijkstra的核心算法:

void Graph::Dijkstra(size_t v) {
	verptr->dis[v] = 0;
	while (true) {
		size_t min = InFinity; //未访问的顶点到开始顶点的权值为∞
		for (int i = 0; i < verptr->vertexnumber; ++i) {   
			if (verptr->visited[i] == false && min > verptr->dis[i]) {  //选择离开始顶点权值最小的顶点
				min = verptr->dis[i];
				v = i;
			}
		}
		if (min == InFinity)  //全部访问完跳出循环
			break;
		verptr->visited[v] = true;  //对访问的进行标记
		auto beg = lists[v].begin();
		while (beg != lists[v].end()) {
			if (verptr->visited[*beg] == false)
				if (verptr->dis[v] + weight[v][*beg] < verptr->dis[*beg])
					verptr->dis[*beg] = verptr->dis[v] + weight[v][*beg];  //对dis进行更新
			++beg;
		}
	}	
}

 在整个算法过程中查找最小值将的时间复杂度为O(|V|^2) (V 为其顶点数),对dis的更新的时间复杂度为O(|E|)(E 为其边数)。因此Dijkstra算法的总的算法时间复杂度为O(|V|^2)。那图是稀疏或者稠密时呢?

  • 当为稠密图时(边数大于顶点数),其算法复杂度为E=O(|V|^2){\color{Blue} }

  • 当为稀疏图时(边数小于顶点数),如果dis存储在一维数组中,那么其算法复杂度为E=O(|V|^2),这种效率是很低的,因此我们将dis换位优先队列。优先队列的现实我们可以用最小二叉堆(小堆),斐波那契堆,这两种不同的数据结构可以很好的提升算法的性能。

 

 完整源码如下:

#include<iostream>
#include<vector>
#include<list>
#include<memory>
const size_t InFinity = 999;
struct Vertex {
	size_t   vertexnumber;
	bool*    visited;
	size_t*  dis;  //储存minweight
};
class Graph {
public:
	Graph(const size_t _vertexnumber) :verptr(std::make_unique<Vertex>()), lists(new std::list<size_t>[_vertexnumber]()), weight(std::vector<std::vector<size_t>>(_vertexnumber, std::vector<size_t>(_vertexnumber, -1))) {
		verptr->vertexnumber = _vertexnumber;
		verptr->visited = new bool[_vertexnumber]();
		verptr->dis = new size_t[_vertexnumber]();
		for (int i = 0; i < verptr->vertexnumber; ++i)
			verptr->dis[i] = InFinity;
	}
	~Graph() {
		if (verptr->visited && verptr->dis) {
			delete[] verptr->visited;
			delete[] verptr->dis;
			delete[] lists;
		}
		else
			throw std::out_of_range("Out of MemorySpace!!");

	}
public:
	void AddEdge(const size_t v, const size_t w, const size_t _weight);
	void Dijkstra(size_t startv);
	void Display(const size_t v)const;
private:
	std::unique_ptr<Vertex>verptr;
	std::list<size_t>*lists;
	std::vector<std::vector<size_t>>weight;

};

void Graph::AddEdge(const size_t v, const size_t w, const size_t _weight) {
	lists[v].push_back(w);
	weight[v][w] = _weight;
}

void Graph::Dijkstra(size_t v) {
	verptr->dis[v] = 0;
	while (true) {
		size_t min = InFinity;
		for (int i = 0; i < verptr->vertexnumber; ++i) {
			if (verptr->visited[i] == false && min > verptr->dis[i]) {
				min = verptr->dis[i];
				v = i;
			}
		}
		if (min == InFinity)
			break;
		verptr->visited[v] = true;
		auto beg = lists[v].begin();
		while (beg != lists[v].end()) {
			if (verptr->visited[*beg] == false)
				if (verptr->dis[v] + weight[v][*beg] < verptr->dis[*beg])
					verptr->dis[*beg] = verptr->dis[v] + weight[v][*beg];
			++beg;
		}
	}
}

void Graph::Display(const size_t v)const {
	for (int i = 0; i < verptr->vertexnumber; ++i) {
		std::cout << v << " to " << i << " minweight: " << verptr->dis[i] << std::endl;
	}
}

int main(void)
{
	const size_t vertexnumber = 7;
	Graph graph(vertexnumber);
	graph.AddEdge(0, 1, 2);
	graph.AddEdge(0, 2, 4);
	graph.AddEdge(0, 3, 1);
	graph.AddEdge(1, 3, 3);
	graph.AddEdge(1, 4, 10);
	graph.AddEdge(2, 5, 5);
	graph.AddEdge(3, 2, 2);
	graph.AddEdge(3, 5, 8);
	graph.AddEdge(3, 6, 4);
	graph.AddEdge(3, 4, 2);
	graph.AddEdge(4, 6, 6);
	graph.AddEdge(6, 5, 1);
	graph.Dijkstra(0);
	graph.Display(0);
	system("pause");
}

 

还有一个问题没有解决?如果权值为负数呢?该怎么办呢?Dijkstra算法是否还可以行得通?如果不行,该怎么解决呢?

 

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值