图的存储方式:
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下标,表示这条边从a到edge[head[a]].to,费用为c.
edge[tot].next表示与这条边相邻的结点,初始化head[i]为-1,表示一开始没有相邻结点。
图来源:https://www.cnblogs.com/LQ-double/p/5971323.html
如果要遍历所有的点则for两层循环就可以了。
—————————————————————————————我是分割线———————————————————————————————
图的遍历方式:
DFS(深度优先搜索):从某个状态开始,不断遍历可以到达的状态直到状态无法转移,然后回退到前一步的状态,然后继续转移到其他状态,如此不断重复,直到到达指定的状态或求出解。
1.找到初始点V0,访问此结点。
2.依次找到所有与V0邻接的未被访问的结点并进行深度优先搜索遍历图。
3.直到所有与V0相通的点都被访问。
图解:
先看一幅有向图
可以发现,其遍历的顺序为
0->3->1->2->4
其的概念就是一条路走到黑之后回头到上一个路口换条路走
图的表示方法包括邻接表,邻接矩阵等,邻接矩阵表示的是用一个二维数组表示图的联通,其中i行j列表示了i结点和j结点的联通情况,如果其为1,说明是联通的,如果其为0,反之;邻接表表示的是一个一维数组,但是数组中每个列表包含着是链表;
看个图加深理解:
其中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(宽度优先搜索):从某状态开始访问,不断遍历从该状态访问可以直接到达的状态,然后从这些状态出发,不断访问这些状态可以直接到达的状态,直到所有可以直接或间接到达的状态都被访问。(这种访问方式需要借助队列实现)
先看一幅有向图
可以发现,其遍历的顺序为
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函数,即可完成对整个图的遍历。