数据结构中图的邻接矩阵构建及其操作(C++语言)
主要是记录一下学习图结构过程中的一些操作,也算是方便以后找工作复习吧,这里主要放上图结构的邻接矩阵的建立及相关操作的总体代码。学习的参考书是:吴艳等编著的《数据结构(用C++语言描述)》,但是书中代码个别部分有一些错误,我修改了,所以下面的代码与原书中有稍微不同。(这些代码是我一边理解原理一边参考书敲出来的,在vs上敲完之后进行过简单地测试,基本能够实现每个模块的功能,里面的注释有的是书上的,有的是我认为不好理解自己额外添加的。这里主要是一个总体代码的汇总,有时间的话我可能会把里面的算法代码分开放置成文章,添加原理解释部分)
以下以邻接矩阵形式存储的图代码主要包括:
1、图的建立以及基本操作(例如:添加或删除顶点,获取边或弧等等)。
2、最小生成树的实现,包括:prim(普里姆)算法、Kruskal(克鲁斯卡尔)算法
3、最短路径求解的实现,包括:Dijkstra(迪杰斯特拉)算法,(从一个顶点到其余各顶点的最短路径)、Floyd(弗洛伊德)算法,(每对顶点之间的最短路径)。
#pragma once
#include<iostream>
using namespace std;
const int maxWeight = 999;
const int DefaultVertices = 30;
template<class T, class E>
class Graphmtx
{
private:
T * VerticesList; //顶点序列表
E Edge[DefaultVertices][DefaultVertices]; //邻接矩阵
int maxVertices; //图中的最大顶点数
int numEdges; //当前的边数
int numVertices; //当前的顶点数
int direction; //0:无向图,1:有向图
int getVertexPos(T vertex) //给出当前顶点vertext在图中的位置
{
for (int i = 0; i < numVertices; i++)
{
if (VerticesList[i] == vertex) { return i; }
}
return -1;
}
public:
Graphmtx(int sz = DefaultVertices, int d = 0); //构造函数,默认为无向图
~Graphmtx() { delete[]VerticesList; }
int NumberOfVertices() { return numVertices; } //获取图中的顶点的个数
int NumberOfEdges() { return numEdges; } //获取图中的边或弧数量
T getValue(int i)const //获取顶点的信息值
{
return i<0 && i>maxVertices ? NULL : VerticesList[i];
}
E getWeight(int v1, int v2)const //获取边或弧(v1,v2)的值
{
if (v1<0 || v1>numVertices || v2<0 || v2>numVertices) { return 0; }
else { return Edge[v1][v2]; }
}
int getFirstNeighbor(int v)const; //获取顶点v的第一个邻接点序号
int getNextNeighbor(int v, int w)const;
bool insertVertex(const T v); //插入一个顶点,成功true,失败false
bool insertEdge(int v1, int v2, E cost); //插入边或弧
bool removeVertex(int v); //移除顶点以及相关的边或弧
bool removeEdge(int v1, int v2); //删除边或弧
template<class T, class E> //原书中没有,在类内声明友元函数,需要在需要在前面加上模板头template<class T, class E>
friend istream & operator>>(istream &in, Graphmtx<T, E> &G); //建立图的邻接矩阵,重载运算符>>,C++prime书中395页有说明
template<class T, class E>
friend ostream & operator<<(ostream &out, Graphmtx<T, E> &G); //输出图的信息
//prim算法,最小生成树
void Prim(T vertex);
int minimum(); //取得代价的最小边
//Kruskal算法,最小生成树
typedef struct
{
int vex1;
int vex2;
E weight;
}EdgeSet; //定义一个边集数组
void Kruskal();
void InsertSort(EdgeSet EE[]);
//Dijkstra算法,最短路径(求顶点V到其余各顶点的关键路径)
void Dijikstra(const int v);
//Floyd(弗洛伊德)算法,最短路径(每对顶点之间的最短路径)
void Floyd();
void Dispath(int D[][DefaultVertices], int path[][DefaultVertices], int n);
void Ppath(int path[][DefaultVertices], int i, int j);
};
//初始化,建立一个空图,为顶点开辟空间,顶点之间未建立边或弧
template<class T, class E>
Graphmtx<T, E>::Graphmtx(int sz, int d)
{
int i, j;
maxVertices = sz; VerticesList = new T[maxVertices]; //开辟顶点空间表
for (i = 0; i < maxVertices; i++) //对邻接矩阵进行初始化
{
for (j = 0; j < maxVertices; j++)
{
if (i == j) { Edge[i][j] = 0; }
else { Edge[i][j] = maxWeight; } //顶点之间没有弧或边
}
}
numVertices = 0; numEdges = 0; direction = d;
}
//获取顶点v的第一个邻接点序号
template<class T, class E>
int Graphmtx<T, E>::getFirstNeighbor(int v)const //获取顶点v的第一个邻接点序号,若没有。返回一1
{
if (v < 0||v>numVertices) {return -1;}
for (int col = 0; col < numVertices; col++) //从第0列开始搜索所有列
{
if (Edge[v][col] > 0 && Edge[v][col] < maxWeight) { return col; } //有边则返回该列数
}
return -1;
}
//查找顶点v下一个邻接点的操作
template<class T, class E>
int Graphmtx<T, E>::getNextNeighbor(int v, int w)const
{
if (v<0 || v>numVertices || w<0 || w>numVertices) { return -1; } //超出范围,本人修改了判断式
if (v==w || Edge[v][w] == maxWeight) { return -1; } //本人添加的判断语句,Edge[v][w] == maxWeight用来判断w不是v的邻接点
for (int col = w + 1; col < numVertices; col++) //要找的邻接点在w之后
{
if (Edge[v][col] > 0 && Edge[v][col] < maxWeight) { return col; }
}
return -1;
}
//插入一个顶点操作,该操作只是在顶点序列表尾追加加一个顶点信息,但并末对新增顶点添加任何边或弧
template<class T, class E>
bool Graphmtx<T, E>::insertVertex(const T v)
{
if (numVertices == maxVertices) { return false; } //溢出,操作失败
VerticesList[numVertices++] = v; //先在尾部进行插入,之后numVertices再加一
return true;
}
/*删除一个顶点操作,矩阵中无法随意删除任意一行或一列的元素,只能删除最后一行或一列,思路:
1、将顶点表的最后一个的顶点替换原有的v顶点。2、对于无向图,要删除与v相关的对称边,有相图,既要删除v的入弧也要删除v的出弧。删除的同时,边或弧的数量减一。
3、用最后一列替代第v列。4、顶点个数减一。5、用最后一行代替第v行。*/
template<class T, class E>
bool Graphmtx<T, E>::removeVertex(int v)
{
if (v<0 || v>numVertices||numVertices==0) { return false; } //溢出或空图,书中为&&,错误
int i;
VerticesList[v] = VerticesList[numVertices - 1]; //用最后一个顶点代替第v个顶点
for (i = 0; i < numVertices; i++) //删除一条边或一条入弧,边或弧的数量减一
{
if (Edge[i][v] > 0 && Edge[i][v] < maxWeight)
{
--numEdges;
if (direction && Edge[v][i]>0 && Edge[v][i]<maxWeight) { numEdges--; } //修改了判断条件,加入了Edge[v][i]>0 && Edge[v][i]<maxWeight,用来判断出弧是否存在
}
}
for (i = 0; i < numVertices; i++) //用最后一列替换第v列
{
Edge[i][v] = Edge[i][numVertices - 1];
}
--numVertices;
for (i = 0; i < numVertices; i++) //用最后一行替换第v行
{
Edge[v][i] = Edge[numVertices - 1][i];
}
return true;
}
//增加一条边或弧的操作,首先要判断是有向图还是无向图,之后边数再加一
template<class T,class E>
bool Graphmtx<T, E>::insertEdge(int v1, int v2, E cost)
{
if (v1<0 || v1>numVertices || v2<0 || v2>numVertices) { cout << "v1 or v2 not int index area!" << endl; return false; }
if (Edge[v1][v2] != maxWeight) { cout << "this edge already existed!" << endl; return false; }
Edge[v1][v2] = cost;
if (!direction) { Edge[v2][v1]=cost; }
numEdges++;
return true;
}
//删除一条边或弧的操作
template<class T, class E>
bool Graphmtx<T, E>::removeEdge(int v1, int v2)
{
if (v1<0 || v1>numVertices || v2<0 || v2>numVertices) { cout << "v1 or v2 not int index area!" << endl; return false; }
if (Edge[v1][v2] == maxWeight) { cout << "this edge is not existed!" << endl; return false; }
Edge[v1][v2] = maxWeight;
if (!direction) { Edge[v2][v1] = maxWeight; }
numEdges--;
return true;
}
/*输入及输出操作, 通过重载输人流和输出流可以封装整个邻接矩阵的输入和输出,将输入流和输出流定义
为邻接矩降类的友元,重载的输入及输出操作即可访问Graphmtx类的私有成员。*/
template<class T, class E>
istream & operator>>(istream &in, Graphmtx<T, E> &G) //重载输入流,建立邻接矩阵表示的图
{
int i, j, k, m, n; T e1, e2; E weight;
cout << "input the number of vertex:" << endl; in >> n;
for (i = 0; i < n; i++) //在顶点序列中依次添加各个顶点的信息
{
cout << "input vertex" << i + 1 << ":"; in >> e1;
G.insertVertex(e1);
}
i = 1;
cout << "input the number of edge:" << endl; in >> m; //输入m条边 或弧
while (i <= m)
{
cout << "input two vertexs and weight:"; in >> e1 >> e2 >> weight;
j = G.getVertexPos(e1); k = G.getVertexPos(e2);
if (j == -1 || k == -1) { cout << "error! can't find e1 or e2!" << endl; } //未找到e1或e2的位置,返回-1
else
{
G.insertEdge(j, k,weight);
++i;
}
}
return in;
}
template<class T,class E>
ostream &operator<<(ostream &out, Graphmtx<T, E> & G) //重载输出流,输出用邻接矩阵表示的G
{
int i, j, n, m; T e1, e2; E w;
n = G.NumberOfVertices();
m = G.NumberOfEdges();
out << "The graph has " << n << "vertexs," << m << "edges."<< 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);
if (!G.direction) //输出边信息
{
out << "(" << e1 << "," << e2 << "," << w << ")" << endl;
}
else
{
out << "<" << e1 << "," << e2 << "," << w << ">" << endl; //输出弧
}
}
}
}
return out;
}
//prim(普里姆)最小生成树算法
typedef struct
{ //最小生成树辅助数组,存放最小代价
int vex; //存放边的一个顶点,vex在已产生的最小生成树顶点集合中
int lowcost; //边的代价
}Closedge[DefaultVertices];
Closedge closedge;
template<class T, class E>
void Graphmtx<T, E>::Prim(T vertex)
{
int u = getVertexPos(vertex);
if (u == -1) { cout << "error position!" << endl; return; }
int i, j, k = u;
for (j = 0; j < numVertices; j++) //用邻接矩阵初始化从顶点u出发的各个连通边
{
closedge[j].vex = u; //vex实际存储的是getVertexPos(vertex)返回的数组的序号,不是顶点名
closedge[j].lowcost = getWeight(k, j); //有值返回值,无值返回0
}
closedge[k].lowcost = 0; //自身顶点为除外
/*
cout << endl;
for (int r = 0; r < numVertices; r++)
{
cout << closedge[r].vex << " " << closedge[r].lowcost << endl;
}
*/
for (i = 0; i < numVertices-1; i++) //选择其余numVertices—1个顶点
{
k = minimum(); //取得代价的最小边
cout << "(" << VerticesList[closedge[k].vex] << "---" << VerticesList[k] << ")" << " "; //colsedge[k].vex实际存储的是u,也就是边的起始点,k也就是边的终点
closedge[k].lowcost = 0; //第k顶点并入U集
for (j = 0; j < numVertices; j++) //开始判断第k行的边
{
if (Edge[k][j] < closedge[j].lowcost) //若Edge的k行j列的值小于closede的第j列,替换closede[j]值
{
closedge[j].vex = k; //修改起始顶点
closedge[j].lowcost = Edge[k][j];
}
}
/*
cout <<"the"<<i+1<<"ceshi:"<< endl;
for (int r = 0; r < numVertices; r++)
{
cout << closedge[r].vex << " " << closedge[r].lowcost << endl;
}
*/
}
}
template<class T, class E>
int Graphmtx<T, E>::minimum() //求closedge中lowest的最小正值,返回在数组中的序号
{
int i = 0, j, k, min;
while (i < numVertices && !closedge[i].lowcost) { i++; }
min = closedge[i].lowcost; //第一个不为0的值
k = i;
for (j = i + 1; j < numVertices; j++)
{
if (closedge[j].lowcost > 0 && min > closedge[j].lowcost) //用k保存最小代价
{
min = closedge[j].lowcost;
k = j;
}
}
return k;
}
//Kruskal(克鲁斯卡尔)算法,书中讲解比较垃圾,此博客较为详细:https://blog.csdn.net/tianjindong0804/article/details/86573765
template<class T, class E>
void Graphmtx<T, E>::Kruskal()
{
int i, j, u, v, s1, s2, k = 0;
int vset[DefaultVertices];
EdgeSet EE[DefaultVertices];
//EE = new EdgeSet[numVertices];
for (i = 0; i <numVertices; i++) //初始化各边信息(两个顶点及权值)
{
for (int j = i + 1; j < numVertices; j++)
{
if (Edge[i][j] > 0 && Edge[i][j] <maxWeight) //这里操作应该是无向边
{
//cout << "Edge value:"<<k<<"," << Edge[i][j] << endl;
EE[k].vex1 = i;
EE[k].vex2 = j;
EE[k].weight = Edge[i][j];
k++;
}
}
}
InsertSort(EE); //将各边进行非降序排序
for (i = 0; i < numVertices; i++) { vset[i] = i; } //初始状态,各顶点自成连通分量
k = 1; j = 0;
while (k < numVertices) //将代价最小的numVertices一l条边落在不同连通分量上
{
u = EE[j].vex1; v = EE[j].vex2; s1 = vset[u]; s2 = vset[v];
if (s1 != s2) //若最小的边不在同一个连通分量上,则连接该边
{
cout << "(" << VerticesList[u] << "," << VerticesList[v] << ")" << EE[j].weight;
++k;
for (i = 0; i < numVertices; i++) //在已有的连通分量加入该边
{
if (vset[i] == s2) { vset[i] = s1; } //此处为书中代码,过于复杂,可以修改优化
}
}
j++;
}
}
//对边集数组进行排序
template<class T, class E>
void Graphmtx<T, E>::InsertSort(EdgeSet EE[]) //对所有边进行直接插入排序,shit!原书代码是错的,md,gdx!
{
int i, j;
EdgeSet temp; cout << endl;
for (i = 1; i < numEdges; i++)
{
temp = EE[i]; j = i - 1;
while (j >= 0 && EE[j+1].weight < EE[j].weight) //修改了原书代码的循环内容
{
EdgeSet temp2;
temp2 = EE[j + 1];
EE[j + 1] = EE[j];
EE[j] = temp2;
j--;
}
}
/*
//测试代码
for (int i = 0; i < numEdges; i++)
{
cout << "sorted test print:" << endl;
cout << EE[i].vex1 << "," << EE[i].vex2 << "," << EE[i].weight << endl;
}*/
}
//Dijkstra算法(迪杰斯特拉算法),最短路径(从一个顶点到其余各顶点的最短路径)
template<class T, class E>
void Graphmtx<T, E>::Dijikstra(const int v)
{
int S[DefaultVertices], dist[DefaultVertices], i, path[DefaultVertices], u;
for (int i = 0; i < numVertices; i++) //各顶点最短路径及值初始化
{
dist[i] = Edge[v][i];
S[i] = 0; //最短路径顶点集合初始化
if (i != v && dist[i] < maxWeight) { path[i] = v; } //最短路径初始化,直连到顶点v
else { path[i] = -1; } //不存在i直连顶点v的或者v自身的设置为-1
}
S[v] = 1; //v归并到最短路径集合
for (i = 0; i < numVertices-1; i++) //需要循环numVertices-1次,找到除v以外的各顶点的最短路径
{
int min = maxWeight;
for (int j = 0; j < numVertices; j++) //找出当前最短路径顶点
{
if (!S[j] && dist[j] < min) //!S[j]用来判断是否在S集合中,整体判断不在集合S中,并且与v连通的顶点
{
u = j; min = dist[j]; //u存储权值最小的顶点的下标
}
}
S[u] = 1; //u加入到集合S中
for (int w = 0; w < numVertices; w++) //经过顶点u更新当前路径
{
if (!S[w] && Edge[u][w] < maxWeight && dist[u] + Edge[u][w] < dist[w]) //判断w不在S集合中,w与上一个最短路径顶点连通,并且经过u顶点到w的路径权值和小于直连权值
{
dist[w] = dist[u] + Edge[u][w]; path[w] = u; //path记录下与顶点w连接的上一个最短路径顶点u
}
}
}
for (i = 0; i < numVertices; i++) //输出最短路径,逆向输出
{
if (i != v)
{
cout << i << "(" << VerticesList[i] << ":" << dist[i] << ")--->";
for (u = path[i]; u != -1; u = path[u]) //u=-1,说明经过上面的操作,对应的path值并没有被修改,也就是没有连通,否则就把与i连通的上一个顶点给u
{
cout << u << "(" <<VerticesList[u] << ")--->";
}
cout << endl;
}
}
}
//Floyd(弗洛伊德)算法,最短路径(每对顶点之间的最短路径)讲解的还可以:https://www.cnblogs.com/Halburt/p/10756572.html
template<class T, class E>
void Graphmtx<T, E>::Floyd() //Ployd算法求顶点v到其余各顶点的关键路径
{
int D[DefaultVertices][DefaultVertices], path[DefaultVertices][DefaultVertices];
int i, j, k;
for (i = 0; i < numVertices; i++)
{
for (j = 0; j < numVertices; j++) //初始化方阵和序列
{
D[i][j] = Edge[i][j]; path[i][j] = -1;
}
}
for (k = 0; k < numVertices; k++) //用穷举法更新方阵序列和路径
{
for (i = 0; i < numVertices; i++)
{
for (j = 0; j < numVertices; j++)
{
if (D[i][j] > D[i][k] + D[k][j])
{
D[i][j] = D[i][k] + D[k][j]; path[i][j] = k;
}
}
}
}
Dispath(D, path, numVertices);
}
//最短路径求解
template<class T, class E>
void Graphmtx<T, E>::Dispath(int D[][DefaultVertices], int path[][DefaultVertices], int n)
{
int i, j;
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
if (D[i][j] == maxWeight) //说明经过穷举法更新过后,无论直接还是间接都没有路径连接i顶点和j顶点
{
if (i != j)
{
cout << "从顶点" << VerticesList[i] << "到顶点" << VerticesList[j] << "没有路径" << endl;
}
}
else
{
if (i != j)
{
cout << "从顶点" << VerticesList[i] << "到顶点" << VerticesList[j] << "的最短路径长度为:" << "("
<< VerticesList[i] << "--->";
Ppath(path, i, j); //递归算法求解源点到终点之间的所有中间顶点
cout << VerticesList[j] << ")" << endl;
}
}
}
}
}
/*个人理解:也就是说初始时刻i到就j中间只能插一个顶点k,其中(i,k),(k,j)直连,但是直连权值不一定是最小的,所以分成两段,第一段查找(i,k)中间有没有顶点间接相连使得
(i,k)连接权值最小,另一段查找(k,j)。也就是将一大段分为两段,然后在每段中如果有中间点则再分为两小段,依次循环查找直到找无可找*/
template<class T, class E>
void Graphmtx<T, E>::Ppath(int path[][DefaultVertices], int i, int j)
{
int k = path[i][j];
if (k == -1) { return; }
Ppath(path, i, k);
cout << VerticesList[k] << "--->";
Ppath(path, k, j);
}