图的存储与遍历(链式前向星中的DFS与BFS)

图的存储方式:

1.图的数组(邻接矩阵)存储表示,其中无向图的存储方式为对称矩阵数组,有向图的存储方式为非对称矩阵数组。求最短路径时常常采用数组存储表示各点间的路径。

2.边集方法                    

边的定义:

     stuct edge_set

         int u,v;

         int weight;

       

edge[N];

边的结点:
edge[i].u

edge[i].v

边的权重:
edge[i].weight

例如:


edge[0].u=1,edge[0].v=2,edge[0].weight=6

edge[1].u=1,edge[0].v=3,edge[0].weight=3

edge[2].u=2,edge[0].v=4,edge[0].weight=9

edge[3].u=3,edge[0].v=4,edge[0].weight=5

3.图的邻接表存储表示法

  邻接表是图的一种链式存储结构

  对图中每个顶点建立一个单链表,第i个单链表中的结点表示依附于顶点vi的边;由此通过遍历每个顶点所对应的链表就可以遍历所有的边。

详解以及数组实现:https://www.cnblogs.com/ECJTUACM-873284962/p/6905416.html


但使用链表来实现邻接表对于痛恨指针的的朋友来说,这简直就是噩梦。于是便有了以结构体数组形式来实现的方法,又称

链式前向星。


4.链式前向星(邻接表存储的结构体数组实现方法)


const int M=105;  //结点数
const int oo=100000000;
struct node {
       int to,next,weight;
} edge[M*100]; //M*100为边数
int head[2*M]; //头结点
构图:
void add(int a, int b, int c) {
    	edge[tot].to=b;    edge[tot].weight=c;    edge[tot].next=head[a];    head[a]=tot++;
    } 

tot表示全局变量,邻接边边数;

head[a]存储结点a的第一个边的数组edge下标,表示这条边aedge[head[a]].to,费用为c.

edge[tot].next表示与这条边相邻的结点,初始化head[i]-1,表示一开始没有相邻结点。


《图的存储与遍历(链式前向星中的DFS与BFS)》

图来源:https://www.cnblogs.com/LQ-double/p/5971323.html

如果要遍历所有的点则for两层循环就可以了。




—————————————————————————————我是分割线———————————————————————————————



图的遍历方式:


DFS(深度优先搜索):从某个状态开始,不断遍历可以到达的状态直到状态无法转移,然后回退到前一步的状态,然后继续转移到其他状态,如此不断重复,直到到达指定的状态或求出解。

1.找到初始点V0,访问此结点。

2.依次找到所有与V0邻接的未被访问的结点并进行深度优先搜索遍历图。

3.直到所有与V0相通的点都被访问。


图解:

先看一幅有向图

《图的存储与遍历(链式前向星中的DFS与BFS)》

可以发现,其遍历的顺序为

0->3->1->2->4

           其的概念就是一条路走到黑之后回头到上一个路口换条路走

图的表示方法包括邻接表,邻接矩阵等,邻接矩阵表示的是用一个二维数组表示图的联通,其中i行j列表示了i结点和j结点的联通情况,如果其为1,说明是联通的,如果其为0,反之;邻接表表示的是一个一维数组,但是数组中每个列表包含着是链表;

看个图加深理解:


《图的存储与遍历(链式前向星中的DFS与BFS)》

其中a是有向图/原图,b是邻接表表示图,c是邻接矩阵表示图;

图解来源:https://www.cnblogs.com/George1994/p/6399889.html

伪代码

递归实现

1. 访问数组初始化:visited[n] = 0
 访问顶点:visited[v] = 1
 取v的第一个邻接点w;
 循环递归:
 while(w存在)
 if(w未被访问过)
 从顶点w出发递归执行;
 w = v的下一个邻接点;

非递归实现(使用栈的数据结构)

1.  栈初始化:visited[n] = 0
 访问顶点:visited[v] = 1
 入栈
 while(栈不为空)
 x = 栈的顶元素,并且出栈;
 if (存在并找到未被访问的x的邻接点w)
 访问w:visited[w] = 1
 w进栈

实现代码(计算距离)

using namespace std;
struct node{
	int to,nex,val;
}edge[200005];
int head[100005],cnt;//head[i]代表  以i结点开始的第一条边的下标 
void add(int u,int v,int w){
	edge[cnt].to=v;
	edge[cnt].val=w;
	edge[cnt].nex=head[u];
	head[u]=cnt;
	cnt++; 
}
void dfs(int t,int f,int w){
	for(int i=head[t];~i;i=edge[i].nex){
		int v=edge[i].to;
		if(v==f)continue;    //这里的f代指父亲结点,用来防止搜回去
		dis[v]=w+edge[i].val;
		dfs(v,t,w+edge[i].val);
	}
}
int main(){
	int n,m,i,j;
	memset(head,-1,sizeof(head));
	cin>>n>>m;
	for(i=1;i<=m;i++){
		int x,y,w;
		cin>>x>>y;
		w=1;
		add(x,y,w);
		add(y,x,w); 		//根据无向图和有向图两种情况进行选择性注释此行
	}
	dfs(1,1,0);
	return 0;
}

BFS(宽度优先搜索):从某状态开始访问,不断遍历从该状态访问可以直接到达的状态,然后从这些状态出发,不断访问这些状态可以直接到达的状态,直到所有可以直接或间接到达的状态都被访问。(这种访问方式需要借助队列实现)

先看一幅有向图

《图的存储与遍历(链式前向星中的DFS与BFS)》

可以发现,其遍历的顺序为

0->3->4->1->2

伪代码

非递归实现

1. 初始化队列:visited[n] = 0
2. 访问顶点:visited[v] = 1
3. 顶点v加入队列
4. 循环:
 while(队列是否为空)
 v = 队列头元素
 w = v的第一个邻接点
 while(w存在)
 if(如果w未访问)
 visited[w] = 1;
 顶点w加入队列
 w = 顶点v的下一个邻接点

上面的寻找下一个邻接点,是寻找之前结点的下一个邻接点,而不是当前的,否则就变成DFS了;

实现代码:

struct node{
	int to,nex,val;
}edge[200005];
int head[100005],cnt;//head[i]代表以i结点开始的第一条边的下标 
void add(int u,int v,int w)
{
	edge[cnt].to=v;
	edge[cnt].val=w;
	edge[cnt].nex=head[u];
	head[u]=cnt;
	cnt++; 
}
int vis[100005];
queue<int>tp;
int main(){
	int n,m,i,j;
	memset(head,-1,sizeof(head));
	cin>>n>>m;
	for(i=1;i<=m;i++)
	{
		int x,y,w;
		cin>>x>>y;
		w=1;
		add(x,y,w);
//		add(y,x,w);		//根据无向图和有向图两种情况进行选择性注释此行
	}
	vis[1]=1;
	tp.push(1);
	while(!tp.empty()){
		int f=tp.front();
		tp.pop();
		for(i=head[f];~i;i=edge[i].nex)
		{
			int v=edge[i].to;
			if(vis[v]) continue;
			vis[v]=vis[f]+1;
			tp.push(v);
		}
	}
	return 0;
}

链式前向星的优点在于可以通过该表达式:for(int i=head[u];~i;i=edge[i].next),很好的对各个点所连有的点进行遍历。通常在题目中只需执行起始顶点的相应bfs/dfs函数,即可完成对整个图的遍历。




     

    原文作者:数据结构之图
    原文地址: https://blog.csdn.net/qq_41287489/article/details/79323313
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞