本文适用于对图论代码实现不了解的初学者。
图的概念
所谓图,即一些顶点和边的集合。其中点的位置、边的形状是无所谓的,重点研究的是点和边的关系。
在图中,一条边仅能连接2个点。边分为有向边(单向连接)和无向边(双向连接)
有向图:边为有向边的图。
无向图:边为无向边的图。
树:边数=顶点数-1的无向连通图。
图的建立
图有两种表示方法:邻接矩阵和邻接表。
邻接矩阵指:对于一个n个顶点的图,建立一个n*n的二维矩阵g[n][n],其中g[i][j]可以按照自己的选择定义:对于无权图,1代表i到j有一条有向边,0代表i到j没有边。对于带权图,a[i][j]=x代表i到j有一条权值为x的边。
如果是无向图,可以让a[i][j]=a[j][i]=x代表i和j连接了一条权值为x的边。
邻接矩阵存在一些缺陷:第一是如果是稀疏矩阵(边数远小于点数的平方),会存在大量的0,浪费了大量的空间,如果n较大时,二维矩阵就无法存储。第二是如果有重边的话(如i到j有两条不同的有向边相连),邻接矩阵将无法存储。
这里介绍一下邻接表。所谓邻接表,指对于n个顶点,先设置n条空链表,假设引进了i到j的一条边,那么在第i个链表上新增一个节点来描述这条边的信息。由于链表占据的空间是可变的,因此这种存图的方法方便存储稀疏矩阵(尤其是树的建立)。
使用vector数组或ArrayList数组建立邻接表
c++里stl标准库的vector工具、java里util包中的ArrayList都是可变长度的数组,可以用来建立邻接表。
vector的说明
需要#include<vector>头文件。
vector<int>a; //vector的声明。等价于int a[n]
a.size() //表示a中的元素个数
a.push_back(i) //在a的最后一个元素后面增加新元素i
a.pop_back() //删除a的最后一个元素
a[i] //表示a的第i个元素。a的所有元素是从a[0] 到a[a.size()-1],这一点和数组是一样的。
ArrayList的说明
需要import java.util.ArrayList;导入相关包。
ArrayList<Integer>list=new ArrayList<Integer>(100010); //实例化
list.size() //list中的元素个数。
list.add(x) //最后一个元素之后添加新元素x
list.get(i) //获取第i个元素,所有元素下标是从0到list.size()-1
用vector建立邻接表的示例
输入:
第一行两个正整数n和m,代表顶点数和边数。
接下来的m行,每行两个正整数x和y,代表x到y之间有一条无向边。
样例输入:
3 2
1 2
1 3
vector<int>g[100010] //建议使用全局变量。
int n,m;
int main(){
cin>>n>>m;
while(m--){
int x,y;
cin>>x>>y;
g[x].push_back(y);
g[y].push_back(x); //如果是x到y的有向边,则不要这句。
}
}
图的dfs模板
int visited[100010]; //全局变量,标记数组。
void dfs(int x){
int i;
for(i=0;i<g[x].size();i++){ //用了全局变量,这里不用参数传入。
if(!visited[g[x][i]]){
visited[g[x][i]]=1;
dfs(g[x][i]);
}
}
}
图的bfs模板
int visited[100010]; //全局变量,标记数组。
void bfs(){
queue<int>q; //使用队列辅助bfs。
q.push(1);
visited[1]=1;
while(!q.empty()){
int temp=q.front();
for(int i=0;i<g[temp].size();i++){
if(!visited[g[temp][i]]){
visited[g[temp][i]]=1;
q.push(g[temp][i]);
}
}
q.pop();
}
}