算法分析与设计实验3:实现单源最短路径算法

实验原理

单元最短路径算法,即在一个给定的图中,找到从某一源节点到其他所有节点的最短路径。这里我以Dijkstra为例,Dijkstra算法是一种贪心算法,用于计算一个节点到其他所有节点的最短路径。

Dijkstra算法基本原理是:

  1. 初始化一个距离数组,将源节点到自身的距离设为0,其他节点到源节点的距离设为无穷大。
  2. 创建一个未访问节点的队列,初始时包含所有节点。
  3. 从未访问节点集合中选择距离源节点最近的节点,标记为已访问,并更新其相邻节点的距离。
  4. 重复步骤3,直到所有节点都被访问。

Dijkstra算法的核心思想是:

每次从未访问节点中选择距离源节点最近的节点,并更新其相邻节点的最短路径估计。这个过程通过贪心策略逐步逼近最优解,Dijkstra算法的关键特性就是贪心策略,即在每一步都选择当前看起来最优的顶点(距离最短的顶点),并更新其邻接顶点的距离。算法通过选择当前未访问顶点中距离源点最近的顶点,保证了每次选择的顶点都是源点到该顶点的最短路径上的,最终实现求得单源最短路径。

流程分析

1.初始化

创建一个距离数组dist,用于存储从源节点s到其他所有节点的最短距离。将dist[s]设为0(源节点到自身的距离为0),其他所有节点的距离设为无穷大(表示尚未访问,未知路径)。

再创建一个优先队列Q,用于存储待处理的节点及其当前估计的最短距离。将源节点s及其距离0加入队列。

2.执行循环操作

当优先队列Q不为空时,重复以下步骤:从未访问顶点集合中选择一个距离源点最近的顶点,称为当前顶点u。

(1)更新距离

对于当前顶点u的每一个邻接顶点v,计算通过当前顶点到达该邻接顶点的距离。如果新计算出的距离小于之前记录的距离,则更新该邻接顶点的距离。

(2)标记访问

将当前顶点v标记为已访问,并从未访问顶点集合中移除。

(3)重复过程

如果未访问顶点集合不为空,重复上述步骤;否则算法结束。

3.输出结果

输出源点到所有其他顶点的最短路径和距离。

 

程序实现

#include<stdio.h>

#define MaxVerNum 30   // 顶点的最大个数
#define INF	32767	  // 定义无穷大
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

typedef int Status;   

// 邻接矩阵作为存储结构
typedef char VertexType;   // 顶点的数据类型
typedef int ArcType;	   // 边的数据类型

// 构造数据类型
typedef struct
{
	VertexType Verts[MaxVerNum];
	ArcType DirArcs[MaxVerNum][MaxVerNum];			// 有向图 -- 矩阵表示法
	int VerNum;										// 顶点个数
	int ArcNum;										// 边的个数
}DirAMGraph;		// Adjacency  Matrix  Graph 邻接矩阵

Status CreateDN(DirAMGraph* G);	// 创建有向图
void Print_DirGraph(DirAMGraph* G);	// 打印有向图的邻接矩阵
int LocateDirVer(DirAMGraph* G, VertexType* v);	// 获取顶点下标
// Dijsktra 狄杰斯特拉算法.  查找某顶点到其它任意顶点的最短路径
Status Dijsktra(DirAMGraph* G, int v, int djst[MaxVerNum], int path[MaxVerNum]);
// 打印起点到其它结点的最短路径 --Dijsktra
void PrintDijsktra(DirAMGraph* G, int djst[MaxVerNum], int path[MaxVerNum]);
// 获取顶点 ver 的下标
int LocateDirVer(DirAMGraph* G, VertexType* v)
{
	if (!G) return ERROR;
	int i;
	for (i = 0; i < G->VerNum; i++)
		if (*v == G->Verts[i])
			return i;
	return -1;	// 返回 -1 表示未找到顶点
}

// 创建有向图
Status CreateDN(DirAMGraph* G)			
{
	if (!G) return ERROR;
	printf("请输入顶点及边个数(Vers Arcs): ");
	scanf("%d %d", &G->VerNum, &G->ArcNum);
	getchar();
	int i;
	printf("\n请输入顶点的值(英文字母): ");
	for (i = 0; i < G->VerNum; i++)
	{
		do
		{
			VertexType v;
			scanf("%c", &v);		
			getchar();
			if ((65 <= v && v <= 90) || (97 <= v && v <= 122))
			{
				G->Verts[i] = v;
				break;
			}
			printf("输入错误,请输入英文字母!\n");
		} while (1);	
	}

	//初始化所有边的权为无穷大
	int j;
	for (i = 0; i < G->VerNum; i++)
		for (j = i; j < G->VerNum; j++)
		{
			G->DirArcs[i][j] = INF;	
			G->DirArcs[j][i] = INF;
		}

	//录入边的权值
	printf("\n请输入边关联的顶点及权值(v1 v2 weight): \n");
	for (i = 0; i < G->ArcNum; i++)
	{
		VertexType v1, v2;
		int w;
		do
		{
			scanf("%c %c %d", &v1, &v2, &w);
			getchar();
			if (v1 < 65 || (90 < v1 && v1 < 97) || 122 < v1)
			{
				printf("输入错误,请输入英文字母!\n");
				continue;
			}
			if (v2 < 65 || (90 < v2 && v2 < 97) || 122 < v2)
			{
				printf("输入错误,请输入英文字母!\n");
				continue;
			}
			//查找顶点位置
			int a, b;
			a = LocateDirVer(G, &v1);
			b = LocateDirVer(G, &v2);
			if (a < 0)		// 判断顶点是否存在
			{
				printf("输入的顶点%c不存在,请重新输入!\n", v1);
				continue;
			}
			if (b < 0)		// 判断顶点是否存在
			{
				printf("输入的顶点%c不存在,请重新输入!\n", v2);
				continue;
			} 
			G->DirArcs[a][b] = w;
			break;
		} while (1);	
	}
	return OK;
}
void Print_DirGraph(DirAMGraph* G)
{
	int i, j;
	printf("\n  ");
	for (i = 0; i < G->VerNum; i++)
		printf("%-7c", G->Verts[i]);
	for (i = 0; i < G->VerNum; i++)
	{
		for (j = 0; j < G->VerNum; j++)
		{
			if (j == 0)  printf("\n%c ", G->Verts[i]);
			printf("%-7d", G->DirArcs[i][j]);
		}
	}
}

Status Dijsktra(DirAMGraph* G, int star, int djst[MaxVerNum], int path[MaxVerNum])
{ 
	if (!G || !djst || !path)
		return ERROR;
	// 找到的最短路径集。
	int Set[MaxVerNum] = { 0 };	// 0表不属于最短路径集合,1表属于最短路径集合
	int i;
	for (i = 0; i < G->VerNum; i++)
	{
		//初始化起点到其它顶点的 最小路径开销值
		djst[i] = G->DirArcs[star][i];
		if (djst[i] < INF)  
			path[i] = star;	// 将起点设置为与其相连通结点的前端结点
		else
			path[i] = -1;	//初始化与起点非连通的结点的前端结点为-1,表示不是其中介点
	}
	Set[star] = 1;	// 起点属于最短路径集合
	int j, k;
	for (i = 0; i < G->VerNum - 1; i++)
	{
		int min = INF, idx = star;
		for (j = 0; j < G->VerNum; j++)
		{
			if (djst[j] < min && Set[j] != 1)
			{
				idx = j;
				min = djst[j];
			}
		}
		Set[idx] = 1;	// 找到的最小路径,合并到最短路径集合
		for (j = 0; j < G->VerNum; j++)		// j 表从起点出发到其它结点所经过的中介点
			for (k = 0; k < G->VerNum; k++)
			{
				if (djst[j] + G->DirArcs[j][k] < djst[k])
				{
					// 更新起点到其它结点的 路径最小开销值
					djst[k] = djst[j] + G->DirArcs[j][k];
					// 起点到其它结点的路径最小开销值更新后,记录结点的前端结点
					path[k] = j;
				}
			}
	}
	return OK;
}

void Trail(DirAMGraph* G, int v, int djst[MaxVerNum], int path[MaxVerNum])
{ 
	if (path[v] == -1)
	{
		printf("%c", G->Verts[v]);
		return;
	} 
	Trail(G, path[v], djst, path); 
	int node = path[v];
	printf("-%d->%c", G->DirArcs[node][v], G->Verts[v]);
}

void PrintDijsktra(DirAMGraph* G, int djst[MaxVerNum], int path[MaxVerNum])
{ 
	if (!djst || !path) return;
	int i, star = 0;
	for (i = 0; i < G->VerNum; i++)
		if (path[i] == -1)
		{
			star = path[i];
			break;
		}
	for (i = 0; i < G->VerNum; i++)
	{
		if (path[i] == star) continue;
		Trail(G, i, djst, path);
		printf("  -总开销:%d\n", djst[i]);
	}
}
int main()
{
	DirAMGraph amg;
	CreateDN(&amg);
	Print_DirGraph(&amg);
	int path[MaxVerNum];
	int djst[MaxVerNum];
	VertexType v;
	printf("\n请输入最短路径的起点: ");
	scanf("%c", &v);
	int index = LocateDirVer(&amg, &v);
	if (index < 0)
	{
		printf("图中不存在顶点%c\n", v);
		return 0;
	}
	Dijsktra(&amg, index, djst, path);
	int i;
	printf("\n下标: ");
	for (i = 0; i < amg.VerNum; i++)
		printf("%-6d", i);
	printf("\ndjst: ");
	for (i = 0; i < amg.VerNum; i++)
		printf("%-6d", djst[i]);
	printf("\npath: ");
	for (i = 0; i < amg.VerNum; i++)
		printf("%-6d", path[i]);
	printf("\n");
	printf("\n起点%c到所有顶点的最短路径: \n", v);
	PrintDijsktra(&amg, djst, path);
	return 0;
}

 调试分析

    (1)经过输入验证的调试,保证了图的有效性:确保图是连通的,且没有负权重的边;验证节点和边的数量:确保输入节点和边的数量一致,并且没有重复的边或节点;检查权重:确保所有边的权重为非负值。

    (2)使用初始化检查进行调试,起点验证:确保选择的起点在图中存在。距离数组初始化:起点到自身的距离应为0,其他所有节点的距离为无穷大。访问数组初始化:所有节点均初始化为未访问。

    (3)算法步骤逐步检查调试,选择未访问节点中距离最小的节点:使用优先队列来有效地选择节点。更新相邻节点的距离:对于每个选择的相邻节点,检查其所有相邻节点,如果通过当前节点到相邻节点的距离更短,则更新距离。标记节点为已访问:防止重复处理。

    (4)边界条件处理调试: 孤立节点:如果图中存在孤立节点,确保这些节点在结果中的距离保持为无穷大。空图:如果输入为空图,确保算法能够处理并返回合理的错误或结果。单节点图:如果图中只有一个节点,确保算法返回的距离数组正确。

    测试结果

     

    首先初始化一个包含5个顶点、6条边的有向图,输入图的顶点为:a、b、c、d、e共5个顶点,6条边为:a->b,权重为1;a->c,权重为2;b->d,权重为3;c->d,权重为4;d->e,权重为5;e->b,权重为6,其余边权重皆为无穷大。

    完成输入后,成功得到了相应的邻接矩阵。随后设置a为源节点,通过调用单源最短路径Dijkstra算法,并成功指出起点a到其余4个节点间的最短路径如下:

    起点a到所有顶点的最短路径:

    a-1->b  -总开销:1

    a-2->c  -总开销:2

    a-1->b-3->d  -总开销:4

    a-1->b-3->d-5->e  -总开销:9

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值