贪心算法问题实验:贪心算法解决TSP问题

news/2024/11/25 5:19:44/

目录

  • 前言
  • 实验内容
  • 实验流程
  • 实验过程
    • 算法分析
    • 伪代码
    • 代码实现
    • 分析算法复杂度
    • 用例测试
  • 总结

前言

TSP问题是指旅行商问题,即给定一组城市和每对城市之间的距离,求解访问每一座城市一次并回到起始城市的最短回路。它是组合优化中的一个NP困难问题,在运筹学和理论计算机科学中有着广泛的应用。

贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法。贪心算法在有最优子结构的问题中尤为有效。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,但是仍然可以通过贪心算法近似地得到全局最优解)。

贪心算法解决TSP问题的基本思想是:从某个城市出发,每次都选择距离当前城市最近的未访问过的城市作为下一个目的地,直到所有城市都被访问过,然后回到出发城市。这种方法虽然简单,但是不能保证得到最优解,因为它没有考虑全局的信息,只是局部贪心。但是它可以在较短的时间内得到一个可行解,作为启发式算法或者近似算法。

实验内容

给出n个城市以及任意两城市间的距离,要求旅行家在旅行n个城市时,各个城市经历且仅经历一次然后回到出发城市,使得所走的路径最短,输出结果,输出时要求有文字说明,使用C语言编写程序实现上述算法,并分许其算法复杂度

实验流程

根据实验内容设计算法伪代码进行算法描述
利用C++/C/Java等编程语言对算法伪代码进行工程化实现
输入测试用例对算法进行验证
列出算法时间复杂度模型并与计算机运行统计时间进行对比分析

实验过程

算法分析

这个问题是一个经典的组合优化问题,叫做旅行商问题(Traveling Salesman Problem,TSP)。它可以用图论的语言来描述:在一个带权重的完全无向图中,找一个权值最小的哈密尔顿回路。

要求使用C语言编写程序实现上述算法,并分析其算法复杂度。

这个问题是一个NP-hard问题,也就是说,目前还没有已知的多项式时间的算法来解决它。因此,对于较大规模的问题,我们通常需要使用启发式算法或近似算法来寻找次优解或近似解。

不过,对于较小规模的问题,我们可以使用动态规划算法来寻找最优解。动态规划算法的基本思想是将原问题分解为子问题,并利用子问题的最优解来构造原问题的最优解。

对于旅行商问题,我们可以用一个二维数组dp[i][S]来表示从城市i出发经过集合S中的所有城市一次且仅一次后回到起始城市的最短路径长度13。其中S是一个二进制数,表示哪些城市被访问过。例如,如果有5个城市,S=10101表示访问了第1、3、5个城市。

我们可以从下往上逐步计算dp[i][S]的值。对于每个i和S,我们枚举下一个要访问的城市j,并比较dp[i][S]+dist[i][j]+dp[j][S-{j}]的大小,其中dist[i][j]表示城市i和j之间的距离,S-{j}表示去掉j后的集合。我们选择最小的值作为dp[i][S]的值,并记录下选择的j作为路径信息。具体地,有如下状态转移方程

dp[i][S] = min(dp[i][S], dp[j][S-{j}] + dist[i][j]) for all j in S and j != i

当我们计算完所有的dp[i][S]后,dp[0][2^n-1]就是从城市0出发经过所有城市并回到起点的最短路径长度。我们可以根据记录的路径信息反向推出具体的路线。

伪代码

输入城市数量n
输入城市间距离矩阵dist[n][n]定义动态规划数组dp[n][2^n],表示从城市i出发经过集合S中的城市并回到起点0的最短路径长度
定义路径记录数组path[n][2^n],表示从城市i出发经过集合S中的城市并回到起点0的最短路径中,i后面的第一个城市将dp和path都初始化为无穷大和-1
将dp[i][0]设为dist[i][0],表示从任意城市i回到起点0的最短路径长度
将path[i][0]设为0,表示从任意城市i回到起点0的最短路径中,i后面的第一个城市是0遍历所有集合状态S(从1到2^n-1)遍历所有城市i(从0到n-1)如果S中不包含i,则跳过遍历所有城市j(从0到n-1)如果S中已经包含j,则跳过如果dp[i][S] > dp[j][S-(1<<(j-1))] + dist[i][j],则更新dp[i][S]和path[i][S]dp[i][S] = dp[j][S-(1<<(j-1))] + dist[i][j]path[i][S] = j输出dp[0][(1<<n)-1],表示从城市0出发经过所有城市并回到起点0的最短路径长度输出具体路线:令i = 0,S = (1<<n)-1当i不等于-1时循环输出i令j = path[i][S]如果j不等于-1,则令S = S - (1<<(j-1))令i = j

代码实现

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>#define MAXN 20 // 城市最大数量
#define INF INT_MAX // 无穷大int n; // 城市数量
int dist[MAXN][MAXN]; // 城市间距离矩阵
int dp[MAXN][1<<MAXN]; // 动态规划数组
int path[MAXN][1<<MAXN]; // 路径记录数组// 计算从城市0出发经过所有城市并回到起点的最短路径长度及其路径
void tsp() {// 初始化dp和pathfor (int i = 0; i < n; i++) {for (int S = 0; S < (1<<n); S++) {dp[i][S] = INF; // 最短路径长度设为无穷大path[i][S] = -1; // 路径记录设为-1}}// 边界条件:从任意城市i回到起点0for (int i = 0; i < n; i++) {dp[i][0] = dist[i][0]; // 最短路径长度就是i和0之间的距离path[i][0] = 0; // 路径记录为0}// 自底向上计算dp和pathfor (int S = 1; S < (1<<n); S++) { // 遍历所有集合状态for (int i = 0; i < n; i++) { // 遍历所有城市作为当前位置if ((S & (1<<(i-1))) == 0) continue; // 如果集合中不包含当前位置,则跳过for (int j = 0; j < n; j++) { // 遍历所有城市作为下一个位置if ((S & (1<<(j-1))) != 0) continue; // 如果集合中已经包含下一个位置,则跳过if (dp[i][S] > dp[j][S-(1<<(j-1))] + dist[i][j]) { // 如果找到更短的路径长度,则更新dp和pathdp[i][S] = dp[j][S-(1<<(j-1))] + dist[i][j];path[i][S] = j;}}}}
}// 输出结果及其文字说明
void output() {printf("从城市0出发经过所有城市并回到起点的最短路径长度为:%d\n", dp[0][(1<<n)-1]); // 最短路径长度就是dp[0][(1<<n)-1]printf("具体路线为:");int i = 0, S = (1<<n)-1; // 当前位置和集合状态(从起点和全集开始)while (i != -1) { // 直到没有下一个位置结束循环printf("%d ", i); // 输出当前位置int j = path[i][S]; // 下一个位置if (j != -1) S -= (1<<(j-1)); // 如果有下一个位置,则更新集合状态(去掉下一个位置)i = j; // 更新当前位置为下一个位置}printf("\n");
}// 主函数
int main() {scanf("%d", &n); // 输入城市数量// 输入距离矩阵(如果两个城市之间没有直接连接,则输入INF)for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {scanf("%d", &dist[i][j]);}}tsp(); // 调用动态规划函数求解旅行商问题output(); // 输出结果及其文字说明return 0;
}

分析算法复杂度

时间复杂度:由于需要遍历n个城市和2n个集合状态,并对每个状态枚举n个可能的下一个位置,所以时间复杂度为O(n22^n)。
空间复杂度:由于需要使用两个二维数组来存储动态规划结果和路径信息,所以空间复杂度也为O(n
2^n)。

用例测试

首先输入一个整数n,表示城市的数量。
然后输入n行,每行有n个整数,表示城市间的距离矩阵。如果两个城市之间没有直接连接,则输入INF。
输入城市数量n:4
输入城市间距离矩阵dist[n][n](如果两个城市之间没有直接连接,则输入INF):

0 10 15 20
5 0 9 10
6 13 0 12
8 8 9 0

输出从城市0出发经过所有城市并回到起点0的最短路径长度为:35
输出具体路线为:0 1 3 2 0

总结

动态规划是一种求解最优化问题的方法,它通过分析所有可能的解,找出最优的解¹。动态规划适合求解具有重叠子问题最优子结构的问题²。旅行商问题就是这样一种问题,它具有以下特点:

  • 重叠子问题:从一个城市出发经过其他城市并回到起点的最短路径,可以分解为从该城市出发到其他城市的最短路径,再加上从其他城市经过剩余城市并回到起点的最短路径。这样,原问题就可以分解为若干个子问题,而这些子问题之间会有很多重复,因为不同的路径可能会经过相同的城市或相同的城市集合³。
  • 最优子结构:从一个城市出发经过其他城市并回到起点的最短路径,必然包含从该城市出发到其他城市的最短路径,以及从其他城市经过剩余城市并回到起点的最短路径。这样,原问题的最优解就可以由子问题的最优解构成³。

因此,使用动态规划来求解旅行商问题是一种有效的方法,它可以利用子问题之间的关系,避免重复计算,从而提高效率⁴。


http://www.ppmy.cn/news/85478.html

相关文章

什么是客户自助服务门户及其搭建方法

随着信息技术的快速发展&#xff0c;越来越多的企业开始转向以客户为中心的服务模式&#xff0c;而客户自助服务门户&#xff08;Customer Self-Service Portal&#xff09;则成为了重要的服务方式。它可以让客户在不需要人工干预的情况下&#xff0c;自行解决问题&#xff0c;…

浅谈注册中心Eureka、Nacos

一、分布式架构理论CAP理论 一致性(Consistency) (所有节点在同一时间具有相同的数据) 可用性(Availability) (保证每个请求不管成功或者失败都有响应) 分隔容忍(Partition tolerance) (系统中任意信息的丢失或失败不会影响系统的继续运作) 二、Eurka注册中心 1、Eurka 采用 …

03-JSON-JSON数据和Java对象的相互转换(jackson解析器、注解、list、map)

一、JSON数据转换Java对象 在 Java 中&#xff0c;将 JSON 数据转换为 Java 对象&#xff0c;Jackson 作为一个优秀的 JSON 处理库&#xff0c;提供了方便的 API 来实现这个需求。具体来说&#xff0c;需要使用 ObjectMapper 类提供的 readValue() 方法&#xff0c;该方法提供…

day6 广播及实现

什么是广播 数据包发送方式只有一个接受方&#xff0c;称为单播 如果同时发给局域网中的所有主机&#xff0c;称为广播 只有用户数据报(使用UDP协议)套接字才能广播 广播地址&#xff1a; 一个网络内主机号全为1的IP地址为广播地址 发到该地址的数据包被所有的主机接收 255…

c++(内存管理)

本节目标&#xff1a; 1、c/c内存分布 2、c语言中动态内存管理方式 3、c中动态内存管理 4、operator new 与 operator delete函数 5、new和delete的实现原理 6、定位new表达式&#xff08;placement - new&#xff09; 7、常见面试题 目录 1.c/c内存分布 2、c语言中动…

03-bootstrap-响应式布局-栅格系统

一、概述 1、栅格系统是 Bootstrap 中响应式布局的重要组成部分&#xff0c;旨在实现页面元素的自适应排版。Bootstrap 栅格系统将屏幕宽度分为 12 列&#xff0c;通过在 HTML 元素上添加相应的类名&#xff0c;可以让元素占据指定数量的列数&#xff0c;从而实现灵活的布局效…

科技云报道:穿行数字经济时代,数据如何找到“安全感”?

科技云报道原创。 数据作为数字经济时代的新型生产要素&#xff0c;正快速融入经济社会的方方面面&#xff0c;甚至常常被形容为“未来的石油”。 在数字经济时代&#xff0c;数据安全与数据流通同等重要。但随着我国数字经济驶入快车道&#xff0c;数据流动和安全发展的矛盾…

SwiftUI 实现一个 iOS 上 Files App 兼容的文件资源管理器

功能需求 在 SwiftUI 中自己白手起家写一个 iOS&#xff08;或iPadOS&#xff09;上迷你的文件资源管理器是有些难度滴&#xff0c;不过从 iOS 11 &#xff08;2017年&#xff09; 官方引入自家的 Files App 之后&#xff0c;我们就可以借助它的魔力轻松完成这一个功能了。 …