图相关文章:
1. 图的建立 - 邻接矩阵与邻接表https://blog.csdn.net/m15253053181/article/details/127552328?spm=1001.2014.3001.55012. 图的遍历 - DFS与BFShttps://blog.csdn.net/m15253053181/article/details/127558368?spm=1001.2014.3001.55013. 顶点度的计算https://blog.csdn.net/m15253053181/article/details/127558599?spm=1001.2014.3001.55014. 最小生成树 - Prim与Kruskalhttps://blog.csdn.net/m15253053181/article/details/127589852?spm=1001.2014.3001.55015. 单源最短路径 - Dijkstra与Bellman-Fordhttps://blog.csdn.net/m15253053181/article/details/127630356?spm=1001.2014.3001.55016. 多源最短路径 - Floydhttps://blog.csdn.net/m15253053181/article/details/128039852?spm=1001.2014.3001.55017. 拓扑排序AOV网https://blog.csdn.net/m15253053181/article/details/128042358?spm=1001.2014.3001.5501
目录
搜索算法
寻找到某个特定目标与遍历整张图本质上是相同的,只是算法结束的条件不同。因此下面以遍历整张图为例进行讲解。
1 深度优先搜索DFS
1.1 案例引入
我们从一个实例来引入深度优先的算法思想。
首先,这里有一个迷宫,我们要怎么去寻找到出口呢?相信不少人都听说过“右手原则”——一直走右手边,没路了就返回。这种策略,便蕴藏着深度优先搜索的思想。
将上述策略以文字描述:
① 路分叉时,任选一条边深入;
② 无边可走时,后退一步找新边;
③ 找到边,从新边继续深入。
注意,在整个流程中,第②步是十分核心的:
② 无边可走时,后退一步找新边;
这其中蕴含着回溯的思想,也是DFS的本质所在。
接下来我们以一个动图来完整展示以下DFS的整体流程:
1.2 DFS含义
从上述案例不难看出DFS的含义,总结如下:
深度优先搜索DFS是利用回溯的思想,将某条路走的很深,这是DFS的实质。
1.3 代码实现
图的DFS类似于树的前序遍历,分为递归与非递归的方法。
其中,非递归实现流程操作如下:
① 起点入栈,标记为已入栈;
② 出栈,访问出栈结点,标记为已访问。再将与出栈结点相邻接的结点(未访问过且未入栈)入栈,标记为已入栈;
③ 重复②,直至所有结点均入栈。
④ 将所有元素逐个出栈,访问。
以下代码均有详细注释,不再赘述。
(1)递归实现 - 邻接矩阵
/* 递归DFS_MG */
void DFS_MG_Rec(MGraph MG, int vertex)
{
int i;
int *visited = (int *)malloc(sizeof(int) * MG->numV); // 创建访问数组
for (i = 0; i < MG->numV; i++) // 初始化为未访问
visited[i] = 0;
printf("DFS of MG(recursive) result:\n");
DFS_MG_Rec_part(MG, vertex, visited); // 对邻接矩阵进行递归DFS
printf("\n");
}
/* 递归DFS_MG的递归部分 */
void DFS_MG_Rec_part(MGraph MG, int vertex, int *visited)
{
int i;
visited[vertex] = 1; // 标记为已访问
printf("%c ", MG->nameV[vertex]);
for (i = 0; i < MG->numV; i++) // 对于V的每个邻接点(未访问)递归调用
if (MG->dis[vertex][i] != -1)
if (!visited[i])
DFS_MG_Rec_part(MG, i, visited);
}
(2)非递归实现 - 邻接矩阵
/* 非递归DFS_MG */
void DFS_MG_nonRec(MGraph MG, int vertex)
{
int i;
int out;
Stack stack = initSqStack();
int *visited = (int *)malloc(sizeof(int) * MG->numV); // 创建访问数组
int *inStack = (int *)malloc(sizeof(int) * MG->numV); // 创建入栈数组(记录顶点是否入过栈)
for (i = 0; i < MG->numV; i++) // 初始化
{
visited[i] = 0;
inStack[i] = 0;
}
SqPush(stack, vertex); // 起点入栈
inStack[vertex] = 1;
while (stack->top != stack->base) // 栈非空
{
out = SqPop(stack); // 出栈
visited[out] = 1; // 标记已访问
printf("%c ", MG->nameV[out]);
for (i = 0; i < MG->numV; i++) // 将相邻顶点(未访问)压入栈中
{
if (MG->dis[out][i] != -1 && !visited[i] && !inStack[i])
{
SqPush(stack, i);
inStack[i] = 1;
}
}
}
printf("\n");
}
(3)递归实现 - 邻接表
/* 递归DFS_L */
void DFS_L_Rec(AdjList L, int vertex)
{
int i;
int *visited = (int *)malloc(sizeof(int) * L->numV); // 创建访问数组
for (i = 0; i < L->numV; i++) // 初始化为未访问
visited[i] = 0;
printf("DFS of AdjList(recursive) result:\n");
DFS_L_Rec_part(L, vertex, visited); // 对邻接矩阵进行递归DFS
printf("\n");
}
/* 递归DFS_L的递归部分 */
void DFS_L_Rec_part(AdjList L, int vertex, int *visited)
{
int i;
LGNode node = L->list[vertex].next;
visited[vertex] = 1; // 标记为已访问
printf("%c ", L->nameV[vertex]);
while (node) // 对于V的每个邻接点(未访问)递归调用
{
if (!visited[node->v])
DFS_L_Rec_part(L, node->v, visited);
node = node->next;
}
}
(4)非递归实现 - 邻接表
/* 非递归DFS_L */
void DFS_L_nonRec(AdjList L, int vertex)
{
int i;
int out;
LGNode node = NULL;
Stack stack = initSqStack();
int *visited = (int *)malloc(sizeof(int) * L->numV); // 创建访问数组
int *inStack = (int *)malloc(sizeof(int) * L->numV); // 创建入栈数组(记录顶点是否入过栈)
for (i = 0; i < L->numV; i++) // 初始化
{
visited[i] = 0;
inStack[i] = 0;
}
SqPush(stack, vertex); // 起点入栈
inStack[vertex] = 1;
while (stack->top != stack->base) // 栈非空
{
out = SqPop(stack); // 出栈
visited[out] = 1; // 标记已访问
printf("%c ", L->nameV[out]);
node = L->list[out].next;
while (node) // 对于V的每个邻接点(未访问)递归调用
{
if (!visited[node->v] && !inStack[node->v])
{
SqPush(stack, node->v);
inStack[node->v] = 1;
}
node = node->next;
}
}
printf("\n");
}
2 广度优先搜索BFS
2.1 案例引入
让我们想象一下这个场景:一滴水滴入水面,关注其涟漪的变化。
显然,水滴落入水面所激起的涟漪会向相邻区域逐渐扩散 。
这种依次扩散的现象,其实就是广度优先搜索BFS的思想。
我们再以一张动图来形象地理解一下。
2.2 BFS含义
从上述案例不难看出BFS的含义,总结如下:
广度优先搜索BFS是尽可能地横向上进行搜索,在处理某顶点时,一次性发现其所有相邻顶点,这是BFS是核心思想。
2.3 代码实现
BFS的思想我们已经清楚,具体要怎么实现呢?难点在于这一条:
在处理某顶点时,一次性发现其所有相邻顶点
如何解决这个问题呢?
我们发现,这种访问的特点可以用队列的数据结构完美解决。
以队列方式进行访问,具体操作如下:
① 起点入队列,标记为已入队列。
② 出队,访问出队结点,标记为已访问。将与出队结点相邻接的所有结点(未访问过且未入队列)入队,标记为已入队。
③ 重复②,直至所有结点已入队。
④ 逐个出队,访问。
以下代码均有详细注释,不再赘述。
(1)队列实现 - 邻接矩阵
/* BFS_MG 队列 */
void BFS_MG(MGraph MG, int vertex)
{
int i;
int out;
Queue queue = createQueue();
int *visited = (int *)malloc(sizeof(int) * MG->numV); // 创建访问数组
int *inQueue = (int *)malloc(sizeof(int) * MG->numV); // 创建入队数组(记录顶点是否入过队列)
for (i = 0; i < MG->numV; i++) // 初始化
{
visited[i] = 0;
inQueue[i] = 0;
}
enqueue(queue, vertex); // 起点入队
inQueue[vertex] = 1;
while (queue->rear) // 队列非空
{
out = dequeue(queue); // 出队
visited[out] = 1; // 标记已访问
printf("%c ", MG->nameV[out]);
for (i = 0; i < MG->numV; i++) // 将相邻顶点(未访问)进入队列
{
if (MG->dis[out][i] != -1 && !visited[i] && !inQueue[i])
{
enqueue(queue, i);
inQueue[i] = 1;
}
}
}
printf("\n");
}
(2)队列实现 - 邻接表
/* BFS_L 队列 */
void BFS_L(AdjList L, int vertex)
{
int i;
int out;
LGNode node = NULL;
Queue queue = createQueue();
int *visited = (int *)malloc(sizeof(int) * L->numV); // 创建访问数组
int *inQueue = (int *)malloc(sizeof(int) * L->numV); // 创建入队数组(记录顶点是否入过队列)
for (i = 0; i < L->numV; i++) // 初始化
{
visited[i] = 0;
inQueue[i] = 0;
}
enqueue(queue, vertex); // 起点入队
inQueue[vertex] = 1;
while (queue->rear) // 队列非空
{
out = dequeue(queue); // 出栈
visited[out] = 1; // 标记已访问
printf("%c ", L->nameV[out]);
node = L->list[out].next;
while (node) // 对于V的每个邻接点(未访问)递归调用
{
if (!visited[node->v] && !inQueue[node->v])
{
enqueue(queue, node->v);
inQueue[node->v] = 1;
}
node = node->next;
}
}
printf("\n");
}