图的存储
图的类定义
class Graph
{
// 对象:由一个顶点的非空集合和一个边集合构成,每条边由一个顶点对来表示
public:
Graph();
// 建立一个空的图
void insertVertex(const T&vertex);
// 在图中插入一个顶点vertex,该顶点暂时没有边
void insertEdge(int v1, int v2, int weight);
// 若构成边的两个顶点v1和v2是图中的顶点,则在图中插入一条边(v1,v2)
void removeVertex(int v);
// 若被删顶点是图中的顶点,则删除顶点v和所有关联到它的边
void removeEdge(int v1, int v2);
// 若构成边的两个顶点v1和v2是图中的顶点,则在图中删除边(v1,v2)
bool IsEmpty();
// 若图中没有边,则函数返回true,否则返回false
T getWeight(int v1, int v2);
// 函数返回边(v1,v2)的权值
int getFirstNeighbor(int v);
// 给出顶点位置为v的第一个邻接顶点的位置,如果找不到,则函数返回一1
int getNextNeighbor(int v1, int v2);
// 给出顶点位置为v1的某邻接顶点v2的下一个邻接顶点的位置,如果找不到,则返回一1
void printGraph();
// 打印图的所有顶点和边
int getVertexCount();
// 返回图中顶点的数量
int getEdgeCount();
// 返回图中边的数量
};
图的模板基类
#define MAX_VERTICES 30
// 最大顶点数
#define MAX_EDGES 40
// 最大边数
#define MAX_WEIGHT 999
// 代表无穷大的值
template <class T, class E>
class Graph {
// 图的类定义
public:
bool GraphEmpty() {
return (numEdges == 0);
}
bool GraphFull() {
return (numVertices == MAX_VERTICES) && (numEdges == MAX_VERTICES * (MAX_VERTICES - 1) / 2);
}
int NumberOfVertices() {
return numVertices;
}
// 返回当前顶点数
int NumberOfEdges() {
return numEdges;
}
// 返回当前边数
virtual T getValue(int i) = 0;
// 取顶点i的值,i不合理返回0
virtual E getWeight(int v1, int v2) = 0;
// 取边(v1,v2)上的权值
virtual int getVertexPos(T vertex) = 0;
// 给出顶点vertex在图中的位置
virtual int getFirstNeighbor(int v) = 0;
// 取顶点v的第一个邻接顶点
virtual int getNextNeighbor(int v, int w) = 0;
// 取邻接顶点w的下一个邻接顶点
virtual bool insertVertex(T vertex) = 0;
// 插入一个顶点vertex
virtual bool insertEdge(int v1, int v2, E cost) = 0;
// 插入边(v1,v2), 权为cost
virtual bool removeVertex(int v, T& vertex) = 0;
// 删除顶点v和所有与其相关联的边
virtual bool removeEdge(int v1, int v2, E& cost) = 0;
// 在图中删除边(v1,v2)
protected:
int numEdges;
// 当前边数
int numVertices;
// 当前顶点数
};
邻接矩阵表示的图
#include "Graph.h"
template <class T,class E>
class Graphmtx : public Graph<T,E> {
//图的邻接矩阵类定义
friend istream&operator >>(istream&in,Graphmtx<T,E>&G);
//输入
friend ostream& operator<<(ostream& out,Graphmtx<T,E>&G);
public:
Graphmtx(int sz = DefaultVertices);
//构造函数
~Graphmtx();
//析构函数
int getVertexPos(T vertex);
//给出顶点vertex在图中的位置
T getValue(int i);
//取顶点i的值,i不合理返回0
E getWeight(int v1,int v2);
//取边(v1,v2)上的权值
int getFirstNeighbor(int v);
//取顶点v的第一个邻接顶点
int getNextNeighbor(int v,int w);
//取v的邻接顶点w的下一个邻接顶点
bool insertVertex(T vertex);
//插入顶点vertex
bool insertEdge(int v1,int v2,E cost);
//插入边(v1,v2),权值为cost
bool removeVertex(int v, T& vertex);
//删除顶点v和所有与它相关联的边
bool removeEdge(int v1,int v2,E& cost);
//在图中删除边(v1,v2)
private:
T VerticesList[maxVertices];
//顶点表,定义所有顶点所在的位置
E Edge[maxVertices][maxVertices];
//邻接矩阵
};
template <class T,class E>
int Graphmtx<T,E>::getVertexPos(T vertex){
//给出顶点vertex在图中的位置
for (int i =0;i<numVertices;i++)
if (VerticesList[i]==vertex) return i;
return -1;
}
template <class T,class E>
Graphmtx<T,E>::Graphmtx(int sz) : numVertices(0), numEdges(0) {
//初始化矩阵
for (int i=0;i<maxVertices;i++)
for (int j=0;j<maxVertices;j++)
Edge[i][j]= (i==j)?0:maxWeight;
}
template <class T,class E>
int Graphmtx<T,E>::getFirstNeighbor(int v){//第一是指数值的第一第二第三
//给出顶点位置为v的第一个邻接顶点的位置,如果找不到,则函数返回-1
if(v!=-1){
for (int col = 0; col < numVertices; col++)
if (Edge[v][col]>0 && Edge[v][col]<maxWeight) return col;
}
return -1;
}
template <class T,class E>
int Graphmtx<T,E>::getNextNeighbor(int v,int w){
//给出顶点v的某邻接顶点w的下一个邻接顶点
if(v!=-1 && w!=-1){
for (int col = w+1; col < numVertices; col++)
if (Edge[v][col]>0 && Edge[v][col]<maxWeight) return col;
}
return -1;
}
template <class T,class E>
bool Graphmtx<T,E>::insertVertex(T vertex) {
//插入顶点vertex
if (numVertices == maxVertices) return false;
//顶点表满,不插入
VerticesList[numVertices] = vertex;
//新加顶点信息
numVertices++;
return true;
//顶点数加1
}
template <class T,class E>
bool Graphmtx<T,E>::removeVertex(int v, T& vertex){
//删除顶点v和所有与它相关联的边,用最后的顶点跟他互换位置
if (v < 0 || v >= numVertices) return false;
//v不在图中,不删除
if (numVertices == 1) return false;
//只剩一个顶点,不删除
for (int i = 0; i < numVertices; i++){
//修改图的边数
if (Edge[v][i] > 0 && Edge[v][i] < maxWeight) numEdges--;}
vertex = VerticesList[v];
//顶点表中删除该顶点
VerticesList[v] = VerticesList[numVertices - 1];
for (int i = 0; i < numVertices; i++)
//用最后一列填补第v列
Edge[i][v] = Edge[i][numVertices - 1];
for (int i = 0; i < numVertices; i++)
//用最后一行填补第v行
Edge[v][i] = Edge[numVertices - 1][i];
numVertices--;
return true;
//顶点数减1
}
template <class T,class E>
bool Graphmtx<T,E>::insertEdge(int v1,int v2,E cost){
//插入边(v1,v2),权值为cost;插入的是编号不是数字
if (v1 >-1 && v1<numVertices && v2 >-1 && v2 < numVertices){
if (Edge[v1][v2]==0 || Edge[v1][v2]==maxWeight){
numEdges++;
//插入边后边数加1
Edge[v1][v2]=Edge[v2][v1]=cost;
//插入边
return true;
}
}
return false;
}
template <class T,class E>
bool Graphmtx<T,E>::removeEdge(int v1,int v2,E&cost){
//在图中删除边(v1,v2)
if (v1 >-1 && v1 < numVertices && v2 >-1 && v2 < numVertices){
if (Edge[v1][v2]>0 && Edge[v1][v2]<maxWeight){
cost = Edge[v1][v2];
//保存边上权值
Edge[v1][v2]=Edge[v2][v1]=maxWeight;
numEdges--;
return true;
}
}
return false;
}
template <class T,class E>
istream& operator >>(istream& in, Graphmtx<T,E>& G){
//通过从输入流对象in输入n个顶点信息和e条无向边的信息建立用邻接矩阵表示的图G。邻接
//矩阵初始化的工作已经在构造函数中完成
int i, j, k, n, m;
T e1, e2;
E weight;
cout << "输入顶点数、边数" << endl;
in >> n >> m;
//输入顶点数n和边数m
for (i = 0; i < n; i++){
//建立顶点表数据
cout << "输入顶点值";
in >> e1;
G.insertVertex(e1);
}
i = 0;
while (i < m){
cout << "输入边信息" << endl;
in >> e1 >> e2 >> weight;
//输入端点信息
j = G.getVertexPos(e1);
k = G.getVertexPos(e2); //查顶点号
if(j == -1 || k == -1)
cout << "边两端点信息有误,重新输入!" << endl;
else {
G.insertEdge(j, k, weight);
i++;
}
}
return in;
}
template <class T,class E>
ostream& operator<<(ostream& out, Graphmtx<T,E>& G){
//输出图的所有顶点和边的信息
int i, j, n, m;
T e1, e2;
E w;
n = G.NumberOfVertices();
m = G.NumberOfEdges();
out << "顶点数" << n << ",边数" << m << endl;
//输出顶点数与边数
out << "所有顶点信息为" << endl;
for (i = 0; i < n; i++) out << G.getValue(i) << " ";
out << endl << "所有边的信息为" << endl;
for (i = 0; i < n; i++)
for (j = i + 1; j < n; j++){
w = G.getWeight(i, j);
//取边上权值
if (w > 0 && w < maxWeight){
//有效
e1 = G.getValue(i);
e2 = G.getValue(j);
//取顶点号
out << "(" << e1 << "," << e2 << "," << w << ")";
out << endl;
}
}
return out;
}
邻接表表示的图
#include "Graph.h"
template <class T, class E>
struct Edge {
//边结点的定义
int dest; //边的另一个顶点位置
E cost; //边上的权值
Edge<T, E>* link; //下一条边链指针
Edge() {} //构造函数
Edge(int num, E weight) : dest(num), cost(weight), link(NULL) {} //构造函数
bool operator!=(Edge<T, E>& R) const { //判断边不等否
return (dest != R.dest) ? true : false;
}
};
template <class T, class E>
struct Vertex {
//顶点的定义
T data; //顶点的名字
Edge<T, E> adj; //边链表的头指针
};
template <class T, class E>
class Graphlnk : public Graph<T, E> {
//图的类定义
friend istream& operator>>(istream& in, Graphlnk<T, E>& G); //输入
friend ostream& operator<<(ostream& out, Graphlnk<T, E>& G); //输出
public:
Graphlnk(int sz = DefaultVertices); //构造函数
~Graphlnk(); //析构函数
int getVertexPos(T vertex); //取顶点vertex在结点表中的序号
T getValue(int i); //取位置为i的顶点中的值
E getWeight(int v1, int v2); //返回边(v1,v2)上的权值
bool insertVertex(T vertex); //在图中插入一个顶点vertex
bool removeVertex(int v, T& vertex); //在图中删除一个顶点v
bool insertEdge(int v1, int v2, E cost); //在图中插入一条边(v1,v2)
bool removeEdge(int v1, int v2, E& cost); //在图中删除一条边(v1,v2)
int getFirstNeighbor(int v); //取顶点v的第一个邻接顶点
int getNextNeighbor(int v, int w); //取v的邻接顶点w的下一个邻接顶点
private:
Vertex<T, E> NodeTable[maxVertices]; //顶点表(各边链表的头结点)
};
template<class T, class E>
int Graphlnk<T, E>::getVertexPos(T vertex) {
//给出顶点vertex在图中的位置
for (int i = 0; i < numVertices; i++) {
if (NodeTable[i].data == vertex) {
return i;
}
}
return -1;
}
template <class T,class E>
Graphlnk<T,E>::Graphlnk() {
//构造函数:建立一个空的邻接表
numVertices = 0; numEdges = 0;
for (int i = 0; i < maxVertices; i++) NodeTable[i].adj = NULL;
}
template <class T,class E>
Graphlnk<T,E>::~Graphlnk() {
//析构函数:删除一个邻接表
for (int i = 0; i < numVertices; i++) {
//删除各边链表中的结点
Edge<T,E>* p = NodeTable[i].adj;
//找到其对应边链表的首结点
while (p != NULL) {
//不断地删除第一个结点
NodeTable[i].adj = p->link;
delete p;
p = NodeTable[i].adj;
}
}
}
template <class T,class E>
int Graphlnk<T,E>::getFirstNeighbor(int v) {
//给出顶点位置为v的第一个邻接顶点的位置,如果找不到,则函数返回-1
if (v != -1) {
//顶点v存在
Edge<T,E>* p = NodeTable[v].adj;
//对应边链表第一个边结点
if (p != NULL) return p->dest;
//存在,返回第一个邻接顶点
}
return -1;
//第一个邻接顶点不存在
}
template <class T,class E>
int Graphlnk<T,E>::getNextNeighbor(int v, int w) {
//给出顶点v的邻接顶点w的下一个邻接顶点的位置,若没有下一个邻接顶点,则函数返回-1
if (v != -1) {
//顶点v存在
Edge<T,E>* p = NodeTable[v].adj;
//对应边链表第一个边结点
while (p != NULL && p->dest != w)
//寻找邻接顶点w
p = p->link;
if (p != NULL && p->link != NULL)
return p->link->dest;
//返回下一个邻接顶点
}
return -1;
//下一个邻接顶点不存在
}
图的遍历
dfs
深度优先搜索是一个不断探查和回溯的过程。在探查的每一步,算法都有一个当前顶点。最初的当前顶点就是指定的起始顶点。每一步探查过程中,首先对当前顶点进行访问,并立即设置该顶点的访问标志visited[v]=true。接着在v的所有邻接顶点中,找出尚未访问过的一个,将其作为下一步探查的当前顶点。如果当前顶点的所有邻接顶点都已经被访问过,则退回一步,将前一步所访问的顶点重新取出,当作探查的当前顶点。重复上述过程,直到最初指定起始顶点的所有邻接顶点都被访问到。此时连通图中的所有顶点也必然都被访问过了。
template<class T, class E>
void DFS_recur(Graphlnk<T, E>& G, int v, bool visited[]) {
// 从顶点位置v出发,以深度优先的次序访问所有可到达的尚未被访问过的顶点。算法中用到一个辅助数组visited,对已访问过的顶点做访问标记
cout << G.getValue(v) << " ";
// 访问顶点v
visited[v] = true;
// 顶点v做访问标记
int w = G.getFirstNeighbor(v);
// 找顶点v的第一个邻接顶点w
while (w != -1) {
// 若邻接顶点w存在
if (visited[w] == false) {
DFS_recur(G, w, visited);
// 若w未访问过,递归访问顶点w
}
w = G.getNextNeighbor(v, w);
// 取v排在w后的下一个邻接顶点
}
}
template<class T, class E>
void DFS_recur(Graphlnk<T, E>& G, T v) {
// 从顶点v出发,对图G进行深度优先遍历的主过程
int loc, n = G.NumberOfVertices();
// 取图中顶点个数
z
// 创建辅助数组visited
for (int i = 0; i < n; i++) {
visited[i] = false;
}
// 辅助数组visited初始化
loc = G.getVertexPos(v);
DFS_recur(G, loc, visited);
// 从顶点loc开始深度优先搜索
delete[] visited;
// 释放visited
}
bfs
与深度优先搜索方法不同,广度优先搜索方法没有探索和回溯的过程,而是一个逐层遍历的过程。在此过程中,图中有多少个顶点就要重复多少步。每步都有一个当前顶点。最初的当前顶点是主过程指定的起始顶点。在每步中,首先访问当前顶点,并设置该顶点的访问标志visited[v]=true。接着依次访问v的各个未曾被访问过的邻接顶点w1,w2,…,然后再顺序访问w1,w2,…的所有还未被访问过的邻接顶点。再从这些访问过的顶点出发,访问它们的所有还未被访问过的邻接顶点,如此做下去,直到图中所有顶点都被访问到为止。
图8.13(a)展示了从顶点A出发进行广度优先搜索的过程。图中各顶点旁边的数字标明了顶点访问的顺序。图8.13(b)展示了经由广度优先搜索得到的广度优先生成树,它由遍历时访问过的n个顶点和遍历时经历的n-1条边组成。
广度优先搜索不是一个递归的过程,其算法也不是递归的。为了实现逐层访问,算法中使用了一个队列,以记忆正在访问的这一层和上一层的顶点,便于向下一层访问。另外,与深度优先搜索过程一样,为避免重复访问,需要一个辅助数组visited[],给被访问过的顶点加标记。程序8.10给出广度优先搜索的算法。
template <class T, class E>
void BFS(Graph<T, E> &G, T v) {
// 从顶点v出发,以广度优先的次序横向搜索图,算法中使用了一个队列
int i, w, n = G.NumberOfVertices(); // 取图中顶点个数
bool *visited = new bool[n]; // visited记录顶点是否被访问过
for (i = 0; i < n; i++) visited[i] = false; // 初始化
int loc = G.getVertexPos(v); // 取顶点号
cout << G.getValue(loc) << ""; // 访问顶点v,做已访问标记
visited[loc] = true;
LinkedQueue<int> Q;
Q.EnQueue(loc); // 顶点进队列,实现分层访问
while (!Q.IsEmpty()) { // 循环,访问所有结点
Q.DeQueue(loc); // 从队列中退出顶点loc
w = G.getFirstNeighbor(loc); // 找顶点loc的第一个邻接顶点w
while (w != -1) { // 若邻接顶点w存在
if (visited[w] == false) { // 若未被访问过
cout << G.getValue(w) << ""; // 访问顶点w
visited[w] = true;
Q.EnQueue(w); // 顶点w进队列
}
w = G.getNextNeighbor(loc, w); // 找顶点loc的下一个邻接顶点
} // 重复检测v的所有邻接顶点
} // 外层循环,判队列空否
delete[] visited;
}
连通分量
template <class T, class E>
void ConnectCom_BFS(Graphlnk<T, E> &G) {
// 通过BFS遍历非连通图G:输出求得的所有连通分量
LinkedQueue<int> Q;
int i, u, w;
bool visited[maxVertices]; // 定义访问标志数组
for (i = 0; i < G.NumberOfVertices(); i++) visited[i] = false;
for (i = 0; i < G.NumberOfVertices(); i++) { // 对所有顶点检查
if (visited[i] == false) { // 从顶点i做BFS遍历
Q.EnQueue(i);
visited[i] = true;
cout << "(" << G.getValue(i);
while (!Q.IsEmpty()) { // 队列不空
Q.DeQueue(u);
w = G.getFirstNeighbor(u);
while (w != -1) {
if (visited[w] == false) {
cout << "," << G.getValue(w);
Q.EnQueue(w);
visited[w] = true;
}
w = G.getNextNeighbor(u, w);
}
}
cout << ")" << endl; // 一个连通分量遍历完
}
}
}
最小生成树
连通图中的每棵生成树,都是原图的一个极小连通图。也就是说,从生成树中删去任何一条边,生成树就不再连通;反之,在生成树中引入任何一条新边,都会形成(恰好)一个回路。
按照不同的遍历算法,会得到不同的生成树;从不同的顶点出发,得到的生成树也有所不同。对于一个带权图(即网络),不同的生成树所对应的总权值也不尽相同。那么,如何找出总权值最小的生成树呢?在多数可以表示为带权图的实际应用中,都会遇到这一问题。
例如,在规划建立n个城市之间的通信网络时,至少要架设n-1条线路。如果在任何两个城市间建立通信线路的成本已经确定,那么如何使总造价最低?若用顶点表示城市,用边表示城市之间的通信线路,边上的权值表示线路对应的造价,就可以将这一问题表示为一个带权图。为了建立成本最低的通信网络,就要找出该网络的一棵最小(代价)生成树(minimum-cost spanning tree)。
按照定义,若连通网络由n个顶点组成,则其生成树必含n个顶点、n-1条边。因此,构造最小生成树的准则有3条:
- 只能使用该网络中的边来构造最小生成树;
- 只能使用恰好n-1条边来联结网络中的n个顶点;
- 选用的这n-1条边不能构成回路。
构造最小生成树的方法有多种,典型的有两种:Kruskal算法和Prim算法。这两个算法都采用了逐步求解的策略,也称贪心策略:给定带权图N={V,E},V中共有n个顶点。首先构造一个包括全部n个顶点和0条边的森林F={T0,T1,…,Tm-1},然后不断迭代。每经过一轮迭代,就会在F中引入一条边。经过n-1轮迭代,最终得到一棵包含n-1条边的最小生成树。
需要指出的是,同一带权图可能有多棵最小生成树。例如,当有多条边具有相等的权值时,很可能出现这种现象。
Kruskal算法
该算法的基本过程:任给一个有n个顶点的连通网络N={V,E},首先构造一个由这n个顶点组成、不含任何边的图T={V,φ},其中每个顶点自成一个连通分量。不断从E中取出权值最小的一条边(若有多条,任取其一),若该边的两个端点来自不同的连通分量,则将此边加入T中。如此重复,直到所有顶点在同一个连通分量上为止。
Kruskal算法的伪代码描述
T=φ;
//T是最小生成树的边集合,初值为空;E是无向带权图的边集合
while(T包含的边少于n-1&&E不空){
从E中选一条具有最小代价(cost)的边(v,w);
从E中删除(v,w);
如果(v,w)加到T中后不会产生回路,则将(v,w)加入T;否则放弃(v,w);
}
if(T中包含的边少于n-1条)cout<<"不是最小生成树"<<endl;
例如,针对图8.17(a)所示的带权图,首先将所有顶点组成一个如图8.17(b)所示的非连通图,然后不断迭代。每次迭代时,选出一条具有最小权值,且两端点不在同一连通分量上的边,加入生成树当中。图8.17(a)共有7个顶点,故经过6轮迭代(见图8.17©~(h)),就可以得到在图8.17(h)中由6条边组成的一棵生成树。
#define maxValue 999
//机器可表示的、问题中不可能出现的大数
#define maxSize 30
template <class T>
struct MSTEdgeNode {
//最小生成树边结点的类声明
int tail, head;
T key;
//两顶点位置及边上的权值
MSTEdgeNode() : tail(-1), head(-1), key(0) {} // 构造函数
bool operator<=(MSTEdgeNode<T>& R) { return key <= R.key; }
bool operator>(MSTEdgeNode<T>& R) { return key > R.key; }
};
template <class T>
class MinSpanTree {
//最小生成树的类定义
protected:
MSTEdgeNode<T> edgevalue[maxSize];
//用边值数组表示树
int maxSize, n;
//数组的最大元素个数和当前个数
public:
MinSpanTree() { n = 0; }
int Insert(MSTEdgeNode<T> item);
void printEdges();
};
template <class T>
int MinSpanTree<T>::Insert(MSTEdgeNode<T> item) {
edgevalue[n++] = item;
}
template <class T>
void MinSpanTree<T>::printEdges() {
cout << "依次输出最小生成树的边:" << endl;
for (int i = 0; i < n; ++i) {
cout << "(" << edgevalue[i].tail << "," << edgevalue[i].head
<< "," << edgevalue[i].key << ")";
}
cout << endl;
}
Kruskal算法的实现
#include "Graphlnk.cpp"
#include "minHeap.cpp"
#include "UFSets.h"
#include "MinSpanTree.cpp"
template <class T, class E>
void Kruskal(Graphlnk<T, E>& G, MinSpanTree<E>& MST) {
Element<E> e;
MSTEdgeNode<E> ed;
int u, v, count;
int n = G.NumberOfVertices();
//顶点数
int m = G.NumberOfEdges();
//边数
minHeap<E> H;
//最小堆,关键码类型为E
UFSets F;
//并查集
for (u = 0; u < n; u++)//堆的初始化
for (v = u + 1; v < n; v++)
if (G.getWeight(u, v) > 0 && G.getWeight(u, v) < maxWeight) {
e.i = u;
e.j = v;
//插入堆
e.key = G.getWeight(u, v);
H.Insert(e);
}
count = 1;
//最小生成树加入边数计数
while (count < n) {
//反复执行,取n-1条边
H.Remove(e);
//从最小堆中退出具最小权值的边ed
u = F.Find(e.i);
v = F.Find(e.j);
//取两顶点所在集合的根u与v
if (u != v) {
//不是同一集合,说明不连通
F.Merge(u, v);//并查集的组合
//合并,连通它们
ed.tail = e.i;
ed.head = e.j;
ed.key = e.key;
MST.Insert(ed);
//该边存入最小生成树
count++;
}
}
}
这是Kruskal算法的实现。该算法接受一个Graphlnk对象G和一个MinSpanTree对象MST作为参数。首先,将G中的所有边插入最小堆H。然后,使用并查集F来判断边是否可以添加到最小生成树中。如果边的两个顶点不在同一集合中,将它们合并,并将边添加到最小生成树MST中。算法反复执行,直至最小生成树中包含n-1条边。
prime算法
#include "Graphlnk.cpp"
#include "minHeap.cpp"
#include "MinSpanTree.cpp"
template <class T, class E>
void Prim(Graphlnk<T, E>& G, T u0, MinSpanTree<E>& MST) {
Element<E> e;
MSTEdgeNode<E> ed;
int i, u, v, count;
int n = G.NumberOfVertices();
//顶点数
int m = G.NumberOfEdges();
//边数
u = G.getVertexPos(u0);
//求起始顶点号u
minHeap<E> H;
//最小堆,关键码类型为E
bool* Vmst = new bool[n];
//最小生成树顶点集合
for (i = 0; i < n; i++) Vmst[i] = false;
Vmst[u] = true;
//u加入生成树
count = 1;
do {
//最小堆的构建
v = G.getFirstNeighbor(u);
while (v != -1) {//最小堆的构建
//重复检测u的所有邻接顶点
if (Vmst[v] == false) {
//若v不在生成树,(u,v)加入堆
e.i = u;
e.j = v;
e.key = G.getWeight(u, v);
//u在树内,v不在树内
H.Insert(e);
}
v = G.getNextNeighbor(u, v);
//找顶点u的下一个邻接顶点v
}
while (H.IsEmpty() == false && count < n) {
H.Remove(e);
//从堆中退出具最小权值的边e
if (Vmst[e.j] == false) {
ed.tail = e.i;
ed.head = e.j;
ed.key = e.key;
MST.Insert(ed);
//加入最小生成树
u = ed.head;
Vmst[u] = true;
//u加入生成树顶点集合
count++;
break;
}
}
} while (count < n);
delete[] Vmst;
}
最短路径
边权全部为整数的算法
Dijkstra求最短路径的算法
#include "DGraphmtx.cpp"
template <class T, class E>//DGraphmtx为有向带权图的邻接矩阵
void ShortestPath(DGraphmtx<T, E>& G, int v, E dist[], int path[]) {
int n = G.NumberOfVertices();
bool* S = new bool[n]; //最短路径顶点集
int i, j, k;
E w, min;
//在Dijkstra的最短路径算法中,path[]数组用于记录每个顶点的前一个顶点的位置,也就是在最短路径中,从起始顶点到达该顶点的前一个顶点。
//例如,如果我们有一个从顶点A到顶点B的最短路径是A->C->B,那么在path[]数组中,path[B]的值就是C,表示在最短路径中,B的前一个顶点是C。
//通过这个path[]数组,我们可以方便地找出从起始顶点到任何一个顶点的最短路径。只需要从目标顶点开始,沿着path[]数组反向遍历,直到回到起始顶点,就得到了一条最短路径。
for (i = 0; i < n; i++) {
dist[i] = G.getWeight(v, i); //数组初始化
S[i] = false;
if (i != v && dist[i] < maxWeight) path[i] = v;
else path[i] = -1;
}
S[v] = true;
dist[v] = 0; //ist[]数组用来存储从起始顶点到图中每一个顶点的最短路径长度。例如,如果我们想要找出从顶点A到顶点B的最短路径长度,我们可以查看dist[B]的值。
for(i = 0; i < n-1; i++) {
min = maxWeight;
int u = -1; //选不在S中具有最短路径的顶点u
for (j = 0; j < n; j++) {
if (S[j] == false && dist[j] < min) {//S[j]表示有没有进行过运算,dist[j]表示和初始点有没有路
u = j; min = dist[j];
}
}
S[u] = true; //将顶点u加入集合S
for (k = 0; k < n; k++) {
w = G.getWeight(u, k);
if (S[k] == false && w < maxWeight && dist[u] + w < dist[k]) {
//顶点k未加入S,且绕过u可以缩短路径
dist[k] = dist[u] + w;
path[k] = u; //修改到k的最短路径
}
}
}
delete[] S;
}
这是Dijkstra求最短路径的算法。该算法接受一个DGraphmtx
对象G,一个起始顶点v,一个用于存储最短路径长度的数组dist和一个用于存储最短路径的数组path作为参数。算法首先将所有顶点的最短路径长度初始化为无穷大,然后设置起始顶点v的最短路径长度为0。然后,迭代地执行以下步骤:从未加入S的顶点中选择一个最短路径长度最小的顶点u,并将其加入S。然后,更新所有从v出发经过u到达的顶点的最短路径长度。当所有顶点都加入S后,算法终止。
当已经更新了最短路了之后不会对这个进行判断,即跟 v 0 v_0 v0直接邻接的最短边一定是最短路,所以一旦有负数存在,就都无法判断;下图中用dij算法会把0到1的最短路径设置为1
![image-20231213165141497](https://i-blog.csdnimg.cn/blog_migrate/86164307281b5171c75d9cfb66f3e683.png)
任意权值的单源最短路径
所有顶点之间的最短路径
AOV网络(用顶点标志活动的网络)
#include "DGraphlnk.cpp"
// DGraphlnk是有向图的邻接表表示
template <class T, class E>
void TopologicalSort(DGraphlnk<T, E>& G) {
int i, j, w, v;
int top = -1;
// 入度为零顶点的栈初始化
int n = G.NumberOfVertices(); // 修正:加上分号
// 网络中顶点个数
int* count = new int[n]; // 修正:new int[n],并添加分号
// 入度数组兼入度为零顶点栈
// 检查有向图所有的边
for (i = 0; i < n; i++) {
j = G.getFirstNeighbor(i);
while (j != -1) {
count[j]++;
// 邻接顶点入度加1
j = G.getNextNeighbor(i, j);
}
}
for (i = 0; i < n; i++) {
// 检查网络所有顶点
if (count[i] == 0) {
count[i] = top;
top = i;
}
// 入度为零的顶点进栈
}
for (i = 0; i < n; i++) {
// 期望输出n个顶点
if (top == -1) {
// 中途栈空,转出
cout << "网络中有回路!" << endl;
return;
} else {
// 继续拓扑排序
v = top;
top = count[top];
// 退栈v
cout << G.getValue(v) << " ";
// 输出
w = G.getFirstNeighbor(v);
while (w != -1) {
// 扫描出边表
if (--count[w] == 0) {
count[w] = top;
top = w;
}
// 顶点入度减至零,进栈
w = G.getNextNeighbor(v, w);
}
}
}
delete[] count; // 修正:添加删除动态数组的语句
}
AOE网络(用边表示活动的网络)
这tm是有向图?