数据结构中图的邻接矩阵构建及其操作(C++语言)

数据结构中图的邻接矩阵构建及其操作(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);
}


  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值