zoukankan      html  css  js  c++  java
  • 数据结构与算法(c++)--拓扑排序

            这次来说一下拓扑排序的东西,仍是基于自己看的资料进行整理的(《数据结构与算法分析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多行出来,太吓人了吧,其实这是为了其它算法铺路的,在后续的博客中会看到,这些都是通用的东西,所以这个类模型可以把很多种算法整合到一起,而共用很多的代码,后面会继续看到。

            好了,这次就这么多了。

  • 相关阅读:
    FZU 2112 并查集、欧拉通路
    HDU 5686 斐波那契数列、Java求大数
    Codeforces 675C Money Transfers 思维题
    HDU 5687 字典树插入查找删除
    HDU 1532 最大流模板题
    HDU 5384 字典树、AC自动机
    山科第三届校赛总结
    HDU 2222 AC自动机模板题
    HDU 3911 线段树区间合并、异或取反操作
    CodeForces 615B Longtail Hedgehog
  • 原文地址:https://www.cnblogs.com/hliu17/p/7399970.html
Copyright © 2011-2022 走看看