数据结构与算法(c++)--拓扑排序

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_29883591/article/details/70188266

        这次来说一下拓扑排序的东西,仍是基于自己看的资料进行整理的(《数据结构与算法分析c++描述》这本书真的好,强烈推荐)。

        拓扑排序是对有向无环图的顶点的一种排序,它使得如果存在一条从Vi到Vj的路径,那么在排序的时候Vj将会出现在Vi的后面。举个例子说,对于有向边(Vi,Vj)而言,在排序的时候,无论如何进行排序选择,最终Vi必定出现在Vj的前面,所以说,拓扑排序的图必须是无环图,试想一下,如果存在一个环的话,那么久必定会出现两个顶点v和w,v先于w的同时w又先于v,这肯定是不可能的。

        下面举一个例子,如下图所示:


        对于上图所示的的有向无环图中,v1,v2,v5,v4,v3,v7,v6和v1,v2,v5,v4,v7,v3,v6都是拓扑排序。由此可以看出,排序不必是唯一的,任何合理的排序都是可以的。

一个简单的求拓扑排序的算法是先找到任意一个没有入边(没有入边的意思是这个顶点没有其他的顶点指向它,即没有指向它的边存在)的顶点,然后显示出该顶点,并将它和它的边一起从图中删除。然后,对图中的其余任何部分应用同样的方法处理。

书上提供了一个简单的伪代码,如下:

void Graph::topsort()
{
	for(int counter=0;counter<NUM_VERTICES;counter++)
	{
		Vertex v=findNewVertexOfIndegreeZero();
		if(v==NOT_A_VERTEX)
		{
			throw CycleFoundException();
		}
		v.topNum=counter;
		for each vertex w adjacent to v
			w.indegree--;
	}
}

下面给出一个处理的代码:

#include<iostream>
using namespace std;
#include<vector>
#include<string>
#include<list>
#include<queue>
#include<climits>
#include<algorithm>

template <typename vertexNametype,typename weight>
class ALGraph
{
private:
	template <typename weight>
	struct Edge
	{
		int nDestVertex;   //邻接顶点编号
		weight edgeWeight;  //边权重
		Edge *pNextEdge;   //下一条边
		Edge(int d,weight w,Edge<weight> *p=NULL):nDestVertex(d),edgeWeight(w),pNextEdge(p){}
	};
	template <typename vertexNametype,typename weight>
	struct Vertex
	{
		vertexNametype vertexName;   //顶点名
		Edge<weight> *pAdjEdges;   //邻接边链表
		Vertex( const vertexNametype &name=vertexNametype(),Edge<weight> *p=NULL):vertexName(name),pAdjEdges(p){}
	};
public:
	explicit ALGraph():m_vertexArray(NULL){}
	~ALGraph()
	{
		for(auto it=m_vertexArray.begin();it!=m_vertexArray.end();++it)
		{
			Edge<weight> *p=it->pAdjEdges;
			while(NULL!=p)
			{
				it->pAdjEdges=p->pNextEdge;
				delete p;
				p=it->pAdjEdges;
			}
		}
		if(!m_vertexArray.empty())
			m_vertexArray.clear();
	}
	bool insertAVertex(const vertexNametype &vertexName)  //插入节点
	{
		int index=getVertexIndex(vertexName);
		if(-1!=index)
		{
			cerr << "点" << vertexName << "已经存在" << endl;
			return false;
		}
		Vertex<vertexNametype,weight> vertexInstance(vertexName);
		m_vertexArray.push_back(vertexInstance);
		return true;
	}
	bool insertAEdge(const vertexNametype &vertexName1,const vertexNametype &vertexName2,const weight &edgeWeight=1)  //插入边
	{
		int index1=getVertexIndex(vertexName1);
		if(-1==index1)
		{
			cerr << "不存在点" << vertexName1 << endl;
			return false;
		}
		int index2=getVertexIndex(vertexName2);
		if(-1==index2)
		{
			cerr << "不存在点" << vertexName2 << endl;
			return false;
		}
		Edge<weight> *p=m_vertexArray[index1].pAdjEdges;
		while(p!=NULL&&p->nDestVertex!=index2)
		{
			p=p->pNextEdge;
		}
		if(NULL==p)
		{
			p=new Edge<weight>(index2,edgeWeight,m_vertexArray[index1].pAdjEdges); 
			m_vertexArray[index1].pAdjEdges=p;     //将p插入到链表开始处
			return true; 
		}
		if(p->nDestVertex==index2)
		{
			Edge<weight> *q=p;
			p=new Edge<weight>(index2,edgeWeight,q->pNextEdge);
			q->pNextEdge=p;
			return true;
		}
		return false;
	}
	bool edgeExist(const vertexNametype &vertexName1,const vertexNametype &vertexName2) const  //判断便是否存在
	{
		int index1=getVertexIndex(vertexName1);
		if(-1==index1)
		{
			cerr << "不存在点" << vertexName1 << endl;
			return false;
		}
		int index2=getVertexIndex(vertexName2);
		if(-1==index2)
		{
			cerr << "不存在点" << vertexName2 << endl;
			return false;
		}
		Edge<weight> *p=m_vertexArray[index1].pAdjEdges;
		while(p!=NULL&&p->nDestVertex!=index2)
		{
			p=p->pNextEdge;
		}
		if(NULL=p)
		{
			cerr << "不存在" << endl;
			return false;
		}
		if(p->nDestVertex==index2)
		{
			cout << "存在" << endl;
			cout << vertexName1 << ":" ;
			while(p!=NULL&&p->nDestVertex==index2)
			{
				cout << "(" << vertexName1 << "," << vertexName2 << "," << p->edgeWeight << ")" ;
				p=p->pNextEdge;
			}
			cout << endl;
			return true;
		}
	}
	void printVertexAdjEdges(const vertexNametype &vertexName) const     //输出邻接表
	{
		int index=getVertexIndex(vertexName);
		if(-1==index)
		{
			cerr << "不存在点" << vertexName << endl;
			return ;
		}
		Edge<weight> *p=m_vertexArray[index].pAdjEdges;
		cout << vertexName << ":" ;
		while(p!=NULL)
		{
			cout << "(" << vertexName << "," << getData(p->pNextEdge) << p->edgeWeight << ")" ;
		}
		cout << endl;
	}
	bool removeAEdge(const vertexNametype &vertexName1,const vertexNametype &vertexName2,const weight &edgeWeight)     //删除边
	{
		int index1=getVertexIndex(vertexName1);
		if(-1==index1)
		{
			cerr << "不存在点" << vertexName1 << endl;
			return false;
		}
		int index2=getVertexIndex(vertexName2);
		if(-1==index2)
		{
			cerr << "不存在点" << vertexName2 << endl;
			return false;
		}
		Edge<weight> *p=m_vertexArray[index1].pAdjEdges;
		Edge<weight> *q=NULL;
		while(p!=NULL&&p->nDestVertex!=index2)
		{
			q=p;     //用q记下将要删除的边的前面的边
			p=p->pNextEdge;
		}
		if(NULL==p)
		{
			cerr << "不存在点" << vertexName1 << "到" << vertexName2 << "的点" << endl;
			return false;
		}
		while(p!=NULL&&edgeWeight!=p->edgeWeight&&p->nDestVertex==index2)
		{
			q=p;
			p=p->pNextEdge;
		}
		if(p->nDestVertex!=index2)
		{
			cerr << "不存在点" << vertexName1 << "到" << vertexName2 << "的点" << endl;
			return false;
		}
		if(NULL==q)
			m_vertexArray[index1].pAdjEdges=p->pNextEdge;
		else
		    q->pNextEdge=p->pNextEdge;
		delete p;
		return true;
	}
	int getVertexIndex(const vertexNametype &vertexName) const  //获取顶点索引
	{
		for(int i=0;i<m_vertexArray.size();i++)
		{
			if(vertexName==getData(i))
				return i;
		}
		return -1;

	}
	int getVertexNumber() const  //获取顶点数
	{
		return m_vertexArray.size();
	}

	friend ostream &operator<<(ostream &out,const ALGraph<vertexNametype,weight> &graphInstance)
	{
		int vertexNum=graphInstance.getVertexNumber();
		out << "这个图有" << vertexNum << "个点" << endl;
		for(int i=0;i<vertexNum;i++)
		{
			vertexNametype vertexName=graphInstance.getData(i);
			out << vertexName << ":" ;
			Edge<weight> *p=graphInstance.m_vertexArray[i].pAdjEdges;
			while(NULL!=p)
			{
			    out << "(" << vertexName << "," << graphInstance.getData(p->nDestVertex) << "," << p->edgeWeight << ")";
				p=p->pNextEdge;
			}
			out << endl;
		}
		return out;
	}

	 list<vertexNametype> topologicialSort() const
	 {
		 list<vertexNametype> vertexList;
		 vector<int> indegree(m_vertexArray.size(),0);
		 queue<Vertex<vertexNametype,weight> > zeroIndegree;
		 for(int i=0;i<m_vertexArray.size();i++)
		 {
			 Edge<weight> *p=m_vertexArray[i].pAdjEdges;
			 while(NULL!=p)
			 {
				 ++indegree[p->nDestVertex];
				 p=p->pNextEdge;
			 }
		 }
		 for(int i=0;i<m_vertexArray.size();i++)
		 {
			 if(0==indegree[i])
				 zeroIndegree.push(m_vertexArray[i]);
		 }
		 while(!zeroIndegree.empty())
		 {
			   Vertex<vertexNametype,weight> v=zeroIndegree.front();
			   zeroIndegree.pop();
			   vertexList.push_back(v.vertexName);
			   Edge<weight> *p=v.pAdjEdges;
			   while(NULL!=p)
			   {
				   if(--indegree[p->nDestVertex]==0)
					   zeroIndegree.push(m_vertexArray[p->nDestVertex]);
				   p=p->pNextEdge;
			   }
		 }
		 if(vertexList.size()<m_vertexArray.size())
		 {
			 cerr << "此图有环,无法进行拓扑排序" << endl;
			 exit(EXIT_FAILURE);
		 }
		 return vertexList;
	 }

	 void printPath(const vertexNametype &beginVertex,const vertexNametype &endVertex,const vector<int> prev)
	 {
		 int beginIndex=getVertexIndex(beginVertex);
		 int endIndex=getVertexIndex(endVertex);
		 printPath(beginIndex,endIndex,prev);
	 }


private:
	vector<Vertex<vertexNametype,weight> > m_vertexArray;
	vertexNametype getData(int index) const  //取顶获点名
	{
		return m_vertexArray[index].vertexName;
	}
	void printPath(int beginIndex,int endIndex,const vector<int> prev)
	{
		 if(beginIndex!=endIndex)
			 printPath(beginIndex,prev[endIndex],prev);
		 cout << m_vertexArray[endIndex].vertexName << " " ;
	}
	Edge<weight> *getEdge(int begin,int end)
	{
		Edge<weight> *p=m_vertexArray[begin].pAdjEdges;
		while(NULL!=p)
		{
			if(p->nDestVertex==end)
				break;
			p=p->pNextEdge;
		}
		return p;
	}
};

int main()
{
	ALGraph<string,int> graph;
	graph.insertAVertex("v1");
	graph.insertAVertex("v2");
	graph.insertAVertex("v3");
	graph.insertAVertex("v4");
	graph.insertAVertex("v5");
	graph.insertAVertex("v6");
	graph.insertAVertex("v7");
	graph.insertAEdge("v1","v2");
	graph.insertAEdge("v1","v4");
	graph.insertAEdge("v1","v3");
	graph.insertAEdge("v2","v4");
	graph.insertAEdge("v2","v5");
	graph.insertAEdge("v3","v6");
	graph.insertAEdge("v4","v7");
	graph.insertAEdge("v4","v6");
	graph.insertAEdge("v4","v3");
	graph.insertAEdge("v5","v7");
	graph.insertAEdge("v7","v6");
	cout << graph << endl;
	list<string> result=graph.topologicialSort();
	cout << "拓扑排序的结果为:"<< endl;
	for(auto it=result.begin();it!=result.end();++it)
		cout << *it << " " ;
	cout << endl;
	return 0;
}

在类 ALGraph中,可以看出,在开头部分我是定义了两个私有的struct的,一个是Edge,代表的是图中的边;一个是Vertex,代表的是图中的点。对于边Edge而言,从定义出的注释可以看出,每个边有顶点,这个顶点自然指的是起始的点,然后便是这条边的权重,对于pNextEdge,这里说一下,这个存储的是下一条边的指针,举个例子来说,对于v1来说,比如说点存放的顺序为v2,v3,v4,那么在读取v1的邻接的边的时候,那么首先督导的Edge中nDestVertex对应的将是v2的编号,而pNextEdge对应的将是边(v1,v3)的那个指针信息,依次类推,这样做是为了我们在读取一个点的入度的时候方便。对于Verte而言,我们存放了顶点的名字和邻接边的 一个指针,这个链表中是此顶点指向的所有边的一个集合。到这里我们会发现在Vertex和Edge中都会有一个Edge的指针,看上去感觉会有点乱啊,其实这个Edge完全是服务于Vertex的,并且由于加入了 指针会让人看上去有点发慌,这个就需要自己慢慢体会了。

        对于上面的代码可能看起来会有点多啊,其实我们在平时解题的时候,很多情况下不会用到这么复杂的,原因之一就是类的运用啊,因为一旦使用了类,不可避免的就会产生很多的代码,尤其是在进行各种方法之间的优化的时候。在这个程序中,我使用了邻接表的方法对图进行处理的,在很多情况下也可以使用矩阵的方法进行处理,这两种方法各有优劣,分别对应于稀疏和稠密类型的图很有效果,这里因为是基于c++的嘛,就给出了一个运用类进行构造的例子,并且采用邻接表进行,相对来说矩阵方法更加简单易写,这里就不给出了。

        至于代码量而言,其实如果仅仅是写这一个算法的话,确实是有点小题大做了,毕竟就一个那么简答的算法实现,没有必要搞个300多行出来,太吓人了吧,其实这是为了其它算法铺路的,在后续的博客中会看到,这些都是通用的东西,所以这个类模型可以把很多种算法整合到一起,而共用很多的代码,后面会继续看到。

        好了,这次就这么多了。

猜你喜欢

转载自blog.csdn.net/qq_29883591/article/details/70188266