图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra

类比二叉树先序遍历与图深度优先搜索

在引入图的深度优先搜索之前,为了更加容易理解.先考究一种特殊的图—二叉树的深度优先搜索算法—即二叉树的递归遍历方法.

二叉树的前序遍历算法:

 

void TreeWalk(node* root)
{
	if(root)
	{
		visit(root);
		TreeWalk(root->left);
		TreeWalk(root->right);
	}
}

对于二叉树来说,步骤如下:

 

1.如果root不为空,先访问root,

2再递归下降地访问root->left和root->right.

在二叉树中,与root邻接的结点只有left and right.所以,每次只需要递归下降访问这两个节点即可.

对于图来说,可以类比二叉树(特殊情况),总结出一般规律:

1先选定任意一个点vex做为生成树的根结点.访问该根点,

2再递归下降地访问与vex邻接的而且未被访问过的所有结点.

在图中,与vex邻接的结点是不像二叉树一样只有left 和 right,因为是不确定的,因此需要循环地查找判断哪些点邻接并且尚未被访问过.

伪代码:

 

void DFS(Graph& g,int vex)
{
	Visit vex and Mark vex is visited;
	for w=FirstAdjacencyVertex to LastAdjacencyVertex(which is not visited before)
		DFS(g,w)
}

对应的C++代码:

 

 

//深度优先算法,可以形成一棵树
void DFS(Graph& g,int vex)
{
	cout<<vex<<' ';
	visited[vex]=true;//is visited
	int w=FirstAdjVertex(g,vex);
	while(w>0)
	{
		DFS(g,w);
		w=NextAdjVertex(g,vex,w);
	}
}

其中,FirstAdjVertex(g,vex)可以返回vex的第一个未被访问的邻接结点w.NextAdjVertex(g,vex,w)可以返回vex的下一个未被访问的邻接结点,在编号上在w之后.(小编号结点优先原则).

 

 

//找第一个未被访问过的邻接结点,找不到返回0
int FirstAdjVertex(Graph& g,int vex)
{
	for(int i=1;i<=g.vnum;++i)//从点1开始找与vex邻接的第一个结点
	{
		if(g.edge[vex][i]==1 && !visited[i])
			return i;
	}
	return 0;
}
//找下一个未被访问过的邻接结点
int NextAdjVertex(Graph& g,int vex,int w)
{
	for(int i=w;i<=g.vnum;++i)//从w开始,找下一个未被访问过的邻接结点
	{
		if(g.edge[vex][i]==1 && !visited[i])//若i与vex邻接且i未访问过,下一次遍历,就从这个点往下.
			return i;
	}
	return 0;//点都从1开始,返回0则所以点已访问过
}

对于无向连通图或者强有向强连通图,可以调用DFS形成一棵深度优先搜索树.但是,对于非连通图或者非强连通图.需要在生成一棵树之后,继续寻找是否还存在未被访问到的点,在这些点上继续调用DFS,最终形成森林.

 

 

void DFS_traver(Graph& g)
{
	for(int i=1;i<=g.vnum;++i)
	{
		if(!visited[i])//如果i结点未被访问过,形成森林
			DFS(g,i);
	}
}

 

深度优先搜索完整代码:

 

 

#include<iostream>
using namespace std;
const int MAX=20;//最大点数
bool visited[MAX]={false};//点标记域:标志是否被访问过.
struct Graph
{
	int vertex[MAX];//点
	int edge[MAX][MAX];//边
	int vnum;//点数
};
//创建图
void CreateGraph(Graph& g)
{
	cin>>g.vnum;
	for(int i=1;i<=g.vnum;++i)
		g.vertex[i]=i;
	for(int i=1;i<=g.vnum;++i)
	{
		for(int j=1;j<=g.vnum;++j)
			cin>>g.edge[i][j];
	}
}
//打印图
void PrintGraph(Graph& g)
{
	for(int i=1;i<=g.vnum;++i)
		cout<<g.vertex[i]<<' ';
	cout<<endl;
	for(int i=1;i<=g.vnum;++i)
	{
		for(int j=1;j<=g.vnum;++j)
			cout<<g.edge[i][j]<<' ';
		cout<<endl;
	}
}
//找第一个未被访问过的邻接结点,找不到返回0
int FirstAdjVertex(Graph& g,int vex)
{
	for(int i=1;i<=g.vnum;++i)//从点1开始找与vex邻接的第一个结点
	{
		if(g.edge[vex][i]==1 && !visited[i])
			return i;
	}
	return 0;
}
//找下一个未被访问过的邻接结点
int NextAdjVertex(Graph& g,int vex,int w)
{
	for(int i=w;i<=g.vnum;++i)//从w开始,找下一个未被访问过的邻接结点
	{
		if(g.edge[vex][i]==1 && !visited[i])//若i与vex邻接且i未访问过,下一次遍历,就从这个点往下.
			return i;
	}
	return 0;//点都从1开始,返回0则所以点已访问过
}
//深度优先算法,可以形成一棵树
void DFS(Graph& g,int vex)
{
	cout<<vex<<' ';
	visited[vex]=true;//is visited
	int w=FirstAdjVertex(g,vex);
	while(w>0)
	{
		DFS(g,w);
		w=NextAdjVertex(g,vex,w);
	}
}

void DFS_traver(Graph& g)
{
	for(int i=1;i<=g.vnum;++i)
	{
		if(!visited[i])//如果i结点未被访问过,形成森林
			DFS(g,i);
	}
}
void main()
{
	Graph g;
	CreateGraph(g);
	PrintGraph(g);
	DFS_traver(g);
}

深度优先链式表示法

#include<iostream>
using namespace std;
const int MAX=6;
struct node
{
	int value;
	node* next;
	bool visited;
	node(int v):value(v),visited(false),next(NULL){}
};
struct Graph
{
	node* A[MAX];//定义一个指针数组,数组的每一个元素都是指针
	Graph()
	{
		for(int i=0;i<MAX;++i)
		{
			A[i]=NULL;
		}
	}
};
node* insertList(node* head,node* x)
{
	if(!head)//if(!head)=if(head==NULL)
	{
		head=x;
		return head;
	}
	head->next=insertList(head->next,x);
	return head;
}
void CreateGraph(Graph& g)
{
	int inter;
	for(int i=1;i<MAX;++i)
	{
		while(cin>>inter && inter !=0)//以0为结束
			g.A[i]=insertList(g.A[i],new node(inter));//g.A[i]是表头
	}
}

void printGraph(Graph& g)
{
	node* p=NULL;
	for(int i=1;i<MAX;++i)
	{
		p=g.A[i];
		while(p)
		{
			cout<<p->value<<' ';
			p=p->next;
		}
		cout<<endl;
	}
}

node* FindAdjVertex(Graph& g,node* vex)
{
	while(vex)//从点1开始找与vex邻接的第一个结点
	{
		if(!g.A[vex->value]->visited)//查看vex是否被访问过,访问标志保存在表头g.A[i]
			return vex;
		vex=vex->next;
	}
	return NULL;
}
/*
//找下一个未被访问过的邻接结点
node* FindAdjVertex(Graph& g,node* w)
{
	while(w)//从w开始,找下一个未被访问过的邻接结点
	{
		if(!g.A[w->value]->visited)//若i与vex邻接且i未访问过,下一次遍历,就从这个点往下.
			return w;
		w=w->next;
	}
	return NULL;//点都从1开始,返回0则所有点已访问过
}
*/
void DFS(Graph& g,node* vex)
{
	cout<<vex->value<<' ';
	//vex->visited=true;//is visited
	g.A[vex->value]->visited=true;//设置标志
	node* w=FindAdjVertex(g,g.A[vex->value]);
	while(w)
	{
		DFS(g,w);
		w=FindAdjVertex(g,g.A[w->value]);
	}
}

void DFS_traver(Graph& g)
{
	for(int i=1;i<MAX;++i)
	{
		if(!g.A[i]->visited)//如果i结点未被访问过,形成森林
			DFS(g,g.A[i]);
	}
}
void main()
{
	Graph g;
	CreateGraph(g);
	printGraph(g);
	//DFS(g,g.A[1]);
	DFS_traver(g);
}

非递归版DFS

 

/*从顶点i开始对图进行深度优先搜索遍历,图由其邻接表adjlist表示*/
void DFS2(Graph& g,node* vex)
{
	node* p=vex;
	stack<node*> st;
	while (p || !st.empty())
	{
		while (p) {
			if(!g.A[p->value]->visited)//如果未被访问过
			{
				cout<<p->value<<' ';//visit
				g.A[p->value]->visited=true;//设为已访问,纵深探索
				
				st.push(p);//同时压栈
				p=g.A[p->value];//找下一个点
			}
			else
				p = p->next;//接着找未被访问过的节点
		}
		if(!st.empty())
		{
			p=st.top();
			st.pop();
			p = p->next;
		}
	}
}

图片来自算法导论第二版:

《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》

广度优先搜索算法:来源:点击打开链接

图的广度优先遍历

  图的广度优先遍历BFS算法是一个分层搜索的过程,和树的层序遍历算法类同,它也需要一个队列以保持遍历过的顶点顺序,以便按出队的顺序再去访问这些顶点的邻接顶点。 
1.连通图的广度优先遍历算法思想。
(1)顶点v入队列。
(2)当队列非空时则继续执行,否则算法结束。
(3)出队列取得队头顶点v;访问顶点v并标记顶点v已被访问。
(4)查找顶点v的第一个邻接顶点col。
(5)若v的邻接顶点col未被访问过的,则col入队列。
(6)继续查找顶点v的另一个新的邻接顶点col,转到步骤(5)。直到顶点v的所有未被访问过的邻接点处理完。转到步骤(2)。
【例】下面以图( a )为例说明广度优先搜索的过程。首先从起点 v1 出发访问 v1。v1 有两个未曾访问的邻接点 v2 和 v3 。先访问 v2 ,再访问 v3 。然后再先访问 v2 的未曾访问过的邻接点 v4、v5 及 v3 的未曾访问过的邻接 v6 和 v7 ,最后访问 v4 的未曾访问过的邻接点 v8 。至此图中所有顶点均已被访问过。得到的顶点访问序列为:
          《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》
       《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》

《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》

《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》

《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》

《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》

代码:特别鸣谢@tommyhu111

 

void BFS(Graph& g,int vex)
{
	queue<int> q;
	visited[vex] = true;								//标记已被访问过
	q.push(vex);										//初始化队列

	while(!q.empty())
	{
		vex=q.front();
		q.pop();										//取出队头数据
		cout<<vex<<' ';

		for ( int j=1; j<=g.vnum; ++j ) 
		{
			if( g.edge[vex][j] == 1 && !visited[j] )		//邻接未被访问过的结点入队
			{
				visited[vex] = true;					//标记已被访问过
				q.push(j);
			}
		}
	}
}
void BFS_traver(Graph& g)
{
	for(int i=1;i<=g.vnum;++i)
	{
		if(!visited[i])									//如果i结点未被访问过,形成森林
			BFS(g,i);
	}
}

 

Dijkstra算法

广度优先搜索的经典应用:Dijkstra算法。Dijkstra算法的思想是BFS+贪心策略。即从源结点向外辐射状地扩展结点,并且每一次都选择最优的结点进行扩展。如果有n个结点,则循环n次就可以将所有结点的最短路径求出来,缺点是无法对权重为负数的图进行并且搜索的结点多效率相对较低。

先直接贴出代码,后面再解释算法运行的原理。

 

#include<stdio.h>

#define MAX_VERTEX_NUM 100 //最大顶点数
#define MAX_INT 10000 //无穷大 

typedef int AdjType; 
typedef char VType; //设顶点为字符类型

struct PathType
{
	int route[MAX_VERTEX_NUM];//存放v到vi的一条最短路径
	int End;
};

//邻接矩阵表示的图
struct Graph
{
	VType V[MAX_VERTEX_NUM]; //顶点存储空间 
	AdjType A[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; //邻接矩阵 
};

//Dijkstra算法
//求G(用邻接矩阵表示)中源点v到其他各顶点最短路径,n为G中顶点数 
void Dijkstra(Graph * G,PathType path[],int dist[],int v,int n)
{
	int i,j,count,min,minIndex;
	//visited[n]用来标志源点到某顶点的最短路径是否求出
	int *visited=new int[n];
	for(i=0;i<n;i++)
	{	//1.初始化
		visited[i]=0;
		dist[i]=G->A[v][i];//v到它的邻接顶点的权为当前最短路径,用这个权值初始化dist[],优先队列(每次找小)
		path[i].route[0]=v; //开始结点都是v
		path[i].End=0;   //路径末尾索引
	}

	dist[v]=0;
	visited[v]=1;//源点到源点本身的最短路径已经求出 

	count=1;
	while(count<=n-1)
	{	//求n-1条最短路径
		min=MAX_INT;// MAX_INT为无穷大值,需要实际情况设置 
		//这一部分实现的是优先队列的功能,找最小值
		for(j=0;j<n;j++)
		{	//找当前最短路径长度 
			//如果j未被访问过且当前j的距离更短,更新min和minIndex
			if( !visited[j] && dist[j]<min )
			{
				minIndex=j;
				min=dist[j]; 
			}
		}
		//当visited[]数组全是1,即合被访问过时,可以退出了。
		if(min==MAX_INT)
			break;//最短路径求完(不足n-1)条,跳出while循环,考虑非连通图

		//贪心选择,选择当前最min的路径

		int pEnd = ++path[minIndex].End;
		path[minIndex].route[pEnd]=minIndex ;
		visited[minIndex]=1;//表示V到minIndex最短路径求出,标记为已被访问过
		

		for(j=0;j<n;j++)
		{	//minIndex求出后,修改dist和path向量,只更新那些未处理过的点 
			if(!visited[j] && dist[minIndex]+G->A[minIndex][j] < dist[j] )
			{
				dist[j]=dist[minIndex]+G->A[minIndex][j];//更新最短路径长度
				path[j]=path[minIndex];//更新为更短的路径
			} 
		}
		count++;
	}
	delete []visited;
}


int main()
{
	int i,j,v=0,n=6;//v为起点,n为顶点个数 
	Graph G;
	//v到各顶点的最短路径向量
	PathType* path = new PathType[n];
	//v到各顶点最短路径长度向量 
	int* dist =new int[n];
	
	AdjType a[MAX_VERTEX_NUM][MAX_VERTEX_NUM]=
	{
		{MAX_INT,7,9,MAX_INT,MAX_INT,14},
		{7,MAX_INT,10,15,MAX_INT,MAX_INT},
		{9,10,MAX_INT,11,MAX_INT,2},
		{MAX_INT,15,11,MAX_INT,6,MAX_INT},
		{MAX_INT,MAX_INT,MAX_INT,6,MAX_INT,9},
		{14,MAX_INT,2,MAX_INT,9,MAX_INT} 
	};

	for(i=0;i<n;i++)
	{
		for(j=0;j<n;j++)
		{
			G.A[i][j]=a[i][j];
		}
	} 

	Dijkstra(&G,path,dist,v,n);

	for(i=0;i<n;i++)
	{
		printf("%d到%d的最短路径:",v+1,i+1);
		for(j=0;j<=path[i].End;j++)
		{
			printf("%d ",path[i].route[j]+1);
		}

		printf("\n");
		printf("最短路径长度:%d",dist[i]);//输出为MAX_INT则表示两点间无路径
		printf("\n");
	}
	delete []path;
	delete []dist;
	return 0;
}

用一个具体的图作为例子:

 

图的领接矩阵(带权短阵):

{MAX_INT,7,9,MAX_INT,MAX_INT,14},
{7,MAX_INT,10,15,MAX_INT,MAX_INT},
{9,10,MAX_INT,11,MAX_INT,2},
{MAX_INT,15,11,MAX_INT,6,MAX_INT},
{MAX_INT,MAX_INT,MAX_INT,6,MAX_INT,9},
{14,MAX_INT,2,MAX_INT,9,MAX_INT} 

图如下:

《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》

Dijkstra算法的第一步:初始化

将所有的结点visited[i]设为0,即各个结点还没有计算出最短路径。

dist[i]表示结点i的权值,也可以看作是从源点v到结点i的最短路径长度。v到它的邻接顶点的权为当前最短路径.初始化比如,dist[0]=0,dist[1]=7,dist[2]=9,dist[5]=14,dist[4]=MAX,dist[3]=MAX(结点从0数起)

path[i]的开头结点都设为源结点v,路径末尾索引都设为0,因为当前只有一个元素。

并且dist[v] =0 ,visited[v]=1,源点到源点的最短路径已经求出。

初始化后是:

《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》

第二步:贪心选择当前未被计算出最短路径的dist[i]的最小值,如上图,现在dist[]数组是{0,7,9,14,MAX,MAX},0已被访问过,则从其余结点中贪心选择最短的一个结点1,(这一部分实现的是一个优先队列的功能,可以用STL的优先队列实现)

第三步:第二步贪心选择好一个结点后,需要更新dist[]数组和path[]数组,因为基于启发式搜索,有可能新发现的路径比原来的路径更短。

《图的深度(DFS)/广度优先搜索算法(BFS)/Dijkstra》

如图:在7,9,14,MAX,MAX中贪心选择最短路径7,即结点1.标记为已计算出最短路径visited[i]=1,然后更新未visited过的结点的路径.对于结点3,有7+15<MAX,则用22更新结点3的权,同时更新最短路径paht[]。对于结点2,7+10>9,新发现的路径比原来的更小,无须更新。对4,5这些和1并不邻接的结点有MAX+7 > MAX,无须更新,同时MAX不能设为INT_MAX

第四步:重复上第二步和第三步的步骤n-1次,就可以求出源点到所有结点的最短路径。

最短路练手:HDU1874

 

#include<iostream>
#include<string.h>
#include<limits.h>
using namespace std;

#define MAX_INT 2139062143

int map[205][205];
int n,s,v;
int visited[205],dist[205];

void Dijkstra()
{
	//第一步:初始化
	int i,min,v;
	memset(visited,false,sizeof(visited));
	memset(dist,127,sizeof(dist));//强大的近似初始化最大值
	dist[s] = 0;

	while(1)
	{
		//第二步:找最小
		min = MAX_INT,v = s;
		for(i=0; i<n; ++i)
		{
			if(!visited[i] && dist[i] < min)
				min = dist[i],v = i;
		}
		if(min == MAX_INT)
			break;
		visited[v] = true;
		//第三步:更新路径长度
		for(i=0; i<n; ++i)
		{
			if(!visited[i] && map[v][i]<MAX_INT && dist[v] + map[v][i] < dist[i])
				dist[i] = dist[v] + map[v][i];
		}
	}
}

int main()
{
	int m,i,x,y,res;
	while(cin>>n>>m)
	{
		memset(map,127,sizeof(map));
		for(i=0; i<m; ++i)
		{
			cin >> x >> y >> res;
			if(map[x][y]>res)//注意重边,选最小的边
				map[x][y] = map[y][x] = res;
		}
		cin >> s >> v;
		Dijkstra();
		if( dist[v] == MAX_INT )
			cout << "-1" << endl;
		else
			cout << dist[v] << endl;
	}
	return 0;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

    原文作者:Dijkstra算法
    原文地址: https://blog.csdn.net/linraise/article/details/9957057
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞