您的位置:首页 > 其它

深度优先搜索和广度优先搜索

2011-10-24 17:01 591 查看
将图记为G(V,E)。图的表示一般有两种方法:邻接矩阵和邻接表。邻接矩阵是一个为|V|*|V|的二维矩阵,记为array,array[i][j]表示节点i和节点j之间是否存在边,对于带权图(又称为网),array[i][j]还可以表示节点i和节点j之间边的权。因为邻接矩阵大小和图中的边数|E|无关,所以邻接矩阵更适合于稠密图(|E|接近|V|²)。邻接矩阵同样适合程序需要经常判断两个顶点之间是否存在边的情形。而图通常采用邻接表表示,因为对邻接表稍作修改就可以用于各种图的变体,因而有很好的适应性。邻接表表示法对图中的每个顶点都维护一条链表,链表中存储的是以该顶点为弧尾(起点)的边的信息。对于有向图,邻接表大小为|E|,无向图的邻接表大小为2|E|。所以邻接表所需存储空间为O(V+E)。

图的基本操作有如下几种:加入(删除)顶点、加入(删除)边,取顶点,求邻接点,求节点的度,图的遍历。除了遍历算法,其他操作都比较简单,所以说说遍历就好了,这也是很多其他图算法的基础。

图按是否有向分为:有向图、无向图;按是否带权分为无权图、带全图(网)。按这两种标准组合可得四种图类型,定义为下面的枚举类型:

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;
			}
	}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: