这个程序虽然用了最小堆,但不是Dijkstra算法的堆优化,其复杂度仍为O(n^2)(make_heap复杂度O(n))而非O(mlogn)
实验二 最短路径算法
一、实验内容及要求
编写程序实现Dijkstra算法:
从文件读入一个有向正权图(n个结点,m条边)的权矩阵表示,输出这个图中某一结点到其余各结点的最短路径长度。程序必须使用Dijkstra算法,使用其他算法(比如穷举)不符合要求。
输入输出格式
输入数据保存在文件 input.txt 中,程序运行后将输出数据保存至output.txt 中。这两个文件都与程序在相同的路径下。
输入的第一行为一个正整数n,表示图中结点的个数;接下来有n行,每一行有n个用空格隔开的整数,第i行的第j个整数表示矩阵中的aij;最后一行是一个整数k,表示需要计算的最短路径的起始结点编号。
输出文件仅有一行,包含n-1个由空格隔开的正整数,分别表示第k个结点到其余各结点的最短路径长度,按照结点编号由小到大顺序输出。特别的,如果两个结点之间不连通则输出-1。
条件限制
输入限制: 整数n满足:1 < n <32,输入矩阵中的整数aij满足0 ≤ aij ≤ 100, k满足1≤k≤n。 输入矩阵代表的有向图没有重边和自环。
注意
由于判断程序正确性是通过比较程序输出文件与标准输出文件来进行的,所以务必注意程序输出格式的规范性。
例子
以下是一个输入和输出的例子:
屏幕输入
6 0 7 1 0 0 0 0 0 0 4 0 1 0 6 0 0 2 7 0 0 0 0 0 0 0 3 0 5 0 0 0 0 0 0 3 0 1 |
屏幕输出:
6 1 8 3 7 |
二、设计思路
【算法】
【数据结构】 最小堆
三、关键代码分析
自定义类的排序和堆算法
Compare类的实现(特别注意最小堆反而要用">"来比较)
class node { // 节点类
public:
int id; // 节点编号
int path; // 节点当前到s的距离
};
struct PathLessmark {
bool operator() (const node& n1, const node& n2)
{
return n1.path > n2.path; // 按node.path升序,注意由于是heap,所以是>
}
} pathLess;
struct IdLessmark {
bool operator() (const node& n1, const node& n2)
{
return n1.id < n2.id; // 按node.id升序
}
} idLess;
堆算法和排序算法
make_heap(C.begin(), C.end(), pathLess); // 按node.path升序
sort(ans.begin(), ans.end(), idLess); // 将ans按节点标号升序
附:完整代码
#include<iostream>
#include<fstream>
#include<vector>
#include<queue>
#include<algorithm> // for heap implemetation
using namespace std;
class node { // 节点类
public:
int id; // 节点编号
int path; // 节点当前到s的距离
};
struct PathLessmark {
bool operator() (const node& n1, const node& n2)
{
return n1.path > n2.path; // 按node.path升序,注意由于是heap,所以是>
}
} pathLess;
struct IdLessmark {
bool operator() (const node& n1, const node& n2)
{
return n1.id < n2.id; // 按node.id升序
}
} idLess;
int main()
{
// 文件输入 input.txt
int n=0, s=0, tmp=0, i=0, j=0; // n: 矩阵函数, s: 起始点
vector< vector<int> > mat; // mat: 二维权矩阵
deque<node> C; // candidate set(双端队列)
ifstream fin("input.txt");
fin >> n;
for (i=0; i<n; i++)
{
vector<int> aLine;
for (j=0; j<n; j++)
{
fin >> tmp;
aLine.push_back(tmp);
}
mat.push_back(aLine);
}
fin >> s; s--; // C++下标从0开始
fin.close();
// 初始化
for (i=0; i<n; i++)
{
if (i != s) // candidate set不包括s
{
node aNode;
aNode.id = i;
if (mat[s][i] == 0)
{
aNode.path = INT_MAX; // 如果无法通达则记作无穷大
}
else
{
aNode.path = mat[s][i]; // 如果与s连通则记作s到i的距离
}
C.push_back(aNode);
}
}
// Dijskra迭代
vector<node> ans; // 结果节点序列
int id_min = s; // 每轮迭代path最小节点的id
int path_min = INT_MAX; // 每轮迭代path最小节点的path
deque<node>::iterator it; // deque迭代器
// 维护最小堆, 找到path最小节点
for (i=0; i<n-1; i++) // n-1次迭代
{
make_heap(C.begin(), C.end(), pathLess); // 按node.path升序
node poped = C.front(); // path最小节点
id_min = poped.id; // 记录每轮迭代path最小节点的id
path_min = poped.path; // 记录每轮迭代path最小节点的path
if (path_min == INT_MAX) // 如果最小的path也是无穷大
{
break; // 这意味着图不连通, 故结束循环
}
ans.push_back(poped); // 将path最小节点加入ans
C.pop_front(); // 在candidate set中去掉path最小节点
for (it = C.begin(); it != C.end(); it++)
{
if (mat[id_min][it->id] != 0) // 如果是path最小节点的后继节点
{
it->path = min(it->path, path_min+mat[id_min][it->id]); // 更新C的path信息
}
}
}
// 处理(n-1)次迭代中途break的情况
for (it = C.begin(); it != C.end(); it++)
{
ans.push_back(*it); // 把C中剩下的不连通的节点加入ans
}
sort(ans.begin(), ans.end(), idLess); // 将ans按节点标号升序
// 文件输出
ofstream fout("output.txt");
for (vector<node>::iterator vit = ans.begin(); vit != ans.end(); vit++)
{
if (vit->path != INT_MAX)
{
fout << vit->path << " ";
}
else
{
fout << -1 << " ";
}
}
fout.close();
return 0;
}