贪心、分支限界、动态规划解决最短路径问题
动态规划实现最短路径问题

动态规划实现最短路径问题⼀、设计最短路径的动态规划算法 <算法导论>中⼀般将设计动态规划算法归纳为下⾯⼏个步骤: 1)分析最优解的结构 2)递归定义最优解的值 3)⾃底向上计算最优解的值 4)从计算的最优解的值上⾯构建出最优解⼆、最短路径的结构 从最优解的结构开始分析(我们假设没有权值为负的路径),对于图G<V,E>的所有结点对最短路径的问题,我们能知道⼀条最短路径的⼦路径都是最短路径。
假设⽤邻接矩阵W=w(ij)来表⽰输⼊带权图,考虑从结点i到结点j的⼀条最短路径p,如果p最多有m(m为有限值)条边。
若i=j,则p的权值为0⽽且不包含其他边。
若i ≠ j,可以将i到j的路径转换为i -> k、k->j。
三、⼀个给定的图 1)给定⼀个有向图 2)我们可以给出这个有向图的邻接矩阵四、C++实现1 #include <iostream>2 #include<fstream>3 #include<sstream>4 #include<vector>5 #include<string>6using namespace std;7const int Max_Num = 100;89 typedef struct Point {10int n; //点的个数11double p[Max_Num];12double q[Max_Num];13int root[Max_Num][Max_Num];14double w[Max_Num][Max_Num];15double e[Max_Num][Max_Num];16 }Point;1718 vector<Point> points;19 vector<string> res;20 vector<int> num;2122void file_read();23void createPoint();24void optimalBST();25void printRoot(Point P);26void printOptimalBST(int i, int j, int r, Point P, ofstream &fileWrite);27 template <class Type>28 Type stringToNum(const string& str) {29 istringstream iss(str);30 Type num;31 iss >> num;32 iss.str("");33return num;34 }3536void file_read() {37string str2, str1 = "", result;38 ifstream fileRead("in.dat");39if (fileRead.is_open()) {40while (getline(fileRead, str2, '\n')) {41if (str2.find("") != -1) {42 str1.append(str2 + "");43 }44else {45 num.push_back(stringToNum<int>(str2));46if (str1 != "") {47 res.push_back(str1);48 }49 str1 = "";50 }51 }52 res.push_back(str1);53 fileRead.close();54 }55 }5657void createPoint() {58string temp;59 Point P;60for (int i = 0; i < res.size(); i++) {61 vector<string> temp_str; //存放按照空格分开后的数字62int n = num[i];63 stringstream input(res[i]);64while (input >> temp) {65 temp_str.push_back(temp);66 }67 P.n = n;68for(int k = 0; k<=n; k++) P.p[k] = stringToNum<double>(temp_str[k]);69for(int k = n + 1; k<temp_str.size(); k++) P.q[k-(n+1)] = stringToNum<double>(temp_str[k]);70 points.push_back(P);71 }72 }7374//根据书上的伪代码:接收概率列表p1....pn和q0.....qn以及规模n作为输⼊计算出e和root75void optimalBST(){76 Point P;77for(int i = 0; i<res.size(); i++) {78 vector<string> temp_str; //存放按照空格分开后的数字79int n = num[i];80string temp;81 stringstream input(res[i]);82while (input >> temp) {83 temp_str.push_back(temp);84 }85 P.n = n;8687for(int k = 0; k<=n; k++) P.p[k] = stringToNum<double>(temp_str[k]);88for(int k = n + 1; k<temp_str.size(); k++) P.q[k-(n+1)] = stringToNum<double>(temp_str[k]); 8990//初始化只包括虚拟键的⼦树91for (int i = 1;i <= P.n + 1;++i){92 P.w[i][i-1] = P.q[i-1];93 P.e[i][i-1] = P.q[i-1];94 }95//由下到上,由左到右逐步计算96for (int len = 1;len <= P.n;++len){97for (int i = 1;i <= P.n - len + 1;++i){98int j = i + len - 1;99 P.e[i][j] = Max_Num;100 P.w[i][j] = P.w[i][j-1] + P.p[j] + P.q[j];101//求取最⼩代价的⼦树的根102for (int r = i;r <= j;++r)103 {104double temp = P.e[i][r-1] + P.e[r+1][j] + P.w[i][j];105if (temp < P.e[i][j])106 {107 P.e[i][j] = temp;108 P.root[i][j] = r;109 }110 }111 }112 }113 points.push_back(P);114 }115 }116117void printOptimalBST(int i, int j, int r, Point P, ofstream &fileWrite){118int root_node = P.root[i][j];//⼦树根节点119if (root_node == P.root[1][P.n]){120//输出整棵树的根121 fileWrite << "k" << root_node << "是根" << endl;122 printOptimalBST(i, root_node - 1, root_node, P, fileWrite);123 printOptimalBST(root_node +1 , j, root_node, P, fileWrite);124return;125 }126127if (j < i - 1){128return;129 }else if (j == i - 1){//遇到虚拟键130if (j < r)131 fileWrite << "d" << j << "是" << "k" << r << "的左孩⼦" << endl;132else133 fileWrite << "d" << j << "是" << "k" << r << "的右孩⼦" << endl;134return;135 }136else{//遇到内部结点137if (root_node < r)138 fileWrite << "k" << root_node << "是" << "k" << r << "的左孩⼦" << endl; 139else140 fileWrite << "k" << root_node << "是" << "k" << r << "的右孩⼦" << endl; 141 }142 printOptimalBST(i, root_node - 1, root_node, P, fileWrite);143 printOptimalBST(root_node + 1, j, root_node, P, fileWrite);144 }145146//输出最优⼆叉查找树所有⼦树的根147void printRoot(Point P){148 cout << "各⼦树的根:" << endl;149for (int i = 1;i <= P.n;++i){150for (int j = 1;j <= P.n;++j){151 cout << P.root[i][j] << "";152 }153 cout << endl;154 }155 cout << endl;156 }157158int main(){159 file_read();160 optimalBST();161 ofstream fileWrite("out.dat");162 Point P ;163for(int i = 0; i<points.size(); i++) {164 P = points[i];165 printRoot(P);166 printOptimalBST(1,P.n,-1, P, fileWrite);167 }168 fileWrite.clear();169return0;170 } 上述代码是将给定的邻接矩阵从⽂件中读取 然后根据输⼊的邻接矩阵求出最短路径。
01背包各种算法代码实现总结(穷举,贪心,动态,递归,回溯,分支限界)

01背包各种算法代码实现总结(穷举,贪⼼,动态,递归,回溯,分⽀限界)2020-05-22所有背包问题实现的例⼦都是下⾯这张图01背包实现之——穷举法:1.我的难点:(1)在⽤穷举法实现代码的时候,我⾃⼰做的时候认为最难的就是怎么将那么多种情况表⽰出来,⼀开开始想⽤for循环进⾏多次嵌套,但是太⿇烦,⽽且还需要不断的进⾏各种标记。
我现在的⽔平实在太菜,然后就在⼀篇中看到⼀个特别巧妙的枚举算法,如下所⽰:int fun(int x[n]){int i;for(i=0;i<n;i++)if(x[i]!=1) {x[i]=1; return;}//从遇到的第⼀位开始,若是0,将其变成1,然后结束for循环,得到⼀种解法else x[i]=0;return;//从第⼀位开始,若是1,将其变成0,然后继续循环,若再循环的时候遇到0,则将其变为1,结束循环。
得到另⼀种解法。
} 虽然我现在也不知道为什么会这样,但是确实是个很好的规律,找到这个规律后,就可以很轻松的⾃⼰写出各种排列情况,以后遇到排列的问题,就⽤这个⽅法。
语⾔不好描述,上图⽚演⽰(是歪的,凑活看吧。
):(2)算法思想:x[i]的值为0/1,即选或者不选w[i]的值表⽰商品i的重量v[i]的值表⽰商品的价值所以这个算法最核⼼的公式就是tw=x[1]*w[1]+x[2]*w[2]+.......+x[n]*w[n]tv=x[1]*w[1]+x[2]*v[2]+......+x[n]*v[n]tv1:⽤于存储当前最优解limit:背包容量如果 tw<limit&&tv>tv1 则可以找到最优解2.代码实现(借鉴)#include<stdio.h>#include<iostream>using namespace std;#define n 4void possible_solution(int x[n]){int i;for(i=0;i<4;i++) //n=4,有2^4-1种解法if(x[i]!=1){x[i]=1;return; //从遇到的第⼀位开始,若是0,将其变成1,然后结束循环,得到⼀种解法}elsex[i]=0;return;//从第⼀位开始,若是1,将其变成0,然后继续循环,若再循环的时候遇到0,则将其变为1,结束循环。
动态规划在最短路径问题中的应用

动态规划在最短路径问题中的应用动态规划是一种解决复杂问题的方法,它将问题分解成更小的子问题,并通过保存子问题的解来避免重复计算,从而提高解决问题的效率。
最短路径问题是在图或者网络中找到从起点到终点的最短路径的问题,可以使用动态规划算法来解决。
本文将介绍动态规划在最短路径问题中的应用及其算法实现。
一、最短路径问题在最短路径问题中,我们需要在图或网络中找到从一个节点到另一个节点的最短路径。
最短路径可以通过边的权重来衡量,权重可以表示距离、时间、代价等。
最短路径问题有多种变体,其中最常见的是单源最短路径和全源最短路径。
单源最短路径问题是在给定一个起点的情况下,找到该起点到其他所有节点的最短路径。
最常用的算法是Dijkstra算法和Bellman-Ford算法。
二、动态规划原理动态规划通过保存子问题的解来避免重复计算,从而提高算法的效率。
它将问题分解成更小的子问题,并使用递推关系来计算子问题的解。
在最短路径问题中,我们可以使用动态规划来计算从起点到每个节点的最短路径。
首先,我们定义一个一维数组dist[]来保存从起点到每个节点的最短路径长度。
初始化时,dist[]的值为无穷大,表示路径长度未知。
然后,我们从起点开始逐步计算每个节点的最短路径长度。
具体的动态规划算法如下:1. 初始化dist[]为无穷大,起点的dist[]为0。
2. 对于每个节点v,按照拓扑顺序进行如下操作:2.1. 对于节点v的所有邻接节点u,如果dist[v] + weight(v, u) < dist[u],则更新dist[u]。
2.2. 拓扑顺序可以根据节点的拓扑顺序进行计算或者使用深度优先搜索(DFS)算法。
三、算法实现下面是使用动态规划算法解决最短路径问题的示例代码:```// 定义图的邻接矩阵和节点个数int graph[MAX][MAX];int numNodes;// 定义dist[]数组来保存最短路径长度int dist[MAX];// 定义拓扑排序和DFS算法需要的变量bool visited[MAX];stack<int> s;// 动态规划算法求解最短路径void shortestPath(int startNode) {// 初始化dist[]数组为无穷大for (int i = 0; i < numNodes; i++) {dist[i] = INT_MAX;}dist[startNode] = 0;// 拓扑排序或DFS计算每个节点的最短路径长度 for (int i = 0; i < numNodes; i++) {if (!visited[i]) {DFS(i);}}// 输出最短路径长度for (int i = 0; i < numNodes; i++) {cout << "Node " << i << ": " << dist[i] << endl; }}// 深度优先搜索void DFS(int node) {visited[node] = true;for (int i = 0; i < numNodes; i++) {if (graph[node][i] != 0 && !visited[i]) {DFS(i);}}s.push(node);}```以上示例代码演示了使用动态规划算法求解最短路径问题的基本原理和步骤。
算法设计与分析-多段图最短路径问题

关于多段图最短路径问题的探讨摘要:本文主要描述的是分别用动态规划法、贪心法和分支限界法来解决多段图最短路径问题时的情况,并在附录中附有实际问题的程序来辅助阐述观点。
文章首先阐述了各个方法的原理,主要的思路是通过输入一组数据,比较三者的输出结果的准确性以及运行时间,以之为基础来分析、讨论三者的性能区别。
另外,众所周知,多段图是有向图的一个简单的模型,它在有向图的基础上忽略了两点之间的线的双向性的问题,并且对点与点之间的线有很多的要求,从而把图简化为可分为几段的模式,文章最后讲述了若这几种方法运行到有向图中的情况,几种方法的对比和它们比较适应的使用情况的讨论,并给出了自己的建议。
关键字:多段图最短路径问题动态规划法分支限界法多段图与有向图的关系有向图最短路径算法引言:当前社会,关于最短路径的问题屡屡出现。
例如在开车自驾游的一个过程中,排除其他影响因素,从一个地点到另一点,这个时候必然是希望有一条距离最短的路程来尽量减少消耗的时间以及花费的(它们在模型中被称为代价),市场上对该问题的解决有很大的需求,因此,这里我将讨论多段图的最短路径的问题。
在早些时间的课程中,我们学习过数据结构这门课程,其中就包括最短路径这方面的讨论。
当时老师给我们介绍了分别面向单源(Dijkstra算法)与非单源(Floyd算法)两种问题的算法法---,这是我们最早的对最短路径方面的理解,也是我们接触的比较早的关于图的问题。
在这学期的算法课程中,我们学习了许多了方法,包括贪心法、动态规划法等算法,它们把以前学习的许多方法都命名并归纳分类起来,其中有许多算法都是可以用来解决这个最短路径的问题的,并且该问题作为一个图的问题,对该问题的继续探讨优化的需求很大,本文将就不同算法在解决该最短路径问题时的不同方法进行对比并给出该问题在不同基础上不同的最终解决方案。
由于时间的限制,本文将重点分析动态规划法下的情况,并会对图的情况加以简化、限制,最后会对其他的图做一些拓展。
数学建模经典问题

数学建模经典问题
数学建模是一种将实际问题转化为数学问题,并运用数学工具解决实际问题的方法。
在数学建模的过程中,我们需要面对各种各样的问题,其中一些问题已经被广泛研究并被视为经典问题。
本文将介绍几个数学建模中的经典问题。
1.旅行商问题
旅行商问题是一个经典的路线优化问题。
假设有一个旅行商要拜访n个城市,每个城市之间的距离是已知的。
旅行商需要找到一条回路,使得他可以在每个城市停留一次,并返回起点城市,同时旅行路程最短。
这个问题是一个NP难问题,可以用动态规划、分支限界等方法求解。
2.背包问题
背包问题是一个经典的优化问题。
假设有一个背包,它的容量为C,有n个物品,每个物品有一个重量和一个价值。
旅行商需要在这些物品中选择一些放入背包,使得背包的重量不超过C,同时所选物品的总价值最大。
这个问题也是一个NP难问题,可以用动态规划、贪心算法等方法求解。
3.热传导方程
热传导方程是一个经典的偏微分方程,描述了物体内部温度的变化。
它可以用来模拟热传导过程,例如烤面包、冷却热水等。
热传导方程可以用有限元方法、有限差分方法等数值方法求解。
4.计算几何
计算几何是一个经典的数学分支,研究几何问题的计算方法。
例如,给定n个点,如何寻找一个最小的圆,使得这n个点都在圆内或圆上。
这个问题可以用Welzl算法等方法求解。
这些经典问题在数学建模中经常出现,它们不仅有理论研究的价值,而且对于实际应用也有着很大的意义。
在数学建模的过程中,我们应该灵活运用各种数学工具,以便更好地解决实际问题。
贪心算法和动态规划的区别与联系

贪⼼算法和动态规划的区别与联系
联系
1.都是⼀种推导算法
2.都是分解成⼦问题来求解,都需要具有最优⼦结构
区别
1.贪⼼:每⼀步的最优解⼀定包含上⼀步的最优解,上⼀步之前的最优解则不作保留;
动态规划:全局最优解中⼀定包含某个局部最优解,但不⼀定包含前⼀个局部最优解,因此需要记录之前的所有的局部最优解
2.贪⼼:如果把所有的⼦问题看成⼀棵树的话,贪⼼从根出发,每次向下遍历最优⼦树即可(通常这个“最优”都是基于当前情况下显⽽易见的“最优”);这样的话,就不需要知道⼀个节点的所有⼦树情况,于是构不成⼀棵完整的树;
动态规划:动态规划则⾃底向上,从叶⼦向根,构造⼦问题的解,对每⼀个⼦树的根,求出下⾯每⼀个叶⼦的值,最后得到⼀棵完整的树,并且最终选择其中的最优值作为⾃⾝的值,得到答案
3.根据以上两条可以知道,贪⼼不能保证求得的最后解是最佳的,⼀般复杂度低;⽽动态规划本质是穷举法,可以保证结果是最佳的,复杂度⾼。
4.针对0-1背包问题:这个问题应⽐较选择该物品和不选择该物品所导致的最终⽅案,然后再作出最好选择,由此就导出许多互相重叠的⼦问题,所以⽤动态规划。
算法的技术手段范文

算法的技术手段范文下面将介绍一些常见的算法的技术手段。
1.分治法分治法将问题划分为若干个规模较小的子问题,然后分别解决这些子问题,最后将各个子问题的解合并得到原问题的解。
这种技术手段在快速排序和归并排序等算法中有广泛的应用。
2.动态规划动态规划将问题划分为多个阶段,根据问题的最优子结构特性,将其解决过程划分为多个阶段的决策过程。
动态规划算法通常需要使用递归和数组的结构来存储中间计算结果,以避免重复计算。
背包问题、最短路径问题等都可以使用动态规划算法来解决。
3.贪心法贪心法在每一步都选择当前最优解,然后继续向下一步迭代,直到得到最终解。
贪心法通常快速且简单,但不能保证得到全局最优解,有时只能得到近似解。
经典的贪心算法有霍夫曼编码和最小生成树算法等。
4.回溯法回溯法通过不断尝试和回溯来达到求解问题的目的。
在过程中,若当前路径不能满足问题的条件,就退回到上一步重新选择路径。
回溯法广泛应用于解决组合、排列、图的遍历等问题。
5.枚举法枚举法是穷举所有可能的解,然后从中选择最优解的一种方法。
枚举法常用于求解排列、组合等问题。
虽然在大规模问题上效率较低,但是在规模较小的问题上,可以得到精确的解。
6.分支限界法分支限界法通过不断扩展最优解的空间,并用界限函数排除不可能达到最优解的部分空间,从而提高算法的效率。
分支限界法常用于解决最优化问题,如旅行商问题、0-1背包问题等。
7.近似算法近似算法是用于求解NP难问题的一种方法,通过折中计算精确性和效率,给出一个近似解。
近似算法的设计思路包括贪心法、局部、随机化等。
近似算法不保证得到全局最优解,但可以在多项式时间内给出一个接近最优解的解。
8.随机算法随机算法利用随机数的性质来解决问题,通过随机挑选可能的解进行,以期望找到一个满足条件的解。
随机算法通常用于解决优化问题,如模拟退火算法和遗传算法等。
总之,算法的技术手段是多种多样的,通过合理的选择和组合,可以提高算法的效率和准确性。
贪心算法最短路径问题c语言代码

贪心算法最短路径问题c语言代码贪心算法最短路径问题C语言代码在计算机算法的领域中,贪心算法是一种常见的解决问题的方法。
贪心算法是一种寻找最优解的方法,就是在每个步骤中都采取最优的选择,这样每一步的最优解最终就可以得到整体的最优解。
在实际应用中,贪心算法通常被用于NP问题的解决,例如最短路径问题。
本文将介绍如何用C语言实现贪心算法解决最短路径问题。
1. 最短路径问题概述最短路径问题是一种图论问题,是指在一个有权重的有向图或无向图中,从一个指定的起点节点到达一个指定终点节点的最短路径问题。
在实际应用中,最短路径问题的应用非常广泛,例如地图导航、网络寻路、信息传递等等。
2. 贪心算法的原理贪心算法是一种自顶向下的设计方法,它主要依赖与一种贪心的选择方法。
在每个步骤中,都会选择能够最优化当前直接的步骤的答案。
因此,当遇到问题难以确定最优解时,可以使用贪心算法。
一般来说,贪心算法的优点是简单易懂,并且在特定情况下能够得到准确的答案。
3. C语言代码实现快速查找从起点到所有节点的距离是这个问题的关键,可以使用某种最短路算法,例如Dijkstra算法或贪心算法。
在这里,我们使用贪心算法解决最短路径问题。
以下是C语言代码示例:#include <stdio.h> #include <stdlib.h> #include <string.h>#define V 6int min_distance(int distance[], int visited[]) { int min_index, min_distance = INT_MAX;for (int i = 0; i < V; i++) { if (visited[i] == 0 && distance[i] <= min_distance){ min_distance = distance[i]; min_index = i; } }return min_index; }int dijkstra(int graph[V][V], int source, int destination) { int distance[V], visited[V], count; memset(distance, 0, sizeof(distance)); memset(visited, 0, sizeof(visited));for (int i = 0; i < V; i++){ distance[i] = INT_MAX; }distance[source] = 0;for (count = 0; count < V - 1; count++){ int u = min_distance(distance, visited);visited[u] = 1;for (int v = 0; v < V; v++){ if (!visited[v] && graph[u][v] &&distance[u] != INT_MAX && distance[u] + graph[u][v]< distance[v]) { distance[v] =distance[u] +graph[u][v]; } } }return distance[destination]; }int main() { int graph[V][V] = { { 0, 1, 0,0, 0, 0 }, { 0, 0, 9, 0, 0,0 }, { 2, 0, 0, 3, 0, 1 }, { 0, 0, 0, 0, 2, 0 }, { 4,6, 0, 2, 0, 0 }, { 0, 0, 0,0, 1, 0 } };int source = 0, destination = 5;int distance = dijkstra(graph, source,destination);printf("The shortest distance from node %dto %d is: %d\n", source, destination, distance);return 0; }4. 结尾在本文中,我们介绍了贪心算法解决最短路径问题的原理和C语言代码实现。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
算法综合实验报告学号: 1004111107 姓名:黄琼莹一、实验内容:分别用动态规划、贪心及分支限界法实现对TSP问题(无向图)的求解,并至少用两个测试用例对所完成的代码进行正确性及效率关系上的验证。
二、程序设计的基本思想、原理和算法描述:(包括程序的数据结构、函数组成、输入/输出设计、符号名说明等)1、动态规划法(1)数据结构:利用二进制来表示集合,则集合S可由一个十进制数x相对应,此x所对应的二进制数为y,如果y的第k位为1,则表示k存在集合S中。
例如:集合S={0,1}(其子集合为{}{0}{1}{01}),我们用二进制数11(所对应十进制数为3)表示S,11中右手边第1个数为1表示0在集合S中,右手边第二个数为1表示1在集合S中,其他位为0表示其它数字不在集合S中;同理,集合S={0,2}(其子集合为{}{0}{2}{02}可用二进制数101(所对应十进制数为5)表示(右手边第1个数为1表示0在集合S中,右手边第二个数为0表示1不在集合S中,右手边第3个数为1表示2在集合S中,则说明0,2在集合中,1不在集合中。
(2)函数组成getmin():获得该数组的最小值;getJ():根据2进制j和j中1的个数找下一个jgetnextj():返回下一个j的十进制数(3)输入/输出设计本题通过键盘进行输入,通过屏幕进行输出由于题目的输入要求是:第一行输入一个整数n(2<=n<=10),接下来的n行,每行输入n-1个整数,表示i与除了自己之外的所有点之间的距离,按点的编号从小到大顺序输入可以设计两个for循环来实现数据的输入,外层for循环实现一行一行地输入,内层for循环实现某一行中数据的输入53 1 5 83 6 7 91 6 4 25 7 4 38 9 2 3(4)符号名说明N:节点数,即城市的数目matr[20][20]:存邻接矩阵d[20][40000]={0}:存动态填表数据min:花费的最小值,即答案jlist[20]:存放j的二进制数组V[20]:标记节点是不是被访问过tmpres[20]:存放结果的数组(5)算法描述假设从顶点i出发,令d(i,V’)表示从顶点i出发经过V’中各个顶点一次且仅一次,最后回到出发点i的最短路径的长度,开始时,V’=V-{i},于是,旅行商问题的动态规划函数为:d(i,V’) = min{c ik + d(k,V’-{k})} (k∈V’) 1)d(k,{}) = c ki (k ≠ i) 2)简单来说,就是用递归表达:从出发点0到1号点,假设1是第一个,则剩下的路程就是从1经过剩下的点最后回到0点的最短路径. 所以当V’为空的时候, d(k,{}) = c ki (k ≠ i), 找的是最后一个点到0点的距离.递归求解1之后,再继续求V’之中剩下的点,最后找出min.如果按照这个思想直接做,对于每一个i都要递归剩下的V中所有的点,所以这样的时间复杂度就近似于N!,其中有很多重复的工作.可以从小的集合到大的集合算,并存入一个二维数组,这样当加入一个节点时,就可以用到之前的结果,如四个点的情况:邻接矩阵:node 0 1 2 30 5 3 21 5 7 92 3 7 123 2 9 12动态填表:表中元素代表第i个节点经过V集合中的点最后到0点的最短值.如果有多个值,取其中最小的一个.i\Vj 0 1 2 3 1,2(取min) 1,3(取min) 2,3(取min) 1,2,3(取min)0 c[0][i]+d[i][v’]=211 5 10 11 {c[1][2]+d[2][{3}]=21,c[1][3]+d[3][{2}]=24}2 3 12 14 {c[2][1]+d[1][{3}]=18,c[2][3]+d[3][{1}]=26}3 2 14 15 {c[3][1]+d[1][{2}]=19,c[3][2]+d[2][{1}]=24}这样一共循环(2^(N-1)-1)*(N-1)次,就把时间复杂度缩小到O(N*2N )的级别.核心伪代码如下:{for (i =1;i<n;i++) //初始化第0列d[i][0]=c[i][0];for( j=1;j<2^(N-1)-1;j++)for(i=1 ; i<n ;i++){if(子集Vj中不包含i){对Vj中的每个元素k,计算d[i][Vj] = min{c[i][k] + d[k][{Vj-k}] | 每一个k∈Vj};}}对V[2^(n-1)-1]中的每个元素k,计算:d[0][2^(n-1)-1] = min{c[0][k] + d[k][2^(n-1)-2]};输出最短路径:d[0][2^(n-1)-1];}2、贪心法(1)数据结构数组a[][]存放邻接矩阵,取到邻接点的最小权值然后直接跳到所对应节点。
(2)函数组成无(3)输入/输出设计5 43 1 5 8 3 6 73 6 7 9 5 2 31 6 42 6 4 25 7 4 3 3 7 58 9 2 3(4)符号名说明a[][]:存放邻接矩阵vis[]:标记已经过的节点sum :最小花费(5)算法描述贪心法的方法是在每个节点中找到与其他节点的最小距离,并且有顺序下面把城市和到目的地之间的关系用一个矩阵表示。
1 2 3 4 51 max 3 4 6 22 3 max 4 6 53 3 2 max4 44 2 3 1 max 65 46 5 2 max由于使用贪婪算法,每次都选择当前最短的目的地,所以应该会这么走:1-5-4-3-2-1 花费值是2+2+1+2+3=10。
可以明显看出选择一个城市出发,时间复杂性为O(n^2)。
3、分支限界法(1)数据结构利用最小堆取得最小数。
用到了堆的数据结构。
结构体数据结构。
(2)函数组成InitMiniHeap():初始化堆put():入堆RemoveMiniHeap():出堆Traveler():解决TSP问题(3)输入/输出设计5 43 1 5 8 3 6 73 6 7 9 5 2 31 6 42 6 4 25 7 4 3 3 7 58 9 2 3(4)符号名说明MAX_CITY_NUMBER :城市最大数目MAX_COST :两个城市之间费用的最大值City_Graph[MAX_CITY_NUMBER][MAX_CITY_NUMBER]:表示城市间边权重的数组City_Size:表示实际输入的城市数目Best_Cost:最小费用Best_Cost_Path[MAX_CITY_NUMBER]:最小费用时的路径struct MiniHeap:堆(5)算法描述①算法开始时创建一个最小堆,用于表示活结点优先队列②堆中每个结点的子树费用的下界lcost值是优先队列的优先级。
③接着算法计算出图中每个顶点的最小费用出边并用minout记录。
④如果所给的有向图中某个顶点没有出边,则该图不可能有回路,算法即告结束。
⑤如果每个顶点都有出边,则根据计算出的minout作算法初始化。
算法的while循环体完成对排列树内部结点的扩展。
对于当前扩展结点,算法分2种情况进行处理:①首先考虑s=n-2的情形,此时当前扩展结点是排列树中某个叶结点的父结点。
如果该叶结点相应一条可行回路且费用小于当前最小费用,则将该叶结点插入到优先队列中,否则舍去该叶结点。
②当s<n-2时,算法依次产生当前扩展结点的所有儿子结点。
由于当前扩展结点所相应的路径是x[0:s],其可行儿子结点是从剩余顶点x[s+1:n-1]中选取的顶点x[i],且(x[s],x[i])是所给有向图G中的一条边。
对于当前扩展结点的每一个可行儿子结点,计算出其前缀(x[0:s],x[i])的费用cc和相应的下界lcost。
当lcost<bestc时,将这个可行儿子结点插入到活结点优先队列中。
算法中while循环的终止条件是排列树的一个叶结点成为当前扩展结点。
当s=n-1时,已找到的回路前缀是x[0:n-1],它已包含图G的所有n个顶点。
因此,当s=n-1时,相应的扩展结点表示一个叶结点。
此时该叶结点所相应的回路的费用等于cc和lcost的值。
剩余的活结点的lcost值不小于已找到的回路的费用。
它们都不可能导致费用更小的回路。
因此已找到叶结点所相应的回路是一个最小费用旅行售货员回路,算法可结束。
算法结束时返回找到的最小费用,相应的最优解由数组v给出。
三、源程序及注释:1、动态规划法#include <iostream>#include <math.h>#include <stdio.h>#include <ctime>#include <algorithm>#define max 0x7ffffffusing namespace std;int N;//节点数int matr[20][20];//存邻接矩阵int d[20][40000]={0};//存动态填表数据int getmin(int *sum)//返回该数组中最小非零值{int i = 0;int min = -1,k;for(;i<N;i++){if((min < 0 && sum[i]>0) || (sum[i]>0 && sum[i]<min)){min = sum[i];k = i;}}return min;}void getJ(int jlist[], int c, int n)//根据2进制j和j中1的个数找下一个j{int i = n-1,j;int tmp = 1 , result = 0;while(!jlist[i])i--;//最高位的1的位臵j = i-1;while(jlist[j])j--;//找最高位1之下的最高0if(!jlist[n-1])//若最高位不为1{//将为1的最高一位向上移一位jlist[i]=0;jlist[i+1]=1;}else if(n-1-j==c)//若最高位为1,且j为当前1的个数所能组成的最大的j{for(i=0;i<n;i++)jlist[i]=0;for(i=0;i<c+1;i++)jlist[i]=1;}else//最高位为1,且在当前数目1下还能变更大{//将和最高位之间有隔0的最高的1上移一位,并把这个1之后的所有1 拉到紧邻它后面int k;k=n-1-j;//最高位一共有多少相邻的1while(!jlist[j])j--;//找和最高位隔着0的最高1for(i=0;j+i<n;i++)jlist[j+i]=0;for(i=0;i<=k;i++)jlist[j+i+1]=1;}}int getnextj( int j ){int nextj = 0;//下一个jint c=0;//计数j的二进制有几个1int jlist[20]={0};//存放j的二进制数组int i=0;int tmp = 1;while(j){if(j%2){c++;jlist[i++]=1;}else{jlist[i++]=0;}j/=2;}getJ(jlist,c,N-1);//处理jlistfor(i=0;i<N-1;i++)//将jlist中的2进制转换成int返回{if(jlist[i])nextj += tmp;tmp *= 2;}return nextj;}int main(){int i,j;int min;scanf("%d",&N);//读入点数for(i = 0; i < N; i++)//读入邻接矩阵{for(j = 0;j < N; j++){if(i!=j)scanf("%d",&matr[i][j]);}matr[i][i] = max ;}int V[20]={0};for(i = 0; i < N; i++)V[i]=1;//将所有点设臵为未经过点V[0]=0;//以第0个点为出发点//2^n次方解法for (i =1;i<N;i++) //初始化第0列d[i][0]=matr[i][0];for(j=1;j<pow(2,N-1)-1;j=getnextj(j))for(i=1; i<N ;i++){if(!(j & ( 1<<(i-1) )))//如果集合j中不包含i{//对V[j]中的每个元素k,计算d[i][j] = min{matr[i][k] + d[k][{j-k}]};int jlist[20]={0}; //存放j的二进制数组int tmpres[20]={0};int c=0,k=j,l;while(k){if(k%2){jlist[c++]=1;}else{jlist[c++]=0;}k/=2;}c=0;for(l=0;l<N;l++){if(jlist[l]){tmpres[c++]=matr[i][l+1] + d[l+1][j-(1<<l)];}}d[i][j] = getmin(tmpres);}}int tmpres[20]={0};j = pow(2,N-1)-1;for(i=1;i<N;i++){tmpres[i]=matr[0][i] + d[i][ j - (1<<(i-1) )];}min = getmin(tmpres);//对V[2^(n-1)-1]中的每个元素k,计算:// d[0][2^(n-1)-1] = min{matr[0][k] + d[k][2^(n-1)-2]};//输出最短路径:d[0][2^(n-1)-1];printf("%d\n",min);getchar();return 0;}2、贪心法#include <iostream>#include <stdio.h>#include <cmath>#include <string.h>#define max 10000000using namespace std ;int a[20][20] ;int vis[20] ;int main(){int n , j , i ;memset(vis , 0 , sizeof(vis)) ;scanf("%d" , &n) ;for(i = 0 ; i < n ; i ++){for(j = 0 ; j < n ; j ++){if(i != j){scanf("%d" , &a[i][j]) ;}}a[i][i] = max ;}int sum = 0 , count = 0;int cur , k = 0 ;i = 0 ;vis[0] = 1 ;while(count!=n){cur = max ;for(j = 0 ; j < n ; j ++)//找到邻接点最小的权值,并返回与其相连的节点 {if(a[i][j] < cur && vis[j] == 0 && i != j){cur = a[i][j] ;k = j ;}}sum += a[i][k] ;i = k ;vis[k] = 1 ;count ++ ;if(count == n)//当经过四个节点时,返回第一个节点时将第一个节点标记为0 {vis[0] = 0 ;}}printf("%d\n" , sum) ;return 0 ;}3、分支限界法#include <stdio.h>#include <iostream>using namespace std;#define MAX_CITY_NUMBER 10 //城市最大数目#define MAX_COST 10000000 //两个城市之间费用的最大值int City_Graph[MAX_CITY_NUMBER][MAX_CITY_NUMBER];//表示城市间边权重的数组int City_Size; //表示实际输入的城市数目int Best_Cost; //最小费用int Best_Cost_Path[MAX_CITY_NUMBER];//最小费用时的路径typedef struct Node{int lcost; //优先级int cc; //当前费用int rcost; //剩余所有结点的最小出边费用的和int s; //当前结点的深度,也就是它在解数组中的索引位置int x[MAX_CITY_NUMBER]; //当前结点对应的路径struct Node* pNext; //指向下一个结点}Node;//---------------------定义堆和相关对操作--------------------------------typedef struct MiniHeap{Node* pHead; //堆的头}MiniHeap;//初始化void InitMiniHeap(MiniHeap* pMiniHeap){pMiniHeap->pHead = new Node;pMiniHeap->pHead->pNext = NULL;}//入堆void put(MiniHeap* pMiniHeap,Node node){Node* next;Node* pre;Node* pinnode = new Node; //将传进来的结点信息copy一份保存//这样在函数外部对node的修改就不会影响到堆了 pinnode->cc = ;pinnode->lcost = node.lcost;pinnode->pNext = node.pNext;pinnode->rcost = node.rcost;pinnode->s = node.s;pinnode->pNext = NULL;for(int k=0;k<City_Size;k++){pinnode->x[k] = node.x[k];}pre = pMiniHeap->pHead;next = pMiniHeap->pHead->pNext;if(next == NULL){pMiniHeap->pHead->pNext = pinnode;}else{while(next != NULL){if((next->lcost) > (pinnode->lcost)){ //发现一个优先级大的,则置于其前面pinnode->pNext = pre->pNext;pre->pNext = pinnode;break; //跳出}pre = next;next = next->pNext;}pre->pNext = pinnode; //放在末尾}}//出堆Node* RemoveMiniHeap(MiniHeap* pMiniHeap){Node* pnode = NULL;if(pMiniHeap->pHead->pNext != NULL){pnode = pMiniHeap->pHead->pNext;pMiniHeap->pHead->pNext = pMiniHeap->pHead->pNext->pNext;}return pnode;}//---------------------分支限界法找最优解-------------------------------- void Traveler(){int i,j;int temp_x[MAX_CITY_NUMBER];Node* pNode = NULL;int miniSum; //所有结点最小出边的费用和int miniOut[MAX_CITY_NUMBER];//保存每个结点的最小出边的索引MiniHeap* heap = new MiniHeap; //分配堆InitMiniHeap(heap); //初始化堆miniSum = 0;for (i=0;i<City_Size;i++){miniOut[i] = MAX_COST; //初始化时每一个结点都不可达for(j=0;j<City_Size;j++){if (City_Graph[i][j]>0 && City_Graph[i][j]<miniOut[i]){//从i到j可达,且更小miniOut[i] = City_Graph[i][j];}}if (miniOut[i] == MAX_COST){// i 城市没有出边Best_Cost = -1;return ;}miniSum += miniOut[i];}for(i=0;i<City_Size;i++){ //初始化的最优路径就是把所有结点依次走一遍Best_Cost_Path[i] = i;}Best_Cost = MAX_COST; //初始化的最优费用是一个很大的数pNode = new Node; //初始化第一个结点并入堆pNode->lcost = 0; //当前结点的优先权为0 也就是最优pNode->cc = 0; //当前费用为0(还没有开始旅行)pNode->rcost = miniSum; //剩余所有结点的最小出边费用和就是初始化的miniSumpNode->s = 0; //层次为0pNode->pNext = NULL;for(int k=0;k<City_Size;k++){pNode->x[k] = Best_Cost_Path[k]; //第一个结点所保存的路径也就是初始化的路径}put(heap,*pNode); //入堆while(pNode != NULL && (pNode->s) < City_Size-1){//堆不空不是叶子for(int k=0;k<City_Size;k++){Best_Cost_Path[k] = pNode->x[k] ; //将最优路径置换为当前结点本身所保存的 }// pNode 结点保存的路径中的含有这条路径上所有结点的索引// x路径中保存的这一层结点的编号就是x[City_Size-2]// 下一层结点的编号就是x[City_Size-1]if ((pNode->s) == City_Size-2){ //是叶子的父亲int edge1 = City_Graph[(pNode->x)[City_Size-2]][(pNode->x)[City_Size-1]];int edge2 = City_Graph[(pNode->x)[City_Size-1]][(pNode->x)[0]];if(edge1 >= 0 && edge2 >= 0 && (pNode->cc+edge1+edge2) < Best_Cost){//edge1 -1 表示不可达//叶子可达起点费用更低Best_Cost = pNode->cc + edge1+edge2;pNode->cc = Best_Cost;pNode->lcost = Best_Cost; //优先权为Best_CostpNode->s++; //到达叶子层 }}else{ //内部结点for (i=pNode->s;i<City_Size;i++){ //从当前层到叶子层if(City_Graph[pNode->x[pNode->s]][pNode->x[i]] >= 0){ //可达//pNode的层数就是它在最优路径中的位置int temp_cc = pNode->cc+City_Graph[pNode->x[pNode->s]][pNode->x[i]];int temp_rcost = pNode->rcost-miniOut[pNode->x[pNode->s]];//下一个结点的剩余最小出边费用和 //等于当前结点的rcost减去当前这个结点的最小出边费用if (temp_cc+temp_rcost<Best_Cost){ //下一个结点的最小出边费用和小于当前的最优解,说明可能存在更优解for (j=0;j<City_Size;j++){ //完全copy路径,以便下面修改 temp_x[j]=Best_Cost_Path[j];}temp_x[pNode->x[pNode->s+1]] = Best_Cost_Path[i];//将当前结点的编号放入路径的深度为s+1的地方temp_x[i] = Best_Cost_Path[pNode->s+1]; //??????????????//将原路//径中的深度为s+1的结点编号放入当前路径的//相当于将原路径中的的深度为i的结点与深度W为s+1的结点交换Node* pNextNode = new Node;pNextNode->cc = temp_cc;pNextNode->lcost = temp_cc+temp_rcost;pNextNode->rcost = temp_rcost;pNextNode->s = pNode->s+1;pNextNode->pNext = NULL;for(int k=0;k<City_Size;k++){pNextNode->x[k] = temp_x[k];}put(heap,*pNextNode);delete pNextNode;}}}}pNode = RemoveMiniHeap(heap);}}int main(){int i,j;scanf("%d",&City_Size);for(i=0;i<City_Size;i++){for(j=0;j<City_Size;j++){if(i!=j){scanf("%d",&City_Graph[i][j]);}City_Graph[i][i] = MAX_COST ;}}Traveler();printf("%d\n",Best_Cost);return 0;}四、运行输出结果:(贴出程序运行完成时的屏幕截图或者输出文件的内容)1、动态规划法2、贪心法3、分支限界法五、调试和运行程序过程中产生的问题及采取的措施:1、动态规划问题:对集合的控制有错误,不能正确的表示集合。