图的基本操作有如下几种:加入(删除)顶点、加入(删除)边,取顶点,求邻接点,求节点的度,图的遍历。除了遍历算法,其他操作都比较简单,所以说说遍历就好了,这也是很多其他图算法的基础。
图按是否有向分为:有向图、无向图;按是否带权分为无权图、带全图(网)。按这两种标准组合可得四种图类型,定义为下面的枚举类型:
enum Graph_Type{ DG, DN, UG, UN };
先给出边类型,顶点类型,图类型的定义:
//VertexType表示顶点值的类型,CostType表示边的权的类型,
template< typename VertexType, typename CostType >
class Edge
{
friend class Graph<VertexType, CostType >;
public:
Edge( int sv, int ev, CostType cost, Edge<VertexType, CostType > *next )
:start_vertex( sv ), end_vertex( ev ), cost( cost ), next_adj( next )
{
}
private:
int start_vertex;//起始节点编号
int end_vertex;//终结点编号
CostType cost;//边的权
Edge<VertexType, CostType > *next_adj;
};
template< typename VertexType, typename CostType >
class Vertex
{
friend class Graph<VertexType, CostType>;
public:
Vertex(VertexType va, int sn, Edge<VertexType, CostType > *fa, int p )
:value(va), serial_number( sn ), first_adj( fa ),
parent(p)
{
}
private:
VertexType value;//顶点值
int serial_number;//顶点编号
Edge<VertexType,CostType> *first_adj;
int parent;//用于生成各种生成树,
};
/*
*之前没有注意到图中的节点的值类型和边的权值的类型是不一样的
*如果只有一个类型参数,那么这个图就不实用了.
*/
template< typename VertexType, typename CostType >
class Graph
{
public:
Graph( int vex_num, int edge_num,
vector<VertexType>& value,
vector<int> begin_vex,
vector<int> end_vex,
vector<CostType>& cost,
Graph_Type gt );
void BreadthFirstSearch( int i );//从节点i开始进行广度优先搜索。
void DepthFirstSearch( );
private:
vector< Vertex<VertexType, CostType> > vertex_table;//顶点数组
int vex_num;//顶点数
int edge_num;//边数
Graph_Type gtype;//图类型
};
先搞清图的遍历的含义:指从图的任一顶点出发,对图中每个顶点访问一次且只访问一次。图的遍历算法有两种,一种是深度优先遍历,一种是广度优先遍历。
先说前者。深度优先遍历的基本思想就是从某个源顶点v出发,沿着以它为起点(尚未探测过)的边(v,w),如果w已经访问过,则另选一条从v出发的尚未探测过的边。否则,访问w,然后从w开始搜索,直至搜索完从w出发的所有路径,即访问完所有从w出发可达的顶点,最后回溯到顶点v,再从v出发,探测尚未探测过的边,直至所有从v出发的边都探测过为止。如果还存在未被访问的顶点,则重复以上过程,直至所有的顶点都被访问过为止。
算法描述起来还是挺麻烦的啊。从描述中可以看出,我们需要使用一个辅助数组visited,用以记录顶点是否被访问过。另外,深度优先算法有点类似于树的先根遍历。
template< typename VertexType, typename CostType >
void Graph<VertexType, CostType>::DepthFirstSearch()
{
vector<bool> visited( vex_num, false );
clear_vex_parent();
for( int j = 0; j < vex_num; ++j )
{
if( visited[j] == false )
{
DepthFirstSearch( j+1, visited );
}
}
}
template< typename VertexType, typename CostType >
void Graph<VertexType, CostType>::DepthFirstSearch( int i, vector<bool> &visited )
{
int index;
visited[i-1] = true;
Vertex<VertexType, CostType> *vex = &this->vertex_table[i-1];
Edge<VertexType, CostType> *edge = vex->first_adj;
while( edge != NULL )
{
index = edge->end_vertex;
if( visited[index-1] == false )
{
this->vertex_table[index-1].parent = i;
DepthFirstSearch( index, visited );
}
edge = edge->next_adj;
}
}
再看广度优先搜索。
从源顶点v出发,依次探测所有从v出发的所有边,访问v的所有邻接点w1,w2,···,
wn,然后再依次访问与w1,w2,···,wn邻接的所有未访问过的顶点,直至图中所有和v路径相通的顶点都已被访问,如果图中存在尚未访问的顶点,则重复上述过程,直至图中所有顶点都已被访问过。图的广度优先搜索类似于树的层次遍历,需要使用队列作为辅助的数据结构,另外,和深度优先搜索一样,需要一个visited数组记录顶点是否被访问过。
两个算法的时间复杂度都是O(V+E),二者实际上都是遍历了一遍邻接表而已,只是方式不同罢了。
template< typename VertexType, typename CostType >
void Graph<VertexType, CostType>::BreadthFirstSearch( int i )
{
vector<bool> visited( vex_num, false );
queue< Vertex<VertexType, CostType>* > vertex_queue;
vertex_queue.push( &vertex_table[i-1] );//添加到队尾
visited[ i-1 ] = true;
int visitedNumber = 1;
clear_vex_parent();
while( visitedNumber != vex_num )
{
while( !vertex_queue.empty() )
{
Vertex<VertexType, CostType> *vertex = vertex_queue.front();
vertex_queue.pop();//删除队头.
Vertex<VertexType, CostType> *adj_vex;
Edge<VertexType, CostType> *edge = vertex->first_adj;
while( edge != NULL )
{
int serial_number = edge->end_vertex;
adj_vex = &vertex_table[serial_number-1];
if( !visited[serial_number-1] )//如果尚未访问.
{
vertex_queue.push( adj_vex );
adj_vex->parent = vertex->serial_number;
//设置新加入队列的节点的父母:。
visited[serial_number-1] = true;
++visitedNumber;
}
edge = edge->next_adj;
}
}//while
//如果图为非连通图,则需要从多个源点开始进行广度优先搜索才能确保搜索到图中的每个节点.
for( int j = 0; j < vex_num; ++j )
if( visited[j] == false )
{
vertex_queue.push( &(vertex_table[j]) );
++visitedNumber;
visited[j] = true;
break;
}
}
}