六、【数据结构】图-邻接矩阵(adjacency matrix)的实现

本文旨采用C++实现图数据结构,具体地以邻接矩阵的方式实现图数据结构。图结构可以描述一组对象之间的二元关系,例如在城市交通中,联接于各个公交车站之间的道侣,或者在互联网中个网络节点之间的路由都可以很方便地用图结构来表述,另外在之前介绍的树结构也属于图的一种,此类一般性的二元关系,属于图论

在将一些对象之间的关系用图结构表示后,那么我们就可以使用图算法方便地解决这些对象的某些问题,比如拓扑排序,最小支撑树,最短路径问题等。事实上,对图结构的处理策略,也可以通过遍历将图转换为半线性结构,进而借助树结构已有的处理方法和机巧解决问题

图有些基本术语,如有向图,无向图,混合图,邻接,关联,简单图,度,环路,带权网络等,可以参考课本。

在这里,图数据结构的实现通过graph.h类和graphMatrix.h两个类实现,其中graph.h类主要是描述图结构的基本属性,比如顶点总数,边总数,并提供了一些对顶点和边的操作接口和一些图算法操作接口,这些接口以纯虚函数的形式声明,因为图结构的具体描述有很多种,如邻接矩阵和邻接表等,这些虚函数需要在图结构的具体实现时进行覆盖。本文采用邻接矩阵这一最基本的实现方式,邻接矩阵在graphMatrix.h中进行实现,其中对graph.h中的虚函数进行了覆盖。

graph接口列表
操作功能对象
reset()复位所有顶点,边的辅助信息
BFS(int v, int& clock)(单个连通域)广度优先算法
DFS(int v, int& clock)(单个连通域)深度优先算法
BCC(int, int&, stack<int>&)(单个连通域)基于DFS的双连通分量分解算法
TSort(int v, int& clock, stack<Tv>* S)(单个连通域)基于DFS的拓扑排序算法
PFS(int s, PU prioUpdater)(单个连通域)优先级搜索框架
insert(const Tv&)插入顶点,返回编号
remove(int)删除顶点及其关联边,返回该顶点信息
vdata(int)返回顶点v的数据
inDegree(int)返回顶点v的入度
outDegree(int)返回顶点v的出度
firstNbr(int)返回顶点v的首个邻接顶点
nextNbr(int, int)返回顶点v(相对于顶点j的)下一邻接顶点
status(int)返回顶点v的状态
dTime(int)返回顶点v的时间标签dTime
fTime(int)返回顶点v的时间标签fTime
parent(int)返回顶点v在遍历树中的父亲
priority(int)顶点v在遍历树中的优先级数
exists(int, int)边(v,u)是否存在
insert(Te const&, int, int, int)在顶点v和u之间插入权重为w的边e
remove(int, int)删除顶点v和u之间的边,并返回该边的信息
type(int, int)边(v,u)的数据
weight(int, int)返回边(u,v)的权重
bfs(int s)广度优先搜索算法
dfs(int s)深度优先搜索算法
bcc(int)基于DFS的双连通分量分解算法
tSort(int s)基于DFS的拓扑排序算法
prim(int s)最小支撑树Prim算法
dijkstra(int s)最短路径Dijkstra算法
pfs(int s, PU prioUpdater)优先级搜索框架
graphMatrix接口列表
操作功能对象
graphMatrix()默认构造函数
~graphMatrix析构函数,释放所有边所占的内存空间
vdata(int i)返回数据
inDegree(int i)返回入度
outDegree(int i)返回出度
firstNbr(int i)返回首个邻接顶点
nextNbr(int i, int j)返回相对于顶点j的下一邻接顶点
status(int i)返回状态
dTime(int i)返回时间标签dTime
fTime(int i)返回时间标签fTime
parent(int i)返回在遍历树中的父亲
priority(int i)返回在遍历树中的优先级数
insert(const Tv& v)插入顶点,返回标号
remove(int i)删除第i个顶点及其关连边
exists(int i, int j)判断从顶点i到j的边是否存在
type(int i, int j)返回边(i,j)的类型
edata(int i, int j)返回边(i,j)的数据
weight(int i, int j)返回边(i,j)的权重
insert(const Te& ed, int w, int i, int j)插入权重为w的边e=(i,j)
remove(int i, int j)删除顶点i到顶点j的边e=(i,j)

(1) graph.h

#pragma once
#include"stack.h"
#include"queue.h"

typedef enum { UNDISCOVERED, DISCOVERED, VISITED } VStatus;         //顶点状态(未发现,发现,已遍历)
typedef enum { UNDETERMINED, TREE, CROSS, FORWARD, BACKWARD }EType; //边在遍历树中的类型(未确定,树,跨边,向前,向后)


//graph模板类
template<typename Tv, typename Te> class graph
{
public:
	void reset();  //复位所有顶点,边的辅助信息
	void BFS(int v, int& clock);  //(单个连通域)广度优先算法
	void DFS(int v, int& clock);  //(单个连通域)深度优先算法
	void BCC(int, int&, stack<int>&);//(单个连通域)基于DFS的双连通分量分解算法
	bool TSort(int v, int& clock, stack<Tv>* S);//(单个连通域)基于DFS的拓扑排序算法
	template<typename PU> void PFS(int s, PU prioUpdater);//(单个连通域)优先级搜索框架

public:
	//顶点相关
	int n;  //顶点总数
	virtual int insert(const Tv&) = 0;//插入顶点,返回编号
	virtual Tv remove(int) = 0;       //删除顶点及其关联边,返回该顶点信息
	virtual Tv& vdata(int) = 0;      //返回顶点v的数据
	virtual int inDegree(int) = 0;    //返回顶点v的入度
	virtual int outDegree(int) = 0;   //返回顶点v的出度
	virtual int firstNbr(int) = 0;    //返回顶点v的首个邻接顶点
	virtual int nextNbr(int, int) = 0;    // 返回顶点v(相对于顶点j的)下一邻接顶点
	virtual VStatus& status(int) = 0; //返回顶点v的状态
	virtual int& dTime(int) = 0;      //返回顶点v的时间标签dTime
	virtual int& fTime(int) = 0;      //返回顶点v的时间标签fTime
	virtual int& parent(int) = 0;     //返回顶点v在遍历树中的父亲
	virtual int& priority(int) = 0;   //顶点v在遍历树中的优先级数

	//边相关(无向边也转换成有向边)
	int e;  //边总数
	virtual bool exists(int, int) = 0;  //边(v,u)是否存在
	virtual void insert(Te const&, int, int, int) = 0;   //在顶点v和u之间插入权重为w的边e
	virtual Te remove(int, int) = 0;   //删除顶点v和u之间的边,并返回该边的信息
	virtual EType& type(int, int) = 0;  //边(v,u)的数据
	virtual int& weight(int, int) = 0;  //返回边(u,v)的权重
	
	//算法
	void bfs(int s);   //(可处理多个独立连通域)广度优先搜索算法
	void dfs(int s);   //深度优先搜索算法
	void bcc(int);   //基于DFS的双连通分量分解算法
	stack<Tv>* tSort(int s);   //基于DFS的拓扑排序算法
	void prim(int s);  //最小支撑树Prim算法
	void dijkstra(int s);   //最短路径Dijkstra算法
	template<typename PU> void pfs(int s, PU prioUpdater);   //优先级搜索框架
};

template<typename V, typename E> struct PrimPU
{
	void operator()(graph<V, E>* g, int uk, int v)
	{
		if (g->status(v) == UNDISCOVERED)   //若顶点v尚未被发现
			if ((g->weight(uk, v)) < (g->priority(v)))  //比较当前边的权重和之前遍历时设置的优先级数
			{
				g->priority(v) = g->weight(uk, v);   //根据权重设置优先级数
				g->parent(v) = uk;   //更新父节点
				cout << "寻找顶点(uk,v)" << "(" << uk << "," << v << ")" <<"----w---"<<g->weight(uk,v)<< endl;
			}
	}
};

template<typename V, typename E> struct DijkstraPU
{
	void operator()(graph<V, E>* g, int uk, int v)
	{
		if (g->status(v) == UNDISCOVERED)   //如果发现顶点v尚未被发现
		{
			if ((g->weight(uk, v) + g->priority(uk)) < (g->priority(v)))
			{
				g->priority(v) = g->weight(uk, v) + g->priority(uk);   //更新优先级数
				g->parent(v) = uk;
			}
		}
	}
};

template<typename Tv, typename Te> void graph<Tv, Te>::reset()
{
	for (int i = 0; i < n; i++)
	{
		status(i) = UNDISCOVERED;   //状态
		dTime(i) = fTime(i) = -1;   //时间标签
		parent(i) = -1;
		priority(i) = INT_MAX;
		for (int j = 0; j < n; j++)
		{
			if (exists(i, j))
				type(i, j) = UNDETERMINED;  //复位存在的边的类型

		}
	}
}

template<typename Tv, typename Te> void graph<Tv, Te>::BFS(int v, int& clock)   //遍历单个
{
	queue<int> Q;  //顶点缓存队列
	status(v) = DISCOVERED; //标记顶点为已发现
	Q.enqueue(v);  //将当前顶点入队
	
	while (!Q.empty())   //只要队列非空,则继续
	{
		v = Q.dequeue();  //每次选择一个顶点出队,遍历其所有邻居,检查是否存在关联边
		dTime(v) = ++clock;
		for (int u = firstNbr(v); u >= 0; u = nextNbr(v, u))   //对于顶点v,从顶点集V的最后一个元素开始寻找邻居(与v存在关联边)
		{

			if (status(u) == UNDISCOVERED)  //如果此邻居顶点尚未发现
			{			cout << "(v,u)" << "(" << v<<"," << u << ")" << endl;
				status(u) = DISCOVERED;  //设置该顶点为已被发现
				type(v, u) = TREE;   //设置边e(v,u)为TREE(遍历树)
				parent(u) = v;       //设置在遍历树中顶点u的父亲为v
				Q.enqueue(u);        //顶点u入队
			}
			else  //如果此邻居顶点已经被发现
			{
				type(v, u) = CROSS;   //设置边e(v,u)为CROSS(跨边),不是遍历树枝干
			}
		}
		status(v) = VISITED;   //设置顶点v为已遍历
	}
}

template<typename Tv, typename Te> void graph<Tv, Te>::bfs(int s)
{
	reset();   //复位所有顶点和已存在边的状态为未被发现,未确定
	int clock = 0;  //时间标签
	int v = s;
	do
	{
		if(status(v)==UNDISCOVERED)
			BFS(v,clock);   //对每个顶点都进行一次单连通域广度优先搜索
		v++;
		cout << "v----" << v << endl;
	} while ((v = (++v%n)) != s);
}

template<typename Tv, typename Te> void graph<Tv, Te>::DFS(int v, int& clock)
{
	status(v) = DISCOVERED;    //标记当前节点为发现
	dTime(v) = ++clock;

	for (int u = firstNbr(v); u > -1; u = nextNbr(v, u))  //遍历所有邻居顶点
	{
		switch (status(u))
		{
		case UNDISCOVERED:   //尚未发现的顶点,继续深入遍历
			status(u) = DISCOVERED;  //标记为已发现
			type(v, u) = TREE;
			parent(u) = v;
			DFS(u, clock);
			break;
		case DISCOVERED:     //已被发现但是尚未遍历完成的顶点,那就是祖先啊
			type(v, u) = BACKWARD;
			break;
		default:   //VISITED  已经遍历完成,根据dTime判断是FORWARD还是CROSS
			type(v, u) = (dTime(v) < dTime(u)) ? FORWARD : CROSS;
			break;
		}
	}
	status(v) = VISITED;
	fTime(v) = ++clock;
}

template<typename Tv, typename Te> void graph<Tv, Te>::dfs(int s)
{
	reset();   //复位所有顶点和已存在边的状态为未被发现,未确定
	int clock = 0;  //时间标签
	int v = s;
	do
	{
		if (status(v) == UNDISCOVERED)
			DFS(v, clock);   //对每个顶点都进行一次单连通域深度优先搜索
		cout << "v----" << v << endl;
	} while ((v=(++v%n)) != s);
}


template<typename Tv, typename Te> bool graph<Tv, Te>::TSort(int v, int& clock, stack<Tv>* S)   //(基于DFS)单个连通域的拓扑排序,从连通域的任一顶点开始即可,因为会有外层tSort函数排查尚未发现的顶点
{
	status(v) = DISCOVERED;       
	dTime(v) = ++clock;
	for (int u = firstNbr(v); u > -1; u = nextNbr(v, u))
	{
		switch (status(u))
		{
		case UNDISCOVERED:   //若此顶点尚未被发现
			status(u) = DISCOVERED;  //标记为已被发现
			type(v, u) = TREE;     //标记边e(v,u)为遍历树的枝干
			parent(u) = v;
			if (!TSort(u, clock, S))  //继续深入遍历
				return false;
			break;
		case DISCOVERED:    //若此顶点已被发现但尚未完成遍历,则为回环
			type(v, u) = BACKWARD;
			return false;  //发现此连通域为有环图,不能生成拓扑序列
		default:   //VISITED  发现已经遍历完毕的顶点
			type(v, u) = (dTime(v) < dTime(u)) ? FORWARD : CROSS;  //根据顶点最开始遍历的时间标签dTime判断是前向边还是两个分支的跨边
			break;
		}
	}  
	//此顶点已经完成遍历
	status(v) = VISITED;
	S->push(vdata(v));
	return true;
}

template<typename Tv, typename Te> stack<Tv>* graph<Tv, Te>::tSort(int s)
{
	reset();
	stack<Tv>* S=new stack<Tv>;
	int v = s;
	int clock = 0;
	do
	{
		if (status(v) == UNDISCOVERED)
		{
			if (!TSort(v, clock, S))   //如果发现是有环图不能生成拓扑序列,则返回
			{
				while (!(S->empty()))
				{
					S->pop(); 
				}
				break;
			}
		}
	} while ((v=(++v)%n)!=s);
	return S;
}

template<typename Tv, typename Te> template<typename PU> void graph<Tv, Te>::PFS(int s, PU prioUpdater)   //(单个连通域)优先级搜索
{
	priority(s) = 0;      //设置s的优先级最高
	status(s) = VISITED;  //标记顶点s为已经便利的
	cout << "选中的顶点:" << vdata(s) << endl;
	parent(s) = -1;       //这句可以不要,因为reset()已经置-1
	while (true)
	{
		for (int w = firstNbr(s); w > -1; w = nextNbr(s, w))   //遍历顶点s的所有邻居
		{
			prioUpdater(this, s, w);  //更新顶点w的优先级和父顶点
		}
		for (int shortest = INT_MAX, w = 0; w < n; w++)
		{
			if (status(w) == UNDISCOVERED)   //如果顶点w尚未被遍历
				if (priority(w) < shortest)
				{
					shortest = priority(w);
					s = w;       //更新级数最小的点
				}
		}
		if (status(s) == VISITED) break;   //如果所有顶点均已经访问,则结束
		status(s) = VISITED; 
		cout << "选中的顶点:"<<vdata(s) << endl;
		cout << parent(s) << endl;
		type(parent(s), s) = TREE;
	}
}

template<typename Tv, typename Te> template<typename PU> void graph<Tv, Te>::pfs(int s, PU prioUpdater)
{
	reset();
	int v = s;
	do
	{
		if (status(v) == UNDISCOVERED)
			PFS(v, prioUpdater);
	} while ((v = (++v%n)) != s);
}



template<typename Tv, typename Te> void graph<Tv, Te>::prim(int s)
{
	PrimPU<Tv, Te> prioUpdater;
	pfs(s, prioUpdater);
}

template<typename Tv, typename Te> void graph<Tv,Te>::dijkstra(int s)
{
	DijkstraPU<Tv, Te> prioUpdater;
	pfs(s, prioUpdater);
}

(2) graphMatrix.h

#pragma once
#include"graph.h"
#include"vector.h"
template<typename Tv> struct vertex  //顶点类
{
	Tv data;
	int inDegree, outDegree;  //入度,出度
	VStatus status;           //状态
	int dTime, fTime;         //时间标签
	int parent;               //在遍历树中的父节点
	int priority;             //在遍历树中的优先级数

	//构造函数
	vertex(const Tv& d = Tv(0)) :data(d), inDegree(0), outDegree(0), status(UNDISCOVERED), dTime(-1), fTime(-1), parent(-1), priority(INT_MAX) {}
};

template<typename Te> struct edge
{
	Te data;
	int weight;   //权重
	EType type;   //类型

	//构造函数
	edge(const Te& d, int w) :data(d), weight(w), type(UNDETERMINED) {}
};

template<typename Tv, typename Te> class graphMatrix :public graph<Tv,Te>
{
public:
	vector<vertex<Tv>> V;  //顶点集
	vector<vector<edge<Te>*>> E;//边集

public:
	//构造函数
	graphMatrix() { n = e = 0; }
	//析构函数
	~graphMatrix()
	{
		for (int i = 0; i < n; i++)
			for (int j = 0; j < n; j++)
				delete E[i][j]; //清除所有边
	}
	//顶点的基本操作
	virtual Tv& vdata(int i) { return V[i].data; }    //返回数据
	virtual int inDegree(int i) { return V[i].inDegree; }  //返回入度
	virtual int outDegree(int i) { return V[i].outDegree; }  //返回出度
	virtual int firstNbr(int i) { return nextNbr(i, n); }  //首个邻接顶点
	virtual int nextNbr(int i, int j)  //相对于顶点j的下一邻接顶点(邻接表更好)
	{
		while ((j > -1) && (!exists(i, --j))); return j;
	}
	virtual VStatus& status(int i) { return V[i].status; }  //返回状态
	virtual int& dTime(int i) { return V[i].dTime; }   //返回时间标签dTime
	virtual int& fTime(int i) { return V[i].fTime; }   //返回时间标签fTime
	virtual int& parent(int i) { return V[i].parent; } //返回在遍历树中的父亲
	virtual int& priority(int i) { return V[i].priority; } //在遍历树中的优先级数

	//顶点的动态操作
	virtual int insert(const Tv& v);   //插入顶点,返回标号
	virtual Tv remove(int i);    //删除第i个顶点及其关连边

	//边的确认操作
	virtual bool exists(int i, int j);   //判断从顶点i到j的边是否存在

	//边的基本操作
	virtual EType& type(int i, int j) {return E[i][j]->type; }   //返回边(i,j)的类型
	virtual Te& edata(int i, int j) { return E[i][j]->data; }     //返回边(i,j)的数据
	virtual int& weight(int i, int j) { return E[i][j]->weight; } //返回边(i,j)的权重

	//边的动态操作
	virtual void insert(const Te& ed, int w, int i, int j);  //插入权重为w的边e=(i,j)
	virtual Te remove(int i, int j);   //删除顶点i到顶点j的边e=(i,j)
};

template<typename Tv,typename Te> int graphMatrix<Tv, Te>::insert(const Tv& v)
{
	for (int j = 0; j < n; j++)   //为每个顶点预留一个潜在的关联边
		E[j].insert(NULL);
	n++;
	E.insert(vector<edge<Te>*>(n, n, (edge<Te>*)NULL));   //创建新顶点对应的边向量
	return V.insert(vertex<Tv>(v));  //顶点向量增加一个顶点
	return 0;
}

template<typename Tv, typename Te> Tv graphMatrix<Tv,Te>::remove(int i)
{
	//清除所有出边
	for (int j = 0; j < n; j++)
	{
		if (exists(i, j))
		{
			delete E[i][j];
			V[j].inDegree--;
		}
	}
	E.remove(i);   //删掉i顶点对应的关连边这一行
	Tv temp = vdata(i);   //缓存数据
	V.remove(i);   //删除顶点
	for (int j = 0; j < n; j++)
	{
		if (edge<Te>* e = E[j].remove(i))
		{
			delete e;
			V[j].outDegree--;
		}
	}
	return temp;
}

template<typename Tv, typename Te> bool graphMatrix<Tv, Te>::exists(int i, int j)
{
	return (0 <= i) && (i < n) && (0 <= j) && (j < n) && (E[i][j] != NULL);
}


template<typename Tv, typename Te>  void graphMatrix<Tv, Te>::insert(const Te& ed, int w, int i, int j)
{
	if (exists(i, j)) return;
	E[i][j] = new edge<Te>(ed, w); //创建新边
	e++;
	V[i].outDegree++;
	V[j].inDegree++;
}

template<typename Tv, typename Te> Te graphMatrix<Tv, Te>::remove(int i, int j)
{
	Te temp = edata(i, j);
	delete E[i][j];
	E[i][j] = NULL;
	e--;
	V[i].outDegree--;
	V[j].inDegree--;
	return temp;
}

 

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值