DFS-深度优先遍历
BFS广度优先搜索树遍历
动画可视化——Prim 算法
动画可视化——Kruskal 算法
最短路径问题之Floyd算法 ——邻接矩阵表示
最短路径问题之Floyd算法
最短路径问题之Dijkstra算法动画可视化
动画可视化——图的拓扑排序
数据结构课程只探讨“简单图”
- 若**G是连通图,则最少有n-1条边**
- 若G是非连通图,则最多可能有
条边
- n个顶点的树,必有n-1条边。
- n个顶点的图,若|E|>n-1,则一定有回路
- **第i个结点的度=第i行(或第i列)的非零元素个数**
- **第i个结点的出度=第i行的非零元素个数**
- **第i个结点的入度=第i列的非零元素个数**
- **第i个结点的度=第i行、第i列的非零元素个数之和**
#define MaxVertexNum 100 //顶点数目的最大值
#define INFINITY 最大的int值 //宏定义常量"无穷"
typedef char VertexType; //顶点的数据类型
typedef int EdgeType; //带权图中边上权值的数据类型
typedef struct{
VertexType Vex[MaxVertexNum]; //顶点
EdgeType Edge[MaxVertexNum][MaxVertexNum]; //边的权
int vexnum, arcnum; //图的当前顶点数和弧数
}MGraph;
使用邻接表中的意义:
关系:
BFS广度优先搜索树遍历
- 若队列非空,队头元素出队并访问,同时将该元素的孩字依次入队
- 重复第二步直到队列为空
- 树不存在“回路”,搜索相邻的结点时,不可能搜到已经访问过的结点
- 图搜索相邻的顶点时,有可能搜到已经访问过的顶点
- NextNeighbor(G,x,y):假设图G中顶点y是顶点x的一个邻接点,返回除y之外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,则返回-1。
- 使用上面两个基本操作
//广度优先遍历
void BFS(Graph G, int v){ //从顶点v出发,广度优先遍历图G
visit(v); //访问初始顶点v
visited[v] = TRUE; //对v做已访问标记
Enqueue(Q, v); //顶点v入队列Q
while (!isEmpty(Q)){
DeQueue(Q, v); //顶点v出队列
for (w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w))
//检测v所有邻接点
if (!visited[w]){ //w为v的尚未访问的邻接顶点
visit(w); //访问顶点w
visited[w] = TRUE;//对w做已访问标记
EnQueue(Q, w); //顶点w入队列
}//if
}//while
}
如果是非连通图,则无法遍历完所有结点
- 访问 |V| 个顶点需要O(|V|)的时间
- **查找每个顶点的邻接点都需要O(|V|)的时间,而总共有|V|个顶点**
- 时间复杂度= **O(|V|^2)**
- 访问 |V| 个顶点需要O(|V|)的时间
- 查找各个顶点的邻接点共需要O(|E|)的时间
- 时间复杂度= **O(|V|+|E|)**
广度优先生成树:
DFS-深度优先遍历
- 每当访问一个结点的时候,要检查是否还有与当前结点相邻的且没有被访问过的结点,如果有的话就往下一层钻。
- 来**自函数调用栈**,最坏情况,递归深度为O(|V|)
- 最好情况,O(1)
- 时间复杂度**=访问各结点所需时间+探索各条边所需时间**
- 邻接矩阵存储的图:
- 访问 |V| 个顶点需要O(|V|)的时间
- 查找每个顶点的邻接点都需要O(|V|)的时间,而总共有|V|个顶点
- **时间复杂度**= O(|V|^2)
- 邻接表存储的图:
- 访问 |V| 个顶点需要O(|V|)的时间
- 查找各个顶点的邻接点共需要O(|E|)的时间
- **时间复杂度= O(|V|+|E|)**
动画可视化——Prim 算法
动画可视化——Kruskal 算法
#include <stdio.h>
#include <limits.h>
#define MAX_VERTEX_NUM 5 // 顶点数量
#define INFINITY INT_MAX // 无穷大
// 邻接矩阵存储图
typedef struct MGraph {
int arc[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
int vexnum;
} MGraph;
// 初始化图
void InitGraph(MGraph *G) {
G->vexnum = MAX_VERTEX_NUM;
// 初始化邻接矩阵,这里需要根据实际图结构填写,假设示例图结构
for (int i = 0; i < MAX_VERTEX_NUM; i++) {
for (int j = 0; j < MAX_VERTEX_NUM; j++) {
if (i == j)
G->arc[i][j] = 0;
else
G->arc[i][j] = INFINITY;
}
}
// 假设从图中已知的边(需根据实际图补充)
G->arc[0][1] = 10;
G->arc[0][4] = 5;
}
// Dijkstra算法
void Dijkstra(MGraph G, int v0, int final[], int dist[], int path[]) {
// 初始化
for (int v = 0; v < G.vexnum; v++) {
final[v] = 0;
dist[v] = G.arc[v0][v];
if (dist[v] < INFINITY)
path[v] = v0;
else
path[v] = -1;
}
final[v0] = 1;
dist[v0] = 0;
// 主循环
for (int i = 1; i < G.vexnum; i++) {
int min = INFINITY;
int k = v0;
// 找未确定最短路径且dist最小的顶点
for (int w = 0; w < G.vexnum; w++) {
if (!final[w] && dist[w] < min) {
min = dist[w];
k = w;
}
}
final[k] = 1;
// 更新邻接顶点
for (int w = 0; w < G.vexnum; w++) {
if (!final[w] && G.arc[k][w] < INFINITY && dist[k] + G.arc[k][w] < dist[w]) {
dist[w] = dist[k] + G.arc[k][w];
path[w] = k;
}
}
}
}
// 输出结果
void PrintResult(int final[], int dist[], int path[], int v0) {
printf("顶点\t是否找到最短路径\t最短路径长度\t前驱顶点\n");
for (int i = 0; i < MAX_VERTEX_NUM; i++) {
printf("V%d\t", i);
if (final[i])
printf("是\t\t");
else
printf("否\t\t");
printf("%d\t\t", dist[i] == INFINITY ? -1 : dist[i]);
printf("%d\n", path[i]);
}
}
int main() {
MGraph G;
InitGraph(&G);
int final[MAX_VERTEX_NUM];
int dist[MAX_VERTEX_NUM];
int path[MAX_VERTEX_NUM];
Dijkstra(G, 0, final, dist, path);
PrintResult(final, dist, path, 0);
return 0;
}
- 循环遍历所有顶点,找**到还没确定最短路径,且dist 最小的顶点Vi,令final[i]=ture**。
- 并检查所有邻接自Vi 的顶点,对于邻接自Vi 的顶点 Vj ,**若 final[j]==false 且 dist[i]+arcs**[**i**](https://blog.csdn.net/qq_35787977/article/details/125579106#) **< dist[j],则令 dist[j]=dist[i]+arcs**[**i**](https://blog.csdn.net/qq_35787977/article/details/125579106#)**; path[j]=i。**
- 注:arcs[i](https://blog.csdn.net/qq_35787977/article/details/125579106#)表示Vi 到Vj 的弧的权值
最短路径问题之Floyd算法 ——邻接矩阵表示
- 初始:不允许在其他顶点中转,最短路径是?
- 0:若允许在 V0 中转,最短路径是?
- 1:若允许在 V0、V1 中转,最短路径是?
- 2:若允许在 V0、V1、V2 中转,最短路径是?
- …
- n-1:若允许在 V0、V1、V2 …… Vn-1 中转,最短路径是?
//……准备工作,根据图的信息初始化矩阵 A 和 path(如上图)
for (int k=0; k<n; k++){ //考虑以 vk 作为中转点
for(int i=0; i<n; i++) { //遍历整个矩阵,i为行号,j为列号
for (int j=0; j<n; j++){
if (A[i][j]>A[i][k]+A[k][j]){ //以 vk 为中转点的路径更短
A[i][j]=A[i][k]+A[k][j]; //更新最短路径长度
path[i][j]=k; //中转点
}
}
}
}
转换为DAG前:
转换为DAG后:
解决方法——分层体系合并法:
动画可视化——图的拓扑排序
拓扑排序:
- **若顶点A在序列中排在顶点B的前面,则在图中不存在从顶点B到顶点A的路径。**
- 拓扑排序是对有向无环图的顶点的一种排序,它使得若存在一条从顶点A到顶点B的路径,则在排序中顶点B出现在顶点A的后面。
- 从AOV网中选择一个没有前驱(入度为0)的顶点并输出。
- 从网中删除该顶点和所有以它为起点的有向边。
- 重复前面的步骤直到当前的AOV网为空或当前网中不存在无前驱的顶点为止
- **从网中删除该顶点和所有以它为终点的有向边。**
- **重复上面步骤直到当前的AOV网为空。**
- 按拓扑排序序列,依次求各个顶点的 ve(k):
- ve(源点) = 0
- **ve(k) = Max{ve(j) + Weight(vj, vk)}, vj为vk 的任意前驱**
- 按逆拓扑排序序列,依次求各个顶点的 vl(k):
- **vl(汇点) = ve(汇点)**
- **vl(k) = Min{vl(j) - Weight(vk, vj)} , vj为vk的任意后继**
- **若边<vk, vj>表示活动ai,则有e(i) = ve(k)**
- **若边<vk, vj>表示活动ai,则有l(i) = vl(j) - Weight(vk, vj)**
- **d(i) = l(i) - e(i)**