文章目录
一、路径问题
1. 判断顶点i和j之间是否有路径
方法1:利用DFS
// DFS
// 从i开始深度遍历,若i和j之间有路径,
// 则j会被访问过,即visited[j] = true;
bool visited[MaxSize];
bool has_path(AGraph* G, int i, int j)
{
memset(visited, false, sizeof visited);
dfs(G, i);
if (visited[j]) return true;
return false;
}
void dfs(AGraph* G, int u)
{
visited[u] = true;
auto p = G->adjlist[u].firstarc;
while (p)
{
auto v = p->adjvex;
if (!visited[v]) dfs(G, v);
p = p->nextarc;
}
}
方法2:利用BFS
// bfs
// 从i开始宽度遍历,若i和j之间有路径,则有2种检索方式:
// 1. j会被访问过,即visited[j] = true;
// 2. j入过队列,在出队时可以进行判断
// 两种方法都可以,因为dfs用的是方法1,那此处bfs采用方法2
bool visited[MaxSize];
bool has_path(AGraph* G, int i, int j)
{
memset(visited, false, sizeof visited);
queue<int> q;
q.push(i);
visited[i] = true;
while (q.size())
{
auto t = q.front();
q.pop();
if (t == j) return true; // 关键的检索步骤
auto p = G->adjlist[t].firstarc;
while (p)
{
auto v = p->adjvex;
if (!visited[v])
{
q.push(v);
visited[v] = true;
}
p = p->nextarc;
}
}
return false;
}
2. 输出从顶点i到j所有的简单路径
// 类比一下找二叉树根结点到叶子结点路径的问题
bool visited[MaxSize];
vector<int> path;
void find_path(AGraph* G, int i, int j)
{
memset(visited, false, sizeof visited);
dfs(G, i, j);
}
void dfs(AGraph* G, int u, int v)
{
visited[u] = true;
path.push_back(u);
if (u == v)
{
for (auto pa : path) cout << pa << ' ';
puts("");
return;
}
auto p = G->adjlist[u].firstarc;
while (p)
{
auto w = p->adjvex;
if (!visited[w]) dfs(G, w, v);
p = p->nextarc;
}
path.pop_back();
visited[u] = false;
}
3. 求不带权无向连通图G中距离顶点u最远的一个结点
// 注意题目条件:无向连通图
// 无向连通图中,每2个结点之间都存在着路径,bfs能找到距离顶点u最远的点
int bfs(AGraph* G, int u)
{
bool visited[MaxSize];
memset(visited, false, sizeof visited);
queue<int> q;
vector<int> res;
q.push(u);
visited[u] = true;
while (q.size())
{
auto t = q.front();
q.pop();
res.push_back(t);
auto p = G->adjlist[t].firstarc;
while (p)
{
auto v = p->adjvex;
if (!visited[v])
{
q.push(v);
visited[v] = true;
}
p = p->nextarc;
}
}
return res[res.size() - 1];
}
4. 打印有向图G中的根结点
根结点定义:结点r到G中每个结点都有路径,则称r为G的根结点
bool visited[MaxSize];
int cnt; // 表示dfs访问过的结点数,访问1个结点,cnt + 1
void has_root(AGraph* G)
{
for (int i = 0; i < G->vexnum; i ++)
{
memset(visited, false, sizeof visited);
cnt = 0;
dfs(G, i);
if (cnt == G->vexnum) cout << i << ' ';
}
}
void dfs(AGraph* G, int u)
{
visited[u] = true;
cnt ++;
auto p = G->adjlist[u].firstarc;
while (p)
{
auto v = p->adjvex;
if (!visited[v]) dfs(G, v);
p = p->nextarc;
}
}
二、连通与环问题
1. 判断无向图G是否是一棵树
注意:树的特点是含有n - 1条边的连通图,n为顶点个数
// 重点:图的边数 = 其结点数-1,图连通
// n个结点:设置一个计数器vn,用以记录访问过的结点
// n - 1条边:设置一个计数器en,用以记录访问过的边
// 连通:可以比较dfs之后访问过的结点数vn和图中顶点数vexnum是否相等判别
bool visited[MaxSize];
int vn = 0, en = 0;
bool is_tree(AGraph* G)
{
memset(visited, false, sizeof visited);
dfs(G, 1);
if (vn == G->vexnum && en / 2 == G->vexnum - 1) return true;
return false;
}
void dfs(AGraph* G, int u)
{
visited[u] = true;
vn ++;
auto p = G->adjlist[u].firstarc;
while (p)
{
en ++;
auto v = p->adjvex;
if (!visited[v]) dfs(G, v);
p = p->nextarc;
}
}
2. 判断有向图中是否存在经过给定顶点u的环
// 感觉有点难理解,直接背吧。。天勤P232
/* 对于有向图,如果从有向图的某个顶点u出发的遍历,
在DFS(u)结束之前出现了一条从顶点v指向u的边,则此有向图必定存在环*/
bool visited[MaxSize];
bool has_circle(AGraph* G, int u)
{
memset(visited, false, sizeof visited);
if (dfs(G, u)) return true;
return false;
}
bool dfs(AGraph* G, int u)
{
bool flag; // 如果有环,flag = true
visited[u] == true;
auto p = G->adjlist[u].firstarc;
while (p)
{
auto v = p->adjvex;
if (visited[v]) return true;
else flag = dfs(G, v);
if (flag == true) return true;
visited[v] = false;
// 最后一句的含意,我的理解是:
// 递归完一整条路径(最后一个顶点已经没有出边了)时,
// 会从最后一个点依次往前抹除遍历痕迹,直到某个点i还存在未访问的邻接点
// 然后从该点i一边继续往下dfs新的路径,一边对新加入路径的顶点进行访问查询
}
return false;
}
三、结点度数问题
1. 求有向图G中顶点k的入度
int cal_degree(AGraph* G, int k)
{
int degree = 0; // 记录结点k的入度
for (int i = 0; i < G->vexnum; i ++)
{
auto p = G->adjlist[i].firstarc;
while (p)
{
auto v = p->adjvex;
if (v == k)
{
degree ++;
break;
}
}
p = p->nextarc;
}
return degree;
}
2. 判断无向连通图G是否存在EL路径
EL路径:当G中度为奇数的顶点个数为不大于2的偶数时,就会存在此路径
// 重点:
// 对于无向图,邻接矩阵的每一行(列)中,非零元素的个数等于本行(列)对应顶点的度
// 对于有向图,邻接矩阵的每一行(列)中,非零元素的个数等于本行(列)对应顶点的出(入)度
// 方法1:采用邻接矩阵存储(原题)
int dist[MaxSize]; // 记录无向图中各顶点的度数
bool has_EL(MGraph G)
{
memset(dist, 0, sizeof dist);
for (int i = 0; i < G.vexnum; i ++)
for (int j = 0; j < G.vexnum; j ++)
dist[i] += G.edges[i][j];
int cnt = 0;
for (int i = 0; i < G.vexnum; i ++)
if (dist[i] % 2) cnt ++;
if (cnt == 0 || cnt == 2) return true;
return false;
}
// 重点:
// 对于无向图,邻接表中,一个顶点的度数等于该顶点的邻接边条数
// 对于有向图,邻接表中,一个顶点的出度数等于该顶点的邻接边条数
// 而一个顶点的入度数就不是这样了
// 方法2:采用邻接表存储(发散)
int dist[MaxSize]; // 记录无向图中各顶点的度数
bool has_EL(MGraph G)
{
memset(dist, 0, sizeof dist);
for (int i = 0; i < G.vexnum; i ++)
{
auto p = G.adjlist[i].firstarc;
while (p)
{
dist[i] ++;
p = p->nextarc;
}
}
int cnt = 0;
for (int i = 0; i < G.vexnum; i ++)
if (dist[i] % 2) cnt ++;
if (cnt == 0 || cnt == 2) return true;
return false;
}
最后——杂七杂八
1. 写出图的深度优先遍历DFS算法的非递归形式
bool visited[MaxSize];
void DfsTraverse(AGraph* G) // 用的指针
{
memset(visited, false, sizeof visited);
for (int i = 0; i < G->vexnum; i ++)
if (!visited[i]) dfs(G, i);
}
// 图的dfs类似树的preorder
void dfs(AGraph* G, int u)
{
stack<int> s;
s.push(u);
visited[u] = true;
while (s.size())
{
auto t = s.top();
s.pop();
visit(t);
auto p = G->adjlist[t].firstarc;
while (p)
{
auto v = p->adjvex;
if (!visited[v])
{
s.push(v);
visited[v] = true;
}
p = p->nextarc;
}
}
}
2. 写出无向图G从邻接表表示转换成邻接矩阵表示的算法
void AGraph_to_MGraph(AGraph* G1, MGraph & G2)
{
// 将邻接矩阵置0
for (int i = 0; i < G2.vexnum; i ++)
for (int j = 0; j < G2.vexnum; j ++)
G2.edges[i][j] = 0;
// 扫描邻接表给邻接矩阵赋值
for (int i = 0; i < G1->vexnum; i ++)
{
auto p = G1->adjlist[i].firstarc;
while (p)
{
auto j = p->adjvex;
G2.edges[i][j] = 1;
p = p->nextarc;
}
}
// 边数和顶点数别忘了赋值给邻接矩阵
G2.vexnum = G1->vexnum;
G2.edgenum = G2->edgenum;
}