图论
作者:互联网
一. 图的概念
1.定义
某类具体事物和这些事物之间的联系,由顶点(vertex)和边(edge)组成, 顶点的集合V,边的集合E,图记为G = (V,E)
顶点---具体事物, 边---具体事物之间的联系
2.分类
1、无向图 Def:边没有指定方向的图
2、有向图 Def:边具有指定方向的图 (有向图中的边又称为弧,起点称为弧头,终点称为 弧尾)
3.带权图 Def: 边上带有权值的图。(不同问题中,权值意义不同,可以是距离、时间、价格、代价等不同属性)
3.无向图的术语
两个顶点之间如果有边连接,那么就视为两个顶点相邻。
路径:相邻顶点的序列。
圈:起点和终点重合的路径。
连通图:任意两点之间都有路径连接的图。
度:顶点连接的边数叫做这个顶点的度。
树:没有圈的连通图。
森林:没有圈的非连通图。
连通图 非连通图
4.有向图的术语
在有向图中,边是单向的:每条边所连接的两个顶点是一个有序对,他们的邻接性是单向的。
有向路径:相邻顶点的序列。
有向环:一条至少含有一条边且起点和终点相同的有向路径。
有向无环图(DAG):没有环的有向图。
度:一个顶点的入度与出度之和称为该顶点的度。
1)入度:以顶点为弧头的边的数目称为该顶点的入度
2)出度:以顶点为弧尾的边的数目称为该顶点的出度
eg.
1->3->5->6 :有向路径 1->3->4->1 :有向环 (3、4、5、6) :无环有向图
节点1的度:3 节点1的入度:1 节点1的出度:2
二.图的表示
如何用计算机来存储图的信息(顶点、边),这是图的存储结构要解决的问题?
1、邻接矩阵
对于一个有V的顶点的图而言,可以使用V*V的二维数组表示。G[i][j] 表示的是顶点i与顶点j的关系。
如果顶点i和顶点j之间有边相连, G[i][j]=1
如果顶点i和顶点j之间无边相连, G[i][j]=0
对于无向图:G[i][j]=G[j][i]
在带权图中,如果在边不存在的情况下,将G[i][j]设置为0,则无法与权值为0的情况分开,因此选择较大的常数INF即可。
邻接矩阵的优点:可以在常数时间内判断两点之间是否有边存在。
邻接矩阵的缺点:表示稀疏图时,浪费大量内存空间。表示稠密图还是很划算。
eg. 邻接矩阵存储图
code
#include<bits/stdc++.h>
using namespace std;
int a[1005][1005];
int main(){
int n,m;
scanf("%d%d",&n,&m);
int u,v;
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
a[u][v]=a[v][u]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(a[i][j])
printf("%d ",j);
}
printf("\n");
}
return 0;
}
2、邻接表
通过把“从顶点0出发有到顶点2,3,5的边”这样的信息保存在链表中来表示图。
出边表的表结点存放的是从表头结点出发的有向边所指的尾顶点
入边表的表结点存放的则是指向表头结点的某个头顶点
带权图的邻接表 :在表结点中增加一个存放权的字段
eg.
将如下的图利用邻接表进行表示,并输出每个顶点的度。
Code(伪)
#include<vector>
//存边(编号和边权)
struct edge{
int v,w;
edge(){}
//构造函数
edge(int V,int W){
v=V;
w=W;
}
};
vector<edge> G[maxn];
void addEdge(int u,int v,int w){
G[u].push_back(edge(v,w));
}
for(int i=1;i<=N;++i){
int u,v,w;
cin>>u>>v>>w;
addEdge(u,v,w);
addEdge(v,u,w);
}
3.链式前向星
如果说邻接表是不好写但效率好,邻接矩阵是好写但效率低的话,前向星就是一个相对中庸的数据结构。前向星固然好写,但效率并不高。而在优化为链式前向星后,效率也得到了较大的提升。虽然说,世界上对链式前向星的使用并不是很广泛,但在不愿意写复杂的邻接表的情况下,链式前向星也是一个很优秀的数据结构。 ——摘自《百度百科》
链式前向星其实就是静态建立的邻接表,时间效率为O(m),空间效率也为O(m)。遍历效率也为O(m)。
对于下面的数据,第一行5个顶点,7条边。接下来是边的起点,终点和权值。也就是边1 -> 2 权值为1。
样例
5 7
1 2 1
2 3 2
3 4 3
1 3 4
4 1 5
1 5 6
链式前向星存的是以【1,n】为起点的边的集合,对于上面的数据输出就是:
1 //以1为起点的边的集合
1 5 6
1 3 4
1 2 1
2 //以2为起点的边的集合
2 3 2
3 //以3为起点的边的集合
3 4 3
4 //以4为起点的边的集合
4 5 7
4 1 5
5 //以5为起点的边不存在
由此可见对于每一个节点,输出的顺序为输入时的逆序
我们先对上面的7条边进行编号第一条边是0以此类推编号【0~6】,然后我们要知道两个变量的含义:
- Next:表示与这个边起点相同的上一条边的编号。
- head[ i ]数组,表示以 i 为起点的最后一条边的编号。
加边函数是这样的:
void add_edge(int u, int v, int w)//加边,u起点,v终点,w边权
{
edge[cnt].to = v; //终点
edge[cnt].w = w; //权值
edge[cnt].next = head[u];//以u为起点上一条边的编号,也就是与这个边起点相同的上一条边的编号
head[u] = cnt++;//更新以u为起点上一条边的编号
}
遍历函数是这样的:
for(int i = 1; i <= n; i++)//n个起点
{
cout << i << endl;
for(int j = head[i]; j != -1; j = edge[j].next)//遍历以i为起点的边
{
cout << i << " " << edge[j].to << " " << edge[j].w << endl;
}
cout << endl;
}
第一层for循环是找每一个点,依次遍历以【1,n】为起点的边的集合。第二层for循环是遍历以 i 为起点的所有边,k首先等于head[ i ],注意head[ i ]中存的是以 i 为起点的最后一条边的编号。然后通过edge[ j ].next来找下一条边的编号。我们初始化head为-1,所以找到你最后一个边(也就是以 i 为起点的第一条边)时,你的edge[ j ].next为 -1做为终止条件。
Code
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1005;//点数最大值
int n, m, cnt;//n个点,m条边
struct Edge
{
int to, w, next;//终点,边权,同起点的上一条边的编号
}edge[maxn];//边集
int head[maxn];//head[i],表示以i为起点的第一条边在边集数组的位置(编号)
void init()//初始化
{
for (int i = 0; i <= n; i++) head[i] = -1;
cnt = 0;
}
void add_edge(int u, int v, int w)//加边,u起点,v终点,w边权
{
edge[cnt].to = v; //终点
edge[cnt].w = w; //权值
edge[cnt].next = head[u];//以u为起点上一条边的编号,也就是与这个边起点相同的上一条边的编号
head[u] = cnt++;//更新以u为起点上一条边的编号
}
int main()
{
cin >> n >> m;
int u, v, w;
init();//初始化
for (int i = 1; i <= m; i++)//输入m条边
{
cin >> u >> v >> w;
add_edge(u, v, w);//加边
/*
加双向边
add_edge(u, v, w);
add_edge(v, u, w);
*/
}
for (int i = 1; i <= n; i++)//n个起点
{
cout << i << endl;
for (int j = head[i]; j != -1; j = edge[j].next)//遍历以i为起点的边
{
cout << i << " " << edge[j].to << " " << edge[j].w << endl;
}
cout << endl;
}
return 0;
}
三.图的遍历
从图中的某个顶点出发,按某种方法对图中的所有顶点访问且仅访问一次。为了保证图中的顶点在遍历过程中仅访问一次,要为每一个顶点设置一个访问标志。
1.DFS
概念
深度优先搜索(Depth-First Search)遍历类似于树的先根遍历,是树的先根遍历的推广。假设初始状态是图中所有顶点未曾被访问,则深度优先搜索可从图中某
个顶点v出发,访问此顶点,然后依次从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到;若此时图中尚有顶点未被访
问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
Code(邻接矩阵)
#include<bits/stdc++.h>
#define Maxn 205
using namespace std;
int n, m;
bool a[Maxn][Maxn], vis[Maxn];
void DFS(int i) {
cout << i << ' ';
vis[i] = 1;
for (int j = 1; j <= n; j++) {
if (a[i][j]) {
if (!vis[j]) {
vis[j] = 1;
DFS(j);
}
}
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int x, y;
cin >> x >> y;
a[x][y] = 1;
}
for (int i = 1; i <= n; i++) {
if (!vis[i])
DFS(i);
}
return 0;
}
2.BFS
对(a)进行广度优先搜索 遍历的过程如图(b)所示, 得到的顶点访问序列为: v1->v2->v3->v4->v5->v6->v7->v8
概念
广度优先搜索(Breadth-First Search)遍历类似于树的按层次遍历的过程。
假设从图中某顶点v出发,在访问v之后依次访问v的各个未被访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接
点”先于“后被访问的顶点的邻接点”被访问,直至图中所有已被访问的顶点的邻接点都被访问到。若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的
顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。换句话说,广度优先搜索遍历图的过程是以v为起始点,由近至远,依次访问和v有路径相通
且路径长度为1,2,…的顶点。
Code
#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[205][205],vis[205];
void bfs(int x){
queue<int> Q;
Q.push(x);
printf("%d ",x);
vis[x]=1;
while(!Q.empty()){
int now=Q.front();
Q.pop();
for(int i=1;i<=n;i++){
if(a[now][i]&&!vis[i]){
Q.push(i);
printf("%d ",i);
vis[i]=1;
}
}
}
}
int main() {
scanf("%d%d",&n,&m);
int u,v;
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
a[u][v]=a[v][u]=1;
}
int k;
scanf("%d",&k);
bfs(k);
return 0;
}
(未完结……)
标签:图论,遍历,int,访问,edge,顶点,起点 来源: https://www.cnblogs.com/pangtuan666/p/16503612.html