图应该是计算机应用的一个比较重要的方向吧,数据结构与算法c++语言描述里面单独拿出一章用来讲图。
不过这本书讲的比较浅显且不全面,如果想深入的研究图的知识,建议去学习下图论,不过图论自学起来有点难,建议去大学蹭课。
下面给出有向加权图的二维矩阵实现方法:
template<class T>
class graph
{
public:
virtual ~graph(){}
virtual int numberOfVertices() const=0;
virtual int numberOfEdges() const=0;
virtual bool existsEdge(int,int)const=0;
virtual void insertEdge(edge<T>*)=0;
virtual void eraseEdge(int,int)=0;
virtual int degree(int) const=0;
virtual int inDegree(int) const=0;
virtual int outDegree(int) const=0;
virtual bool directed() const=0;
virtual bool weighted() const=0;
virtual vertexIterator<T>* iterator(int)=0;
};
template<class T>
class adjacencyWdigraph:public graph<T>
{
protected:
int n;//顶点个数
int e;//边的个数
T **a;//邻接数组
T noEdge;//表示不存在的边
public:
adjacencyWdigraph(int numberOfVertices=0,T theNoEdge=0)//初始化函数,构造一个数据全是noEdge的矩阵
{//构造函数
//确定顶点的合法性
if(numberOfVertices<0)
throw illegalParameterValue("number of vertices must be >=0");
n=numberOfVertices;
e=0;
noEdge=theNoEdge;
make2dArray(a,n+1,n+1);//构造一个二维矩阵,这个函数我们之前写过
for(int i=1;i<=n;i++)//将二维矩阵里面的元素全部初始化为noEdge
fill(a[i],a[i]+n+1,noEdge);
}
~adjacencyWdigraph(){delete2dArray(a,n+1);}//析构掉初始化函数里面的堆栈空间
int numberOfVertices() const {return n;}
int numberOfEdges() const {return e;}
bool directed() const {return true;}
bool weighted() const {return true;}
bool existsEdge(int i,int j)const
{//返回值是真,当且仅当(i,j)是图的一条边
if(i<1||j<1||i>n||j>n||a[i][j]==noEdge)
return false;
else
return true;
}
void insertEdge(edge<T>*theEdge)//edge模板类有三个函数分别获取边的两个点和权重
{//插入边;如果该边已经存在,则用theEdge->weight()修改边的权
int v1=theEdge->vertex1();
int v2=theEdge->vertex2();
if(v1<1||v2<1||v1>n||v2>n||v1==v2)
{
ostringstream s;
s<<"("<<v1<<","<<v2<<") is not a permissible edge";
throw illegalParameterValue(s.str());
}
if(a[v1][v2]==noEdge)
e++;
a[v1][v2]=theEdge->weight();
}
void eraseEdge(int i,int j)
{//删除边(i,j)
if(i>1||j>1||i<n||j<n||a[i][j]!=noEdge)
{
a[i][j]=noEdge;
e--;
}
}
void checkVertex(int theVertex)const
{//确认是有效顶点
if(theVertex<1||theVertex>n)
{
ostringstream s;
s<<" no vertex"<<theVertex;
throw illegalParameterValue(s.str());
}
}
int degree(int theVertex)const
{
throw undefinedMethod("degree() undefined");//这里定义的是一个有向加权图,所以没有度
}
int outDegree(int theVertex)const
{//返回顶点theVertex的出度
checkVertex(theVertex);
int sum=0;
for(int j=1;j<=n;j++)
if(a[theVertex][j]!=noEdge)//以j为列的点的和为出度
sum++;
return sum;
}
int inDegree(int theVertex)const
{//返回顶点theVertex的出度
checkVertex(theVertex);
int sum=0;
for(int j=1;j<=n;j++)
if(a[j][theVertex]!=noEdge)//以j为列的点的和为出度
sum++;
return sum;
}
class myIterator:public vertexIterator<T>
{
public:
myIterator(T* theRow,T theNoEdge,int numberOfVertices)
{
row=theRow;
noEdge=theNoEdge;
n=numberOfVertices
currentVertex=1;
}
~myIterator(){}
int next(T& theWeight)//有点像python的生成器
{//返回下一个顶点。若不存在,则返回0
//赋权值theWight=边的权值
//寻找下一个邻接的顶点
for(int j=currentVertex;j<=n;j++)
if(row[j]!=noEdge)//我们的矩阵为n+1阶所以第零行和第零列是不存储数据的,如果输入的顶点不关联任何边即其权值为noEdge
{
currentVertex=j+1;
theWeight=row[j];
return j;//返回当前顶点及其权值,currentVertex+1
}
//不存在下一个邻接的顶点
currentVertex=n+1;//否则返回0退出函数
return 0;
}
protected:
T* row; //邻接矩阵的行
T noEdge; //当且仅当没有关联的边
int n; //顶点数
int currentVertex;
};
myIterator* iterator(int theVertex)
{//返回顶点theVetex的迭代器
checkVertex(theVertex);
return new myIterator(a[theVertex],noEdge,n);
}
}
其实上述代码在你理解了图之后是非常简单的,只是最后的那个迭代部分需要提一下,最后的函数之所以用new在堆栈中分配内存,是为了让迭代器在使用后不会被自动析构,而currentVetex不会被重置,这样我们就能连续的使用next每次都以上一个点为起始点。
class linkedDigraph:public graph<bool>
{
protected:
int n;
int e;
graphChain<int> *aList;//邻接表
public:
linkedDigraph(int numberOfVertices=0)
{
if(numberOfVertices<0)
throw illegalParameterValue("number of vertexs must be >=0");
n=numberOfVertices;
e=0;
aList=new graphChain<int> [n+1];//创建一个数组,每个数组元素都是一个链表
}
~linkedDigraph(){delete [] aList;}
//关于numberOfVertexs,numberOfEdges,directed和weighted的代码与上面的类的代码相同这里不再重复
bool existsEdge(int i,int j) const
{
if(i<1||j<1||i>n||j>n||aList[i].indexOf(j)==-1)
return false;
else
return true;
}
void insertEdge(edge<bool>* theEdge)
{
int v1=theEdge->vertex1();
int v2=theEdge->vertex2();
if(v1<1||v2<1||v1>n||v2>n||v1==v2)
{
ostringstream s;
s<<"("<<v1<<","<<v2<<") is not a permissible edge";
throw illegalParameterValue(s.str());
}
if(aList[v1].indexOf(v2)==-1)
{//新边
aList[v1].insert(0,v2);
e++;
}
}
void eraseEdge(int i,int j)
{
if(i>=1&&j>=1&&i<=n&&j<=n)
{
int *v=aList[i].eraseElement(j);//删除,存在也删,不存在也删,不同之处在于,前者边数要减1
if(v!=NULL)//边(i,j)存在
e--;
}
}
void checkVertex(int theVertex)const
{
if(theVertex<1||theVertex>n)
{
ostringstream s;
s<<" no vertex"<<theVertex;
throw illegalParameterValue(s.str());
}
}
int degree(int theVertex)const
{
throw undefinedMethod("degree() undefined");
}
int outDegree(int theVertex)const
{
checkVertex(theVertex);
return aList[theVertex].size();//该链表的大小就是其所有临界点的个数也就是出度
}
int inDegree(int theVertex)const
{
checkVertex(theVertex);
//计算顶点theVetex的入边
int sum=0;
for(int j=1;j<=n;j++)//入度考虑的列和。所以要考虑每一行的数据
if(aList[j].indexOf(theVertex)!=-1)
sum++;
return sum;
}
//迭代器代码省略
}
上面是用链表实现的一个加权有向图,其实很多代码和之前的类完全一样,唯一的区别就是链表实现中行用链表表示,一个链表的所有数据都是头节点的邻接点。
还有就是上述代码没有实现graphChain的代码,但是我们却默认使用了。
广度优先搜索:从一个顶点开始,搜索所有可到达顶点的方法。
virtual void bfs(int v,int reach[],int label)
{//广度优先搜索。reach[i]用来标记从v可到达的所有顶点
arrayQueue<int>q(10);
reach[v]=label;
q.push(v);
while(!q.empty())
{//从队列中删除一个标记过的顶点
int w=q.front();
q.pop();
//标记所有没有到达的的邻接于顶点w的顶点
vertexIterator<T>*iw =iterator(w);
int u;
while((u=iw.next())!=0)
if(reach[u]==0)
{
q.push(u);
reach[u]=label;
}
delete iw;
}
}
上述代码输入为起始顶点,可到达的顶点容器以及已到达顶点的标记。
首先创建一个队列将要查找邻接顶点的起始点v压入其中,然后从队列q中每次提取一个数据并删除然后查找该数据的邻接顶点,然后将找到的邻接顶点压入reach数组中并标记为label,再然后将这些顶点压入队列q,然后再提取q中的数据并删除,寻找该数据的邻接顶点,存入reach并标记为label(如果之前没有存入的话),并将那些没有到达过的点压入队列q,如此循环直至q为空,最后reach里面就是我们需要的v能到到的所有点的集合。
上面这个代码我们之前哪里好像使用过类似的。
上面的代码为使用迭代器生成的一个统一的代码,它适用于不同形式的图的实现。
下面分别为二维数组和链表实现图写专门的广度搜索代码:
二维数组的:
void bfs(int v,int reach[],int label)
{//广度优先搜索。reach[i]用来标记从v可到达的所有顶点
arrayQueue<int>q(10);
reach[v]=label;
q.push(v);
while(!q.empty())
{//从队列中删除一个标记过的顶点
int w=q.front();
q.pop();
//标记所有没有到达的的邻接于顶点w的顶点
for(int u=1;u<=n;u++)
{//访问顶点w的一个关联的顶点
if(a[w][u]!=noEdge&&reach[u]==0)
{//u是一个没有到达的顶点
q.push(u);
reach[u]=label;//做到达标记
}
}
}
}
与通用代码大同小异,只是没用迭代器。
链表描述的:
void bfs(int v,int reach[],int label)
{//广度优先搜索。reach[i]用来标记从v可到达的所有顶点
arrayQueue<int>q(10);
reach[v]=label;
q.push(v);
while(!q.empty())
{//从队列中删除一个标记过的顶点
int w=q.front();
q.pop();
//标记所有没有到达的的邻接于顶点w的顶点
for(chainNode<int>*u=aList[w].firstNode;u!=NULL;u->next)
{//访问顶点w的一个关联的顶点
if(reach[u->element]==0)
{//u->element是一个没有到达的顶点
q.push(u->element);
reach[u->elememt]=label;//做到达标记。
}
}
}
}
通用的代码虽然能够适用于所有的图的描述方法,但是在性能上要差于定制的方法,所以要根据不同的情况使用不同的方法。
深度优先搜索:从起始点v开始寻找所有可能到达的顶点。(与广度搜索的目的一样,但是方法不一样)
这里是优先搜索当前点的一个邻接点然后更新当前点(而不会一次搜索当前点的所有邻接点)当当前点没有邻接点时就回退一步。
这种方法我们在迷宫代码那里使用过一次,只不过这里使用的是迭代的方法。
深度优先搜索的通用实现:
void rDfs(int v)
{//深度优先搜索递归方法
reach[v]=label;
vertexIterator<T>*iv=iterator(v);//从v开始迭代
int u;
while((u=iv->next())!=0)//只要v还存在邻接顶点就一直循环
//访问与v相邻的顶点
if(reach[u]==0)//u是一个没有到达的顶点
rDfs(u);//开始迭代
delete iv;
}
void dfs(int v,int reach[],int label)
{//深度优先搜索。reach[i]用来标记所有邻接于顶点v的可到达的顶点
graph<T>::reach=reach;
graph<T>::label=label;
rDfs(v);
}
每一次递归都会将一个与当前点相邻的点进行标记,直到当前点没有邻接点,然后会回退一步去寻找另一个相邻点的相邻点,直到回退到最初的循环层,并且循环结束。