基于贪心算法求解TSP问题(JAVA)
概述
详细
前段时间在搞贪心算法,为了举例,故拿TSP来开刀,写了段求解算法代码以便有需之人,注意代码考虑可读性从最容易理解角度写,没有优化,有需要可以自行优化!
一、TPS问题
TSP问题(Travelling Salesman Problem)即旅行商问题,又译为旅行推销员问题、货郎担问题,是数学领域中著名问题之一。假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求得的路径路程为所有路径之中的最小值。
TSP问题是一个组合优化问题。该问题可以被证明具有NPC计算复杂性。TSP问题可以分为两类,一类是对称TSP问题(Symmetric TSP),另一类是非对称问题(Asymmetric TSP)。所有的TSP问题都可以用一个图(Graph)来描述:
V={c1, c2, …, ci, …, cn},i = 1,2, …, n,是所有城市的集合.ci表示第i个城市,n为城市的数目;
E={(r, s): r,s∈ V}是所有城市之间连接的集合;
C = {crs: r,s∈ V}是所有城市之间连接的成本度量(一般为城市之间的距离);
如果crs = csr, 那么该TSP问题为对称的,否则为非对称的。
一个TSP问题可以表达为:
求解遍历图G = (V, E, C),所有的节点一次并且回到起始节点,使得连接这些节点的路径成本最低。
二、贪心算法
贪心算法,又名贪婪算法(学校里老教授都喜欢叫贪婪算法),是一种常用的求解最优化问题的简单、迅速的算法。贪心算法总是做出在当前看来最好的选择,它所做的每一个在当前状态下某种意义上是最好的选择即贪心选择,并希望通过每次所作的贪心选择导致最终得到问题最优解。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。
1、贪心算法的基本思路
从问题的某一个初始解触发逐步逼近给定的目标,以尽可能快地求得更好的解。当达到某算法中的某一步不能再继续前进时,算法停止。大致步骤如下:
1)建立数学模型来描述问题;
2)把求解的问题分成若干个子问题
3)对每一个子问题求解,得到子问题的局部最优解
4)把子问题的局部最优解合成原问题的一个解
2、贪心算法的实现框架
贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择,而贪心策略适用的前提是:局部最优策略能导致产生全局最优解。
从问题的某一初始解出发;
while (能朝给定总目标前进一步)
{
利用可行的决策,求出可行解的一个解元素;
}
由所有解元素组合成问题的一个可行解;
3、贪心算法存在的问题
1)不能保证求得的最后解是最佳的;
2)不能用来求最大最小解问题;
3)只能在某些特定条件约束的情况下使用,例如贪心策略必须具备无后效性等。
4、典型的贪心算法使用领域
马踏棋盘、背包、装箱等
三、贪心算法求解TSP问题
贪心策略:在当前节点下遍历所有能到达的下一节点,选择距离最近的节点作为下一节点。基本思路是,从一节点出发遍历所有能到达的下一节点,选择距离最近的节点作为下一节点,然后把当前节点标记已走过,下一节点作为当前节点,重复贪心策略,以此类推直至所有节点都标记为已走节点结束。
我们使用TSP问题依然来自于tsplib上的att48,这是一个对称TSP问题,城市规模为48,其最优值为10628.其距离计算方法下图所示:
好,下面是具体代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | package noah; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; public class TxTsp { private int cityNum; // 城市数量 private int [][] distance; // 距离矩阵 private int [] colable; //代表列,也表示是否走过,走过置0 private int [] row; //代表行,选过置0 public TxTsp( int n) { cityNum = n; } private void init(String filename) throws IOException { // 读取数据 int [] x; int [] y; String strbuff; BufferedReader data = new BufferedReader( new InputStreamReader( new FileInputStream(filename))); distance = new int [cityNum][cityNum]; x = new int [cityNum]; y = new int [cityNum]; for ( int i = 0 ; i < cityNum; i++) { // 读取一行数据,数据格式1 6734 1453 strbuff = data.readLine(); // 字符分割 String[] strcol = strbuff.split( " " ); x[i] = Integer.valueOf(strcol[ 1 ]); // x坐标 y[i] = Integer.valueOf(strcol[ 2 ]); // y坐标 } data.close(); // 计算距离矩阵 // ,针对具体问题,距离计算方法也不一样,此处用的是att48作为案例,它有48个城市,距离计算方法为伪欧氏距离,最优值为10628 for ( int i = 0 ; i < cityNum - 1 ; i++) { distance[i][i] = 0 ; // 对角线为0 for ( int j = i + 1 ; j < cityNum; j++) { double rij = Math .sqrt(((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j])) / 10.0 ); // 四舍五入,取整 int tij = ( int ) Math.round(rij); if (tij < rij) { distance[i][j] = tij + 1 ; distance[j][i] = distance[i][j]; } else { distance[i][j] = tij; distance[j][i] = distance[i][j]; } } } distance[cityNum - 1 ][cityNum - 1 ] = 0 ; colable = new int [cityNum]; colable[ 0 ] = 0 ; for ( int i = 1 ; i < cityNum; i++) { colable[i] = 1 ; } row = new int [cityNum]; for ( int i = 0 ; i < cityNum; i++) { row[i] = 1 ; } } public void solve(){ int [] temp = new int [cityNum]; String path= "0" ; int s= 0 ; //计算距离 int i= 0 ; //当前节点 int j= 0 ; //下一个节点 //默认从0开始 while (row[i]== 1 ){ //复制一行 for ( int k = 0 ; k < cityNum; k++) { temp[k] = distance[i][k]; //System.out.print(temp[k]+" "); } //System.out.println(); //选择下一个节点,要求不是已经走过,并且与i不同 j = selectmin(temp); //找出下一节点 row[i] = 0 ; //行置0,表示已经选过 colable[j] = 0 ; //列0,表示已经走过 path+= "-->" + j; //System.out.println(i + "-->" + j); //System.out.println(distance[i][j]); s = s + distance[i][j]; i = j; //当前节点指向下一节点 } System.out.println( "路径:" + path); System.out.println( "总距离为:" + s); } public int selectmin( int [] p){ int j = 0 , m = p[ 0 ], k = 0 ; //寻找第一个可用节点,注意最后一次寻找,没有可用节点 while (colable[j] == 0 ) { j++; //System.out.print(j+" "); if (j>=cityNum){ //没有可用节点,说明已结束,最后一次为 *-->0 m = p[ 0 ]; break ; //或者直接return 0; } else { m = p[j]; } } //从可用节点J开始往后扫描,找出距离最小节点 for (; j < cityNum; j++) { if (colable[j] == 1 ) { if (m >= p[j]) { m = p[j]; k = j; } } } return k; } public void printinit() { System.out.println( "print begin...." ); for ( int i = 0 ; i < cityNum; i++) { for ( int j = 0 ; j < cityNum; j++) { System.out.print(distance[i][j] + " " ); } System.out.println(); } System.out.println( "print end...." ); } public static void main(String[] args) throws IOException { System.out.println( "Start...." ); TxTsp ts = new TxTsp( 48 ); ts.init( "c://data.txt" ); //ts.printinit(); ts.solve(); } } |
四、项目运行介绍
下载项目后,导入eclipse,项目截图如下:
求解结果截图:
五、总结
单从求解结果来看,我个人其实还是能接受这个解,但仔细想想,实际上这个求解结果有太多运气成分在里面,贪心算法毕竟是贪心算法,只能缓一时,而不是长久之计,问题的模型、参数对贪心算法求解结果具有决定性作用,这在某种程度上是不能接受的,于是聪明的人类就发明了各种智能算法(也叫启发式算法),但在我看来所谓的智能算法本质上就是贪心算法和随机化算法结合,例如传统遗传算法用的选择策略就是典型的贪心选择,正是这些贪心算法和随机算法的结合,我们才看到今天各种各样的智能算法。
注:本文著作权归作者,由demo大师发表,拒绝转载,转载需要作者授权
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?