图的存储
邻接矩阵
设图
G
(
V
,
E
)
G(V, E)
G(V,E)的顶点标号为
0
,
1
,
⋯
,
N
−
1
0, 1, \cdots , N-1
0,1,⋯,N−1, 那么可以令二维数组
G
[
N
]
[
N
]
G[N][N]
G[N][N]的两维分别表示图的顶点标号,即如果
G
[
i
]
[
j
]
G[i][j]
G[i][j]为1, 则说明顶点i和j之间有边;如果
G
[
i
]
[
j
]
G[i][j]
G[i][j]为0,则说明顶点i和顶点j之间不存在边,这个二维数组被称为邻接矩阵。
如果存在边权,则可以令
G
[
i
]
[
j
]
G[i][j]
G[i][j]存放边权,对不存在的边可以设边权为0、-1或是一个很大的数。
邻接矩阵只适合用于顶点数目不太大(一般不超过1000)的情况。
邻接表
由于vector有变长数组之称,因此可以用vector数组 A d j [ N ] Adj[N] Adj[N],其中 N N N为顶点个数,来存储邻接表。
- 如果邻接表只存放每条边的终点编号,而不存放边权:
vector<int> Adj[N]; \\定义int型的vector数组存储邻接表
Adj[1].push_back(3); \\添加一条从1号顶点到达3号顶点的有向边
- 如果需要同时存放边的终点编号和边权:
//方法一:
//定义Node型的vector数组存储邻接表
struct Node{
int v; //边的终点编号
int w; //边权
}
vector<Node> Adj[N];
//添加从1号到达3号顶点的有向边,边权为4
Node temp;
temp.v = 3;
temp.w = 4;
Adj[1].push_back(temp);
//方法二:
//定义Node型的vector数组存储邻接表
struct Node{
int v, w;
Node(int _v, int _w): v(_v), w(_w){} //构造函数
}
vector<Node> Adj[N];
//添加从1号到达3号顶点的有向边,边权为4
Node temp(3, 4);
Adj[1].push_back(temp);
图的遍历
采用深度优先搜索(DFS)法遍历图
沿着一条路径直到无法继续前进,才退回到路径上离当前顶点最近的还存在未访问分支顶点的岔道口,并前往访问那些未访问分支顶点,直到遍历完整个图。
- 连通分量:无向图中,图G的任意两个顶点都连通(即两个顶点之间可以通过一定路径相互到达),则称图G为连通图。非连通图中的极大连通子图,称为连通分量。
- 强连通分量:有向图中,任意两个顶点都强连通, 则称图G为强连通图。非强连通图的极大强连通子图,称为强连通分量。
DFS Code
const int MAXV = 1000; //最大顶点数
const int INF = 1000000000; //设INF为一个很大的数
//方法一:邻接矩阵版
int n, G[MAXV][MAXV]; //n为顶点数,MAXV为最大顶点数
bool vis[MAXV] = {false}; //如果顶点i已被访问,则vis[i] == true.
void DFS(int u, int depth){ //u为当前访问的顶点标号, depth为深度
vis[u] = true; //设置u已被访问
for(int v = 0; v < n; v++ ){
if(vis[v] == false && G[u][v] != INF){
DFS(v, depth + 1); //访问v, 深度加1
}
}
}
//遍历图G
//如果要遍历整个图,就需要对所有连通块分别进行遍历。
void DFSTrave(){
for(int u = 0; u < n; u++){
if(vis[u] == false){
DFS(u, 1);
}
}
}
//方法二:邻接表版
int n;
vector<int> Adj[MAXV]; //图G的邻接表
bool vis[MAXV] = {false};
void DFS(int u, int depth){
vis[MAXV] = true;
for(int i = 0; i < Adj[u].size(); i++){
v = Adj[u][i];
if(vis[v] == false){
DFS(v, depth + 1);
}
}
}
void DFSTrave(){
for(int u = 0; u < n; u++){
if(vis[u] == false){
DFS(u, 1);
}
}
}
采用广度优先搜索(BFS)法遍历图
如果要遍历整个图,则需要对所有连通块分别进行遍历。使用BFS遍历图的基本思想是建立一个队列,并把初始顶点加入队列,此后每次都取出队首顶点进行访问,并把从该顶点出发可以到达的未曾加入过队列的顶点全部加入队列,直到队列为空。
BFS Code
//方法一: 零阶矩阵版
int n, G[MAXV][MAXV]; //n为顶点数, MAXV最大顶点数
bool inq[MAXV] = {false}; //若顶点i曾入过队列,则inq[i] == true。初值为false
void BFS(int u){ //遍历u所在的连通块
queue<int> q; //定义队列q
q.push(u); //将初始点u入列
inq[u] = true; //设置u已被加入过队列
while(! q.empty){ //只要队列非空
int u = q.front(); //取出队首元素
q.pop(); //将队首元素出队
for(int v = 0; v < n; v++){
if(G[u][v] != INF && inq[v] == false){
q.push(v);
inq[v] == true;
}
}
}
}
void BFSTrave(){
for(int u = 0; u < n; u++){ //枚举所有顶点
if(inq[u] == false){ //如果u未曾加入过队列
BFS(q); //遍历u所在的连通块
}
}
}
//方法二:邻接表版
struct Node{
int v; //顶点编号
int layer; //顶点层号
};
vector<Node> Adj[N];
int n;
bool inq[MAX];
void BFS(int s){ //s为起始顶点编号
queue<Node> q; //BFS队列
Node start; //起始顶点
start.v = s; //起始顶点编号
start.layer = 0; //起始顶点层号为0
q.push(start); //将起始顶点压入队列
inq[start.v] = true; //起始顶点的编号设为已被加入过队列
while(!q.empty()){
Node topNode = q.front(); //取出队首顶点
q.pop(); //队首顶点出队
int u = topNode.v; //队首顶点的编号
for(int i = 0; i < Adj[u].size(); i++){
Node next = Adj[u][i]
next.layer = topNode.layer + 1; //next层号等于当前顶点层号加1
//如果next的编号未被加入过队列
if(inq[next.v] == false){
q.push(next); //将next入队
inq[next.v] = true; //next的编号设为已被加入过队列
}
}
}
}