【经典算法实现 36】图的最短路径计算(广度优先遍历法 和 深度优先遍历法)
本文接着前文,如果对网的创建、深度优先遍历算法或广度优先遍历算法 有疑问的同学,可参考下前文:
《【经典算法实现 33】图的创建 — 有向邻接矩阵法 与 网》
《【经典算法实现 34】带权有向邻接矩阵(网的遍历)— 深度优先遍历算法》
《【经典算法实现 35】带权有向邻接矩阵(网的遍历)— 广度优先遍历算法》
老样子,先上图:
问题来了,请计算从 A 到 G 的最短路径。
我们看得出来有很多条路径,比如 A->B->D->E->G
或 A-C->D->E->G
,到度哪条最短呢?
这个该怎么实现呢?
答:
最短路很多六种方法:
- 广度优先算法
- 深度优先算法
- 弗洛伊德算法
- 迪杰斯特拉算法
- Bellman-Ford算法
- SPFA(Shortest Path Faster Algorithm)
本文目前讲前两种方法:广度优先算法,深度优先算法
一、最短路径算法
1.1 通过广度 / 深度优先 实现思路
广度 / 深度 优先遍历算法,实现了让我们能够不重复的访问到图上的每一个结点。
最短路径算法实现思路(默认以A 为起点):
- 创建一个二维数组保存某个节点到
A
的距离,比如{'A', 0}
,说明'A'
到'A'
的最短路径为 0 - 在每次遍历节点时,都计算一下上个节点到当前节点的距离,加上上个节点到
A
的最短路径,如果存在更短,则更新数组
1.2 广度优先遍历实现计算最短路径代码
// 网矩阵广度遍历算法
void BFS_Matrix()
{
char cur, next;
char find_list[Node_Num], *f = find_list, i, j;
// 分配二级指针内存,用于保存最短路径,及父节点
// sh[0]: 要求最短路径的节点
// sh[1]: 前一个节点
// sh[2]: 当前到A的最短距离
int **sh = (int **)malloc(sizeof(int *) * Node_Num);
for(i = 0; i<Node_Num; i++){
*(sh+i) = (int *)malloc(sizeof(int) * 3);
(*(sh+i))[0] = 0;
(*(sh+i))[1] = 0;
(*(sh+i))[2] = INT_MAX; // 初始距离是最大
}
// 先将 'A' 入队列
Push_Node(pNode[0]);
*f++ = pNode[0]; // 记录 A 已访问过
sh[0][0] = pNode[0];
sh[0][1] = pNode[0];
// 只要栈头指针指向的数据不为0,说明队列 不为空
while(list_head->node != 0){
cur = Pop_Queue(); // 出队列
// 找到下一个未访问过的节点
for(i =0; i<Node_Num ; i++){
if( sNode[cur-'A'][i] > 0){
next = 'A' + i;
printf("\n当前%c->%c = %d",cur, next, sNode[cur-'A'][i]);
// 找到节点,判断新节点是否访问过,如果没访问过就放入队列
for(j=0; j<Node_Num; j++){
if(find_list[j] == next)
break;
// 如果到最后一个还不匹配,说明没访问过
if( j == Node_Num-1){
Push_Node(next);
*f++ = next; // 记录 next已访问过
}
}
printf("\n当前节点%c 的距离 %d, 父节点%c 最短距离:%d\n", next, sNode[cur-'A'][next-'A'],cur, sh[cur-'A'][2]);
// 更新最短路径 = sh[cur-'A'][2] + sNode[cur-'A'][next-'A']
if(next != 'A'){
if( sh[cur-'A'][2] == INT_MAX ){
sh[next-'A'][0] = next;
sh[next-'A'][1] = cur;
sh[next-'A'][2] = sNode[cur-'A'][next-'A'];
}else if(sNode[cur-'A'][next-'A'] + sh[cur-'A'][2] < sh[next-'A'][2]){
sh[next-'A'][0] = next;
sh[next-'A'][1] = cur;
sh[next-'A'][2] = sNode[cur-'A'][next-'A'] + sh[cur-'A'][2];
}
}
}
}
}
printf("\n\n网矩阵广度遍历结束, 遍历顺序为:\n");
for(i=0; i<Node_Num; i++)
printf("%c--> ", find_list[i]);
printf("\n\n");
printf("\n\n最短距离为:\n");
for(i=0; i<Node_Num; i++)
printf("当前结点:%c,父结点:%c, 当前到'A'最短距离:%d\n", sh[i][0], sh[i][1], sh[i][2]==INT_MAX?0:sh[i][2]);
printf("\n\n");
}
运行结果为:
New_List_Queue -- size=10
列表创建完毕
网矩阵创建完毕
A B C D E F G
A -1 5 1 -1 -1 -1 -1
B 5 -1 -1 1 -1 -1 -1
C -1 -1 -1 3 -1 -1 -1
D -1 -1 -1 -1 6 4 -1
E -1 -1 -1 -1 -1 -1 1
F -1 -1 2 -1 -1 -1 -1
G -1 -1 -1 -1 -1 -1 -1
网矩阵打印完毕
插入元素 A, list_head(00A80DC0), list_tail(00A80DC0)
弹出元素 A
当前A->B = 5
插入元素 B, list_head(00A80DD8), list_tail(00A80DD8)
当前节点B 的距离 5, 父节点A 最短距离:65535
当前A->C = 1
插入元素 C, list_head(00A80DD8), list_tail(00A80DF0)
当前节点C 的距离 1, 父节点A 最短距离:65535
弹出元素 B
当前B->A = 5
当前节点A 的距离 5, 父节点B 最短距离:5
当前B->D = 1
插入元素 D, list_head(00A80DF0), list_tail(00A80E08)
当前节点D 的距离 1, 父节点B 最短距离:5
弹出元素 C
当前C->D = 3
当前节点D 的距离 3, 父节点C 最短距离:1
弹出元素 D
当前D->E = 6
插入元素 E, list_head(00A80E20), list_tail(00A80E20)
当前节点E 的距离 6, 父节点D 最短距离:4
当前D->F = 4
插入元素 F, list_head(00A80E20), list_tail(00A80E38)
当前节点F 的距离 4, 父节点D 最短距离:4
弹出元素 E
当前E->G = 1
插入元素 G, list_head(00A80E38), list_tail(00A80E50)
当前节点G 的距离 1, 父节点E 最短距离:10
弹出元素 F
当前F->C = 2
当前节点C 的距离 2, 父节点F 最短距离:8
弹出元素 G
网矩阵广度遍历结束, 遍历顺序为:
A--> B--> C--> D--> E--> F--> G-->
最短距离为:
当前结点:A,父结点:A, 当前到'A'最短距离:0
当前结点:B,父结点:A, 当前到'A'最短距离:5
当前结点:C,父结点:A, 当前到'A'最短距离:1
当前结点:D,父结点:C, 当前到'A'最短距离:4
当前结点:E,父结点:D, 当前到'A'最短距离:10
当前结点:F,父结点:D, 当前到'A'最短距离:8
当前结点:G,父结点:E, 当前到'A'最短距离:11
1.3 深度优先遍历实现计算最短路径代码
// 网矩阵深度遍历算法
void DFS_Matrix()
{
char cur, next;
char find_list[Node_Num], *f = find_list, flag, i, j;
// 分配二级指针内存,用于保存最短路径,及父节点
int **sh = (int **)malloc(sizeof(int *) * Node_Num);
for(i = 0; i<Node_Num; i++){
*(sh+i) = (int *)malloc(sizeof(int) * 3);
(*(sh+i))[0] = 0;
(*(sh+i))[1] = 0;
(*(sh+i))[2] = INT_MAX; // 初始距离是最大
}
// 先将 'A' 入栈
Push_Node(pNode[0]);
*f++ = pNode[0]; // 记录 A 已访问过
sh[0][0] = pNode[0];
sh[0][1] = pNode[0];
// 只要栈头指针指向的数据不为0,说明栈不为空
while(list_head->node != 0){
cur = Pop_Stack(); // 出栈
// 找到下一个未访问过的节点
for(i =0, flag=0; i<Node_Num && flag==0; i++){
if( sNode[cur-'A'][i] > 0){
next = 'A' + i;
//printf("\n当前%c->%c = %d, flag=%d",cur, next, sNode[cur-'A'][i], flag);
// 找到节点,判断新节点是否访问过,如果访问过返回1
for(j=0; j<Node_Num && flag==0; j++){
if(find_list[j] == next){
break;
}
if(j == Node_Num-1)
flag = 1;
}
}
}
if(flag == 1){
Push_Node(cur); // 将当前节点入栈
Push_Node(next); // 将新节点入栈
*f++ = next; // 记新节点已访问过
printf("\n当前节点%c 的距离 %d, 父节点%c 最短距离:%d\n", next, sNode[cur-'A'][next-'A'],cur, sh[cur-'A'][2]);
}else
printf(", 未找到有效节点,跳过",cur);
//针对当前节点,遍历所有可连接的点
for( i=0; i<Node_Num; i++){
if( sNode[cur-'A'][i] > 0){
next = 'A' + i;
if(next != 'A'){
if( sh[cur-'A'][2] == INT_MAX ){
sh[next-'A'][0] = next;
sh[next-'A'][1] = cur;
sh[next-'A'][2] = sNode[cur-'A'][next-'A'];
}else if(sNode[cur-'A'][next-'A'] + sh[cur-'A'][2] < sh[next-'A'][2]){
sh[next-'A'][0] = next;
sh[next-'A'][1] = cur;
sh[next-'A'][2] = sNode[cur-'A'][next-'A'] + sh[cur-'A'][2];
}
}
}
}
}
printf("\n\n网矩阵深度遍历结束, 遍历顺序为:\n");
for(i=0; i<Node_Num; i++)
printf("%c--> ", find_list[i]);
printf("\n\n");
printf("\n\n最短距离为:\n");
for(i=0; i<Node_Num; i++)
printf("当前结点:%c,父结点:%c, 当前到'A'最短距离:%d\n", sh[i][0], sh[i][1], sh[i][2]==INT_MAX?0:sh[i][2]);
printf("\n\n");
}
运行结果为:
New_List_Queue -- size=10
列表创建完毕
网矩阵创建完毕
A B C D E F G
A -1 5 1 -1 -1 -1 -1
B 5 -1 -1 1 -1 -1 -1
C -1 -1 -1 3 -1 -1 -1
D -1 -1 -1 -1 6 4 -1
E -1 -1 -1 -1 -1 -1 1
F -1 -1 2 -1 -1 -1 -1
G -1 -1 -1 -1 -1 -1 -1
网矩阵打印完毕
插入元素 A, list_head(006C0DC0), list_tail(006C0DC0)
弹出元素 A
插入元素 A, list_head(006C0DC0), list_tail(006C0DC0)
插入元素 B, list_head(006C0DC0), list_tail(006C0DD8)
当前节点B 的距离 5, 父节点A 最短距离:65535
弹出元素 B
插入元素 B, list_head(006C0DC0), list_tail(006C0DD8)
插入元素 D, list_head(006C0DC0), list_tail(006C0DF0)
当前节点D 的距离 1, 父节点B 最短距离:5
弹出元素 D
插入元素 D, list_head(006C0DC0), list_tail(006C0DF0)
插入元素 E, list_head(006C0DC0), list_tail(006C0E08)
当前节点E 的距离 6, 父节点D 最短距离:6
弹出元素 E
插入元素 E, list_head(006C0DC0), list_tail(006C0E08)
插入元素 G, list_head(006C0DC0), list_tail(006C0E20)
当前节点G 的距离 1, 父节点E 最短距离:12
弹出元素 G, 未找到有效节点,跳过
弹出元素 E, 未找到有效节点,跳过
弹出元素 D
插入元素 D, list_head(006C0DC0), list_tail(006C0DF0)
插入元素 F, list_head(006C0DC0), list_tail(006C0E08)
当前节点F 的距离 4, 父节点D 最短距离:6
弹出元素 F
插入元素 F, list_head(006C0DC0), list_tail(006C0E08)
插入元素 C, list_head(006C0DC0), list_tail(006C0E20)
当前节点C 的距离 2, 父节点F 最短距离:10
弹出元素 C, 未找到有效节点,跳过
弹出元素 F, 未找到有效节点,跳过
弹出元素 D, 未找到有效节点,跳过
弹出元素 B, 未找到有效节点,跳过
弹出元素 A, 未找到有效节点,跳过
网矩阵深度遍历结束, 遍历顺序为:
A--> B--> D--> E--> G--> F--> C-->
最短距离为:
当前结点:A,父结点:A, 当前到'A'最短距离:0
当前结点:B,父结点:A, 当前到'A'最短距离:5
当前结点:C,父结点:A, 当前到'A'最短距离:1
当前结点:D,父结点:C, 当前到'A'最短距离:4
当前结点:E,父结点:D, 当前到'A'最短距离:10
当前结点:F,父结点:D, 当前到'A'最短距离:8
当前结点:G,父结点:E, 当前到'A'最短距离:13
二、完整代码
/*
横轴为:终点 纵轴为:起始点
A->B D->C 在数组中存储为sNode[A][B],sNode[D][C]
\ A B C D
A 0 1 0 0
B 0 0 0 0
C 0 0 0 0
D 0 0 1 0
*/
#include <stdio.h>
#define Node_Num 7 // 顶点的数量
char pNode[Node_Num]={'A', 'B', 'C', 'D', 'E', 'F', 'G'}; // 用于保存顶点的值
int sNode[Node_Num][Node_Num]; // 用于表示各边的关系
#define INT_MAX 65535
#define sNode_Num 9 // 9条边
const int s_char[sNode_Num][3]={
{'A','B', 5}, {'A','C', 1},
{'B','D', 1}, {'B','A', 5},
{'C','D', 3},
{'D','E', 6}, {'D','F', 4},
{'E','G', 1},
{'F','C', 2}};
void Create_Matrix()
{
int i = 0, start, end;
memset(sNode, INT_MAX, sizeof(sNode)); //初始化所有边为最大
for(i = 0; i<sNode_Num; i++){
// 获取当前边的起始点
start = s_char[i][0] - 'A';
end = s_char[i][1] - 'A';
sNode[start][end] = s_char[i][2];
}
printf("\n网矩阵创建完毕\n\n");
}
void Print_Matrix(void)
{
int start,end;
printf(" ");
for(start = 0; start<Node_Num; start++)
printf("%c ", pNode[start]);
for(start = 0; start<Node_Num; start++){
printf("\n%c ", pNode[start]);
for(end = 0; end<Node_Num; end++){
printf("%2d ", sNode[start][end]);
}
}
printf("\n\n网矩阵打印完毕\n");
}
// 栈代码 ***********************************************************************
// 实现一个 10 个元素的循环队列
#define Queue_Size 10
typedef struct List{
char node;
struct List *next;
struct List *pre;
}List;
// 定义循环队列的头部和尾部
List * list_head = NULL;
List * list_tail = NULL;
// 往list后添加新的节点,list为队尾节点
List * Insert_pNode(List *list){
List *p;
if(list == NULL){
return NULL;
}
p = (List *)malloc(sizeof(List));
p->node = 0;
p->next = list->next;
p->pre = list->next->pre;
list->next->pre = p;
list->next = p;
return p;
}
List * New_List_Queue(int size){
List * list;
int i;
if(size == 0)
return NULL;
printf("\n%s -- size=%d\n",__func__, size);
// 创建头节点,单向循环链表头节点指向自已
list = (List *)malloc(sizeof(List));
list->node = 0;
list->next = list;
list->pre = list;
list_head = list; //队头指向 list
list_tail = list; //队尾指向 list
//创建剩余的节点
for(i = 0; i<size-1; i++){
list = Insert_pNode(list);
}
printf("\n列表创建完毕\n");
//创建完毕,返回list 的头结点,即 list->next,因为list即最后一个节点
return list->next;
}
void Delete_List_Queue(List *list){
List *p;
while(list->next != list){
p = list->next;
list->next = p->next;
list->next->pre = p->pre;
printf("\n释放ListQueue:%p", p);
free(p);
}
printf("\n释放ListQueue:%p\n\n", list);
free(list);
}
//向队尾指针添加 Node节点
void Push_Node(char node){
List *p = NULL;
// 如果队列满了,则自动扩充队列
if(list_tail == list_head && list_head->node != 0){
// 找到list_head 的前一个节点
p = list_head;
while(p->next != list_head) p = p->next;
// 在队尾插入节点,返回队尾指针
list_tail = Insert_pNode(p);
printf("\n队列容量已满,增加一个元素, list_tail=%p, list_head=%p\n", list_tail, list_head);
}
if(list_tail != NULL){
printf("\n插入元素 %c, list_head(%p), list_tail(%p)",node, list_head,list_tail );
list_tail->node = node;
list_tail = list_tail->next;
}
}
//队列:从队头取出一个 Node节点,队列无数据时,返回NULL,即先进先出
char Pop_Queue(){
char node;
if(list_head != NULL && list_head->node != 0){
node = list_head->node;
list_head->node = 0;
list_head = list_head->next;
printf("\n弹出元素 %c",node);
return node;
}
return 0;
}
//栈: 从队尾取出一个 Node节点,队列无数据时,返回NULL,即先进后出
char Pop_Stack(){
char node;
if(list_tail->pre != NULL && list_tail->pre->node != 0){
node = list_tail->pre->node;
list_tail->pre->node = 0;
list_tail = list_tail->pre;
printf("\n弹出元素 %c",node);
return node;
}
return 0;
}
// 网矩阵深度遍历算法
void DFS_Matrix()
{
char cur, next;
char find_list[Node_Num], *f = find_list, flag, i, j;
// 分配二级指针内存,用于保存最短路径,及父节点
int **sh = (int **)malloc(sizeof(int *) * Node_Num);
for(i = 0; i<Node_Num; i++){
*(sh+i) = (int *)malloc(sizeof(int) * 3);
(*(sh+i))[0] = 0;
(*(sh+i))[1] = 0;
(*(sh+i))[2] = INT_MAX; // 初始距离是最大
}
// 先将 'A' 入栈
Push_Node(pNode[0]);
*f++ = pNode[0]; // 记录 A 已访问过
sh[0][0] = pNode[0];
sh[0][1] = pNode[0];
// 只要栈头指针指向的数据不为0,说明栈不为空
while(list_head->node != 0){
cur = Pop_Stack(); // 出栈
// 找到下一个未访问过的节点
for(i =0, flag=0; i<Node_Num && flag==0; i++){
if( sNode[cur-'A'][i] > 0){
next = 'A' + i;
//printf("\n当前%c->%c = %d, flag=%d",cur, next, sNode[cur-'A'][i], flag);
// 找到节点,判断新节点是否访问过,如果访问过返回1
for(j=0; j<Node_Num && flag==0; j++){
if(find_list[j] == next){
break;
}
if(j == Node_Num-1)
flag = 1;
}
}
}
if(flag == 1){
Push_Node(cur); // 将当前节点入栈
Push_Node(next); // 将新节点入栈
*f++ = next; // 记新节点已访问过
printf("\n当前节点%c 的距离 %d, 父节点%c 最短距离:%d\n", next, sNode[cur-'A'][next-'A'],cur, sh[cur-'A'][2]);
}else
printf(", 未找到有效节点,跳过",cur);
//针对当前节点,遍历所有可连接的点
for( i=0; i<Node_Num; i++){
if( sNode[cur-'A'][i] > 0){
next = 'A' + i;
if(next != 'A'){
if( sh[cur-'A'][2] == INT_MAX ){
sh[next-'A'][0] = next;
sh[next-'A'][1] = cur;
sh[next-'A'][2] = sNode[cur-'A'][next-'A'];
}else if(sNode[cur-'A'][next-'A'] + sh[cur-'A'][2] < sh[next-'A'][2]){
sh[next-'A'][0] = next;
sh[next-'A'][1] = cur;
sh[next-'A'][2] = sNode[cur-'A'][next-'A'] + sh[cur-'A'][2];
}
}
}
}
}
printf("\n\n网矩阵深度遍历结束, 遍历顺序为:\n");
for(i=0; i<Node_Num; i++)
printf("%c--> ", find_list[i]);
printf("\n\n");
printf("\n\n最短距离为:\n");
for(i=0; i<Node_Num; i++)
printf("当前结点:%c,父结点:%c, 当前到'A'最短距离:%d\n", sh[i][0], sh[i][1], sh[i][2]==INT_MAX?0:sh[i][2]);
printf("\n\n");
}
// 网矩阵广度遍历算法
void BFS_Matrix()
{
char cur, next;
char find_list[Node_Num], *f = find_list, i, j;
// 分配二级指针内存,用于保存最短路径,及父节点
int **sh = (int **)malloc(sizeof(int *) * Node_Num);
for(i = 0; i<Node_Num; i++){
*(sh+i) = (int *)malloc(sizeof(int) * 3);
(*(sh+i))[0] = 0;
(*(sh+i))[1] = 0;
(*(sh+i))[2] = INT_MAX; // 初始距离是最大
}
// 先将 'A' 入队列
Push_Node(pNode[0]);
*f++ = pNode[0]; // 记录 A 已访问过
sh[0][0] = pNode[0];
sh[0][1] = pNode[0];
// 只要栈头指针指向的数据不为0,说明队列 不为空
while(list_head->node != 0){
cur = Pop_Queue(); // 出队列
// 找到下一个未访问过的节点
for(i =0; i<Node_Num ; i++){
if( sNode[cur-'A'][i] > 0){
next = 'A' + i;
printf("\n当前%c->%c = %d",cur, next, sNode[cur-'A'][i]);
// 找到节点,判断新节点是否访问过,如果没访问过就放入队列
for(j=0; j<Node_Num; j++){
if(find_list[j] == next)
break;
// 如果到最后一个还不匹配,说明没访问过
if( j == Node_Num-1){
Push_Node(next);
*f++ = next; // 记录 next已访问过
}
}
printf("\n当前节点%c 的距离 %d, 父节点%c 最短距离:%d\n", next, sNode[cur-'A'][next-'A'],cur, sh[cur-'A'][2]);
// 更新最短路径 = sh[cur-'A'][2] + sNode[cur-'A'][next-'A']
if(next != 'A'){
if( sh[cur-'A'][2] == INT_MAX ){
sh[next-'A'][0] = next;
sh[next-'A'][1] = cur;
sh[next-'A'][2] = sNode[cur-'A'][next-'A'];
}else if(sNode[cur-'A'][next-'A'] + sh[cur-'A'][2] < sh[next-'A'][2]){
sh[next-'A'][0] = next;
sh[next-'A'][1] = cur;
sh[next-'A'][2] = sNode[cur-'A'][next-'A'] + sh[cur-'A'][2];
}
}
}
}
}
printf("\n\n网矩阵广度遍历结束, 遍历顺序为:\n");
for(i=0; i<Node_Num; i++)
printf("%c--> ", find_list[i]);
printf("\n\n");
printf("\n\n最短距离为:\n");
for(i=0; i<Node_Num; i++)
printf("当前结点:%c,父结点:%c, 当前到'A'最短距离:%d\n", sh[i][0], sh[i][1], sh[i][2]==INT_MAX?0:sh[i][2]);
printf("\n\n");
}
int main()
{
// 初始化栈链表
List * list = New_List_Queue(Queue_Size);
// 创建网矩阵
Create_Matrix();
// 找印网矩阵
Print_Matrix();
// 网矩阵的深度遍历
//DFS_Matrix();
// 网矩阵的广度遍历
BFS_Matrix();
return 0;
}