无权最短路径
- 对于无权图G(边没有权值或认为权值为1),如果G是连通的,则每个顶点之间都存在路径。
- 最短路径算法就是要找到一条连接不同顶点的最短路径。
- 上图表示一个有向无权图,顶点 到 之间存在多条路径如 为其中的1条,而最短的一条路径为 。
无权最短路径算法
- 求顶点间的最短路径,首先可以遍历所有可能的路径,这种做法时间复杂度高。
- 另外,可以采取广度优先搜索的策略求顶点间的最短路径。具体过程如下:
- 一些必要准备:
- (1) 选取一个点s,例如上图的 。我们求 到所有其他顶点的最短路径。
- (2) 借助一个标记每个顶点状态的信息表InfoTable,包含三个必要的信息:是否被遍历Known,与
的距离dist,以及标记上一个经过的顶点的Path。下图为求
的最短路径的状态表的初始化。Known=0表示没被遍历,dist无穷大表示初始其他顶点都不可达。
- 对于下图求 到其他顶点的最短路径
-
主要步骤 - (1) 标记 的Known为1,找与 最近的点即遍历 的邻接表,更新邻接表中的顶点 的Known=1,dist=0+1=1,Path= 。
- (2) 接着找离 和 的最近的点,对于 ,遍历其邻接表,更新 和 的相关状态。
- (3) 最终得到一个状态表,从改状态表中可以提取 到其他所有顶点的最短路径。
- (4) 该方法的时间复杂度为 ,主要细节在代码中描述:
template<class T>
void Graph<T>::UnweightPath(int index) // 求图中某个顶点的无权最短路径
{
if (index >= size)
cout << "Out of range";
int Currdist; // 表示遍历过程中每一次的到index顶点的距离
for (Currdist = 0;Currdist < size;Currdist++) // 每次通过Currdist来指示接下来要处理的顶点及其邻接表
{
for (int i = 0;i < size;i++) // 遍历index顶点状态信息表
{
if (VSet[index].table[i].Known == false && VSet[index].table[i].dist == Currdist) // 如果某个顶点没被经过,且dist为当前距离值
{
VSet[index].table[i].Known = true; // 改变其状态
for (vector<Vertex<T>>::iterator iter = VSet[i].adj_list.begin();iter != VSet[i].adj_list.end();iter++) // 遍历该顶点的邻接点
{
if (VSet[index].table[iter->value].dist == INF) // 如果邻接点的dist为INF
{
VSet[index].table[iter->value].dist = Currdist + 1; // 更新其dist值
VSet[index].table[iter->value].Path = VSet[i].value;
}
}
}
}
}
}
无权最短路径算法2(借助队列)
主要步骤:
- 首先将目标顶点 入队列。
- 当队列不为空时,执行循环:
- 顶点出队列,标记信息表中该顶点的状态Known=1(这个可以省去不要)。遍历该顶点的邻接表,当该顶点的邻接点的dist为无穷大时,更新它们的dist值为该顶点的dist+1,更新它们的Path值为该顶点。它们(该顶点的邻接点)入队列。
- 当队列为空时,遍历完 的所有可达顶点。关于 的状态表也更新完毕。读取状态表的Path栏,可以得到到所有顶点的最短路径。
- 借助队列的无权最短路径算法的时间复杂度为 。
template<class T>
void Graph<T>::UnweightPathQ(int index)
{
queue<Vertex<T>> Q; // 借助队列模板建立一个元素类型为Vertex的队列Q
Vertex<T> W;
Q.push(VSet[index]); // 下标为index的顶点入队列
while (!Q.empty()) // 当队列不为空
{
W = Q.front(); // 取队首元素
Q.pop(); // 出队
VSet[index].table[W.value].Known = true; // 该句可以省略
for (vector<Vertex<T>>::iterator iter = W.adj_list.begin(); iter != W.adj_list.end();iter++) // 遍历W的邻接表
{
if (VSet[index].table[iter->value].dist == INF) // 如果邻接顶点的dist为无穷大
{
VSet[index].table[iter->value].dist = VSet[index].table[W.value].dist + 1; // 则更新其dist为刚出队顶点的dist值加1
VSet[index].table[iter->value].Path = VSet[W.value].value; // 更新其Path为刚出队顶点(关键字值)
Q.push(VSet[iter->value]); // 邻接点入队列
}
}
}
}
附图的无权最短路径算法实现C++
- 上面可以看出,无论是算法1还是借助队列的算法2,求某个顶点到其他顶点的无权最短路径的过程,就是更新与之相关的状态信息表的过程。
- 故创建一个状态信息表类,顶点类包含一个状态信息表的成员。
#include<iostream>
#include<vector>
#include<stack>
#include<queue>
#include<iterator>
using namespace std;
const int INF = 0x7fffffff; // 表示最大整型数
template<class T> class Vertex; // 提前声明顶点类
template<class T>
class InfoTable { // 创建一个信息表类
public:
bool Known; // 是否被遍历
int dist; // 顶点间的距离
T Path; // 用顶点关键字表示的路径栏
};
template<class T>
class Vertex { // 创建一个顶点类
public:
T value; // 顶点的关键字值
vector<Vertex<T>> adj_list; // 顶点的邻接表
InfoTable<T>* table; // 最短路径时每个顶点的信息栏
Vertex(T value = 0) :value(value) {}
};
template<class T>
class Graph{ // 创建一个图类
public:
vector<Vertex<T>> VSet; // 表示顶点的集合
Graph(int sz) :size(sz) {} // 构造函数
Graph(const Graph<T> &G) { size = G.size;VSet = G.VSet; } // 复制构造函数
void UnweightPath(int index); // 无权最短路径
void InitInfoTable(); // 初始化图中顶点的状态信息表
void PrintPath(int index); // 打印某个节点的最短路径
void UnweightPathQ(int index); // 借助队列的指定下标顶点的无权最短路径
private:
int size; // 图中顶点的个数
};
template<class T>
void Graph<T>::InitInfoTable() // 初始化图中每个顶点的状态信息表
{
for (int i = 0;i < size;i++)
{
VSet[i].table = new InfoTable<T>[size]; // 为每个顶点的状态表申请空间
for (int j = 0;j < size;j++)
{
VSet[i].table[j].Known = false; // 每个节点都没被经过
VSet[i].table[j].dist = INF; // 初始时每个顶点距离为无穷,表示不可达
VSet[i].table[j].Path = -1; // 初始Path为-1,方便以后遍历
}
VSet[i].table[i].dist = 0; // 初始时每个顶点距离自身为0
}
}
template<class T>
void Graph<T>::UnweightPath(int index) // 求图中某个顶点的无权最短路径
{
if (index >= size)
cout << "Out of range";
int Currdist; // 表示遍历过程中每一次的到index顶点的距离
for (Currdist = 0;Currdist < size;Currdist++)
{
for (int i = 0;i < size;i++) // 遍历该顶点的状态表
{
if (VSet[index].table[i].Known == false && VSet[index].table[i].dist == Currdist) // 如果某个顶点没被经过,且dist为当前距离值
{
VSet[index].table[i].Known = true; // 改变其状态
for (vector<Vertex<T>>::iterator iter = VSet[i].adj_list.begin();iter != VSet[i].adj_list.end();iter++) // 遍历该顶点的邻接点
{
if (VSet[index].table[iter->value].dist == INF) // 如果邻接点的dist为INF
{
VSet[index].table[iter->value].dist = Currdist + 1; // 更新其dist值
VSet[index].table[iter->value].Path = VSet[i].value;
}
}
}
}
}
}
template<class T>
void Graph<T>::UnweightPathQ(int index)
{
queue<Vertex<T>> Q; // 借助队列模板建立一个元素类型为Vertex的队列Q
Vertex<T> W;
Q.push(VSet[index]); // 下标为index的顶点入队列
while (!Q.empty()) // 当队列不为空
{
W = Q.front(); // 取队首元素
Q.pop(); // 出队
VSet[index].table[W.value].Known = true; // 该句可以省略
for (vector<Vertex<T>>::iterator iter = W.adj_list.begin(); iter != W.adj_list.end();iter++) // 遍历W的邻接表
{
if (VSet[index].table[iter->value].dist == INF) // 如果邻接顶点的dist为无穷大
{
VSet[index].table[iter->value].dist = VSet[index].table[W.value].dist + 1; // 则更新其dist为刚出队顶点的dist值加1
VSet[index].table[iter->value].Path = VSet[W.value].value; // 更新其Path为刚出队顶点(关键字值)
Q.push(VSet[iter->value]); // 邻接点入队列
}
}
}
}
template<class T>
void Graph<T>::PrintPath(int index)
{
cout << "The InfoTable of V" << index << " is:\n";
for (int i = 0;i < size;i++) // 打印下标为index的顶点的状态表
{
cout<<"V"<<i<<" "<<VSet[index].table[i].Known<<" " << VSet[index].table[i].dist
<< " "<<"V"<< VSet[index].table[i].Path << endl;
}
cout << "\nShow the unweighted paths form V"<< index << " to other vertices: \n";
stack<T> S; // 借助栈输出从index顶点出发到各个顶点的无权最短路径
for (int i = 0;i < size;i++)
{
int j = i; // 每次取一个顶点作为开始
S.push(VSet[j].value); // 顶点入栈
while (VSet[index].table[j].Path != -1) // 当遍历指定顶点的“Path”形成路径
{
S.push(VSet[index].table[j].Path); // 将路径上的顶点的Path值入栈
j = VSet[index].table[j].Path; // 更新顶点下标
}
while (!S.empty()) // 栈不为空时,打印路径并出栈
{
cout << "->";
cout << "V" << S.top();
S.pop();
}
cout << endl;
}
cout << endl;
}
int main()
{
Graph<int> G(7); // 创建一个图对象G
Vertex<int> V[] = { Vertex<int>(0), Vertex<int>(1), Vertex<int>(2), Vertex<int>(3),
Vertex<int>(4), Vertex<int>(5), Vertex<int>(6) };
V[0].adj_list.push_back(V[1]);
V[0].adj_list.push_back(V[3]); // 顶点0的邻接表
V[1].adj_list.push_back(V[3]);
V[1].adj_list.push_back(V[4]); // 顶点1的邻接表
V[2].adj_list.push_back(V[0]);
V[2].adj_list.push_back(V[5]); // 顶点2的邻接表
V[3].adj_list.push_back(V[2]); // 顶点3的邻接表
V[3].adj_list.push_back(V[4]);
V[3].adj_list.push_back(V[5]);
V[3].adj_list.push_back(V[6]);
V[4].adj_list.push_back(V[6]); // 顶点4的邻接表
V[6].adj_list.push_back(V[5]); // 顶点6的邻接表
for (int i = 0;i < 7;i++)
{
G.VSet.push_back(V[i]);
}
G.InitInfoTable(); // 初始化图中顶点的状态信息表
G.UnweightPath(2); // 改变该顶点的状态信息表
G.PrintPath(2); // 打印从V2到其他顶点路径
Graph<int> G1(G); // 复制构造G1
G1.InitInfoTable(); // 初始化图中顶点的状态信息表
G1.UnweightPathQ(0); // 借助队列的无权最短路径,同样通过改变index顶点的状态信息表实现
G1.PrintPath(0); // 打印从V2到其他顶点路径
system("pause");
return 0;
}
- 运行结果
The InfoTable of V2 is:
V0 1 1 V2
V1 1 2 V0
V2 1 0 V-1
V3 1 2 V0
V4 1 3 V1
V5 1 1 V2
V6 1 3 V3
Show the unweighted paths form V2 to other vertices:
->V2->V0
->V2->V0->V1
->V2
->V2->V0->V3
->V2->V0->V1->V4
->V2->V5
->V2->V0->V3->V6
The InfoTable of V0 is:
V0 1 0 V-1
V1 1 1 V0
V2 1 2 V3
V3 1 1 V0
V4 1 2 V1
V5 1 2 V3
V6 1 2 V3
Show the unweighted paths form V0 to other vertices:
->V0
->V0->V1
->V0->V3->V2
->V0->V3
->V0->V1->V4
->V0->V3->V5
->V0->V3->V6
请按任意键继续. . .
参考资料
Mark Allen Weiss: 数据结构与算法分析