图算法专题(一)【定义、存储和遍历】

图算法

    • 图的定义和相关术语
    • 图的存储
      • 邻接矩阵
      • 邻接表
    • 图的遍历
      • 使用深度优先搜索(DFS)法遍历图
        • 用DFS遍历图
        • DFS的具体实现
      • 使用广度优先搜索(BFS)法遍历图
        • 用BFS遍历图
        • BFS的具体实现

图的定义和相关术语

  图由顶点和边组成,每条边的两端都必须是图的两个顶点。记号G(V,E)表示图G的顶点集为V,边集为E。

  一般来说,图可以分为有向图和无向图

  顶点的是指和该顶点相连的边的条数。对有向图来说,顶点的出边条数称为该顶点的出度,顶点的入边条数称为该顶点的入度

  顶点和边都可以有一定属性,而量化的属性称为权值,顶点的权值和边的权值分别称为点权边权

图的存储

  一般来说图的存储方式有两种:邻接矩阵和邻接表。

邻接矩阵

  当顶点数目不大的时候(不超过1000),使用邻接矩阵存储图。

  设图G(V,E)的顶点坐标为0,1,····,N-1,那么就可以令二维数组G[N][N]的两维分别表示图的顶点标号,即如果G[i][j]为1,则说明顶点i和顶点j之间有边;如果G[i][j]为0,说明i和j之间不存在边。这个二维数组G[][]被称为邻接矩阵。另外如果边存在权值,则可以令G[i][j]存放边权

邻接表

  在一些顶点数目较大(在1000以上)的情况下,一般都需要使用邻接表而非邻接矩阵来存储图。

  设图G(V,E)的顶点坐标为0,1,····,N-1,如果把同一个顶点的所有出边放在一个列表中,那么N个顶点就会有N个列表,这N个列表被称为G的邻接表,即为Adj[N],其中Adj[i]存放顶点i的所有出边组成的列表。

  使用vector来实现邻接表更为简单。开一个vector数组Adj[N],其中N为顶点个数,这样每个Adj[i]就都是一个变长数组vector,使得存储空间只与图的边数有关。

  如果邻接表只存放每条边的终点编号,而不存放边权,那么vector中的元素类型可以直接定义为int型:

	vector<int> Adj[N];

  如果想添加一条从1号顶点到3号的有向边:

	Adj[1].push_back(3);

  如果同时存放终点编号和边权,那么可以建立结构体Node,用来存放每条边的终点编号和边权:

	struct Node{
		int v;//边的终点编号
		int w;//边权
	};

  这样邻接表中的元素类型就是Node型:

	vector<Node> Adj[N];

  如果想添加一条从1号顶点到3号的边权为4的有向边:

	Node temp;
	temp.v = 3;
	temp.w = 4;
	Adj[1].push_back(temp);

  更快的做法是定义结构体构造函数:

struct Node{
	int v;
	int w;
	Node(int _v,int _w): v(_v),w(_w){} //构造函数
};

  这样就可以不定义临时变量来实现加边操作:

Adj[1].push_back(Node(3,4));

图的遍历

  图的遍历是指对图中所有顶点按一定顺序进行访问,遍历方法一般有两种:深度优先搜索(DFS)和广度优先搜索(BFS)。

使用深度优先搜索(DFS)法遍历图

用DFS遍历图

  使用DFS来遍历图,沿着一条路径直到无法继续前进,才退回到路径上离当前顶点最近的还存在未访问分支顶点的岔道口,并前往访问那些未访问分支顶点,直到遍历完整个图。

DFS的具体实现

  先介绍两个概念:

  • 连通分量。 在无向图中,如果两个顶点之间可以相互到达,那么就称这两个顶点连通。如果图G(V,E)中任意两个顶点都连通,则成G为连通图;否则,称G为非连通图,且称其中的极大连通子图为连通分量。
  • 强连通分量。在有向图中,如果两个顶点可以各自通过一条有向路径到达另一个顶点,就称这两个顶点强连通。如果图G(V,E)中任意两个顶点都强连通,则成G为强连通图;否则,称G为非强连通图,且称其中的极大强连通子图为强连通分量。

  连通分量和强连通分量均称为连通块

  DFS遍历图的基本思路:将经过的顶点设置已访问,在下次递归碰到这个顶点的时候就不再去处理,直到整个图中所有顶点都被标记为已访问。

  DFS伪代码如下,如果已知给定的图是一个连通图,则只用一次DFS就可以完成遍历:

DFS(u){//访问顶点u
	vis[u] = true;//设置u已访问
	for(从u出发可以到达的所有顶点v) //枚举从u出发可以到达的所有顶点v
		if(vis[v] == false) //如果v未访问
			DFS(v);//递归访问v
}
DFSTrave(G){//遍历图G
	for(G的所有顶点U)//遍历G的所有顶点u
		if(vis[u] == false) //如果u未被访问
			DFS(u);//访问u所在的连通块
}
  1. 连通矩阵版:
#include
using namespace std;
const int MAXV = 1000;
const int INF = 0x3fffffff;

int n,G[MAXV][MAXV];//n为顶点个数,G为图 
bool vis[MAXV] = {false};//标记是否访问

void DFS(int u,int depth){ //u为当前访问的顶点标号,depth为深度 
	vis[u] = true;//标记u已被访问 
	//如果需要对u进行一些操作,可以在这里进行
	//下面对所有从u出发能到达的分支顶点v进行枚举
	for(int v= 0;v<n;v++){//对每个顶点v 
		if(vis[v]== false && G[u][v] !=INF){
			DFS(v,depth+1);//访问v,深度+1 
		}
	} 
}
void DFSTrave(){//遍历图G 
	for(int u =0;u<n;u++){
		if(vis[u]==false){
			DFS(u,1);//访问u和u所在的连通块,1表示初始为1层 
		}
	}
} 
  1. 邻接表版:
#include
using namespace std;
const int MAXV = 1000;
const int INF = 0x3fffffff;
struct Node{
	int v;//终点顶点
	int w;//权值
	Node(int _v,int _w): v(_v),w(_w) {}
};

vector<Node> Adj[MAXV];//邻接表 
int n;//顶点个数 
bool vis[MAXV] = {false};

void DFS(int u,int depth){
	vis[u] = true;
	//如果要对u进行一些操作,可以在此处进行 
	for(int i = 0;i<Adj[u].size();i++){
		int v = Adj[u][i].v;
		if(vis[v]==false){
			DFS(v,depth+1);
		}
	} 
}

void DFSTrave(){
	for(int u =0;u<n;u++){
		if(vis[u] == false){
			DFS(u,1);
		}
	}
}

使用广度优先搜索(BFS)法遍历图

用BFS遍历图

  使用BFS需要一个队列,通过反复取队首顶点,然后将该顶点可达的所有未曾加入过队列的顶点全部入队,直到队列为空时遍历结束。

BFS的具体实现

  如果要遍历整个图,则需要对所有连通块分别进行遍历。基本思路是:建立一个队列,并把初始顶点加入队列,此后每次都取出队首顶点进行访问,并把从该顶点出发可以到达的未曾加入过队列的顶点全部加入队列,直到队列为空。

  下面是伪代码,如果图连通,只需要一次BFS即可完成遍历:

BFS(u){//遍历u所在的连通块
	queue q;//定义队列q
	将u入队;
	inq[u] = true;
	while(q非空){
		取出q的队首元素u进行访问;
		for(从u出发的所有可达顶点v)
			if(inq[v] == false){
				将v入队;
				inq[v] = true;
			}
	}
}
BFSTrave(G){//遍历图G
	for(G的所有顶点u)
		if(inq[u] == false){
			BFS(u);
		}
}

1.邻接矩阵版:

#include
using namespace std;
const int MAXV = 1000;
const int INF = 0x3fffffff;

int n,G[MAXV][MAXV];
bool inq[MAXV] = {false};

void BFS(int u){//遍历u所在的连通块 
	queue<int> q;//定义队列q 
	q.push(u);
	inq[u] = true;
	while(!q.empty()){
		int u = q.front();
		q.pop();
		for(int v = 0;v<n;v++){
			if(inq[v] == false && G[u][v] != INF){
				q.push(v);
				inq[v] = true;
			}
		}
	}
}
void BFSTrave(){
	for(int u = 0;u<n;u++){
		if(inq[u] == false){
			BFS(u);
		}
	}
}
  1. 邻接表版:
      有时候需要输出该连通块内所有其他顶点的层号
#include
using namespace std;
const int MAXV = 1000;
const int INF = 0x3fffffff;
struct Node{
	int v;
	int layer;//顶点层号
};

vector<Node> Adj[MAXV];
int n;

void BFS(int s){//s为起始顶点编号
	queue<Node> q;
	Node start;//起始顶点
	start.v = s;//起始顶点编号
	start.layer = 0;//起始顶点层号为0
	q.push(start);
	inq[start.v] = true;
	while(!q.empty()){
		Node topNode = q.front();
		q.pop();
		int u = topNode.v;
		for(int i =0;i<Adj[u].size(),i++){
			Node next = Adj[u][i];
			next.layer = topNode.layer+1;//next层号等于当前顶点层号+1
			if(inq[next.v] == false){
				q.push(next);
				inq[next.v] = true;
			}
		}
	}
}
void BFSTrave(){
	for(int u = 0;u<n;u++){
		if(inq[u]==false){
			BFS(u);
		}
	}
}

你可能感兴趣的:(模板)