鉴于CSDN上实现dijkstra算法大多是使用邻接矩阵,因此记录一下使用邻接表的实现。
该算法通过读入文件构造邻接表,然后使用dijkstra构造单源最短路径。
代码如下:
dijkstra.h
#define MAXSIZE 210
//定义无向图中的顶点
//定义无向图中的边(弧)
typedef struct Edge{
int node;//该边的终点
int cost;//经过这条边需要的代价
struct Edge* nextedge;//与该顶点相连的下一条边
}Edge;
typedef struct Node{
Edge* firstedge;//该顶点所对应的边表的头节点
}Node;
//定义邻接表
typedef struct {
Node* list[MAXSIZE];
int nodecount;
int edgecount;
}AdjacentList;
dijkstra.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "dijkstra.h"
#pragma warning(disable:4996)
#define INF 1000000
#define BUFFERSIZE 255
//初始化邻接表
AdjacentList* initList() {
AdjacentList* list = (AdjacentList*)malloc(sizeof(AdjacentList));
if (list == NULL) {
printf("can not create a list\n");
return NULL;
}
memset(list->list, 0, sizeof(list->list));
list->nodecount = 0;
list->edgecount = 0;
return list;
}
//创建新的顶点
Node* createNode() {
Node* node = (Node*)malloc(sizeof(Node));
if (node == NULL) {
printf("can not create a node\n");
return NULL;
}
node->firstedge = NULL;
return node;
}
//创建新的边
Edge* createEdge(int node, int cost) {
Edge* edge = (Edge*)malloc(sizeof(Edge));
if (edge == NULL) {
printf("can not create an edge\n");
return NULL;
}
edge->cost = cost;
edge->node = node;
edge->nextedge = NULL;
return edge;
}
//采用头插法向边表中插入新的边
int insertEdge(Node* firstnode, Edge* newedge) {
if (firstnode == NULL) {
return 0;
}
if (firstnode->firstedge == NULL) {
firstnode->firstedge = newedge;
return 1;
}
newedge->nextedge = firstnode->firstedge;
firstnode->firstedge = newedge;
return 1;
}
//构造邻接表
AdjacentList* createList(AdjacentList* list, char* filename) {
FILE* infile;
Edge* edge;
char* split = " \t";
int node = -1;
char buffer[BUFFERSIZE];
if ((infile = fopen(filename, "rt")) == NULL) {
fputs(" Couldn't open the file.\n", stdout);
return NULL;
}
char* p;//保存每一行最开始的顶点号
//根据逗号分割字符串后,用于保存边表元素的右半部分
char* right;
int edgenode;
int edgecost;
while (!feof(infile)) {
if (fgets(buffer, BUFFERSIZE, infile) == NULL) {
break;
}
buffer[strlen(buffer)] = '\0';
p = strtok(buffer, split);
node = atoi(p);
if (list->list[node] == NULL) {
//如果邻接表中尚无该顶点,构造
list->list[node] = createNode();
}
list->nodecount++;
p = strtok(NULL, split);
//字符串剩余部分为边表信息,循环操作
for (; p!=NULL && *p!='\n';) {
int i = 0;//用于指示","出现的位置
while (p[i] != ',') { i++; }
right = strdup(p + i + 1);
edgecost = atoi(right);
p[i] = '\0';
edgenode = atoi(p);
//构造新的边,并顶点对应的边表
edge = createEdge(edgenode, edgecost);
int res = insertEdge(list->list[node], edge);
if (res == 0) {
printf("can not insert edge %d,%d \n", edgenode, edgecost);
exit(EXIT_FAILURE);
}
list->edgecount++;
p = strtok(NULL, split);
}
}
return list;
}
//计算从源顶点到其他顶点的单源最短路径
void shortestpath(AdjacentList* list, int visited[], int distance[], int prenode[], int src) {
int count = 0;//记录直接与源点相连的顶点个数
visited[src] = 1;//源点已经添加到最短路径中
distance[src] = 0;
count++;
//如果有顶点和源点直接相连,另外设置距离
if (list->list[src] != NULL) {
Edge* p = list->list[src]->firstedge;
for (; p != NULL; ) {
distance[p->node] = p->cost;
prenode[p->node] = src;//这些顶点的前驱顶点为源点
p = p->nextedge;
}
}
while (count <= list->nodecount) {
int min = INF;
int target_node = 0;
//首先在剩余节点中寻找距离最新加入最短路径的顶点,最近的那一个
for (int i = 1; i <= list->nodecount; i++) {
if (visited[i] == 0 && min > distance[i]) {
min = distance[i];
target_node = i;
}
}
visited[target_node] = 1;//将最短距离顶点加入最短路径中
count++;
//已新加入的顶点为中介,更新剩余未加入最短路径的顶点的距离
if (list->list[target_node] != NULL) {
Edge* p = list->list[target_node]->firstedge;
for (; p != NULL; ) {
if (visited[p->node] == 0 && distance[target_node] + p->cost < distance[p->node]) {
distance[p->node] = distance[target_node] + p->cost;
prenode[p->node] = target_node;
}
p = p->nextedge;
}
}
}
}
int main(int argc, char* argv[]) {
//判断
if (argc != 2)
{
fprintf(stderr, "please enter the file \n");
exit(EXIT_FAILURE);
}
AdjacentList* list;
list = initList();
list = createList(list, "data.txt");
int visited[201] = {0};//标记顶点是否已经被记录为最短路径
int distance[201];//标记从源点到其他顶点的路径长度
int prenode[201] = {0};//如果某个顶点要加入最短路径,记录该顶点在最短路径中的前一个顶点
//初始将每个顶点的距离设置为无穷大
for (int i = 0; i < 201; i++) {
distance[i] = INF;
}
int src = 0;
char input[4];
printf("请输入源点序号(1——200): ");
scanf("%s", input);
input[strlen(input)] = '\0';
src = atoi(input);
if (src <= 0 || src > 200) {
printf("\n序号不合法,请输入正确序号\n");
return 0;
}
shortestpath(list, visited, distance, prenode, src);
int path[200] = { 0 };//用于保存路径信息
//输出最短路径信息
for (int i = 1; i <= 200; i++) {
if (distance[i] == INF) {
printf("顶点 %d 到顶点 %d 之间没有最短路径\n", src, i);
}
else {
int p = i;
int n = 0;
while (p != 0) {
path[n] = p;
p = prenode[p];//使p指向自己的前驱节点
n++;
}
//n为路径中节点数量
printf("顶点 %d 到顶点 %d 之间的最短路径为: ", src, i);
printf("%d", path[n - 1]);
for (int j = n - 2; j >= 0; j--) {
printf("—>%d", path[j]);
}
printf(" 路径长度为 %d\n", distance[i]);
}
memset(path, 0, 200);
}
return 0;
}
以下为测试用例:‘data.txt’
链接: https://pan.baidu.com/s/1MKQO6IzYfMkutv3ozHOtGw 提取码: 81tu
时间复杂度分析:该算法是中规中矩的实现,时间复杂度为O(n^2),如果有同学有兴趣,可以在“查找最短距离的顶点”这一块,使用堆排序来优化,这样的话,时间复杂度应该为O(nlogn), n为无向图的顶点个数。