NPC问题及其解决方法(回溯法、动态规划、贪心法、深度优先遍历)
NP问题(Non-deterministic Polynomial ):多项式复杂程度的非确定性问题,这些问题无法根据公式直接地计算出来。比如,找大质数的问题(有没有一个公式,你一套公式,就可以一步步推算出来,下一个质数应该是多少呢?这样的公式是没有的);再比如,大的合数分解质因数的问题(有没有一个公式,把合数代进去,就直接可以算出,它的因子各自是多少?也没有这样的公式)。
NPC问题(Non-deterministic Polynomial complete):NP完全问题,可以这么认为,这种问题只有把解域里面的所有可能都穷举了之后才能得出答案,这样的问题是NP里面最难,但是这样算法的复杂程度,是指数关系。一般说来,如果要证明一个问题是NPC问题的话,可以拿已经是NPC问题的一个问题经过多项式时间的变化变成所需要证明的问题,那么所要证明的问题就是一个NPC问题了。NPC问题是一个问题族,如果里面任意一个问题有了多项式的解,即找到一个算法,那么所有的问题都可以有多项式的解。
著名的NPC问题:
背包问题(Knapsack problem):01背包是在M件物品取出若干件放在空间为W的背包里,每件物品的体积为W1,W2……Wn,与之相对应的价值为V1,V2……Vn。求出获得最大价值的方案。
旅行商问题(Traveling Saleman Problem,TSP),该问题是在寻求单一旅行者由起点出发,通过所有给定的需求点之后,最后再回到原点的最小路径成本。
哈密顿路径问题(Hamiltonian path problem)与哈密顿环路问题(Hamiltonian cycle problem)为旅行推销员问题的特殊案例。哈密顿图:由指定的起点前往指定的终点,途中经过所有其他节点且只经过一次。
欧拉回路(从图的某一个顶点出发,图中每条边走且仅走一次,最后回到出发点;如果这样的回路存在,则称之为欧拉回路。)与欧拉路径(从图的某一个顶点出发,图中每条边走且仅走一次,最后到达某一个点;如果这样的路径存在,则称之为欧拉路径。)
- 无向图欧拉回路存在条件:所有顶点的度数均为偶数。
- 无向图欧拉路径存在条件:至多有两个顶点的度数为奇数,其他顶点的度数均为偶数。
- 有向图欧拉回路存在条件:所有顶点的入度和出度相等。
- 有向图欧拉路径存在条件:至多有两个顶点的入度和出度绝对值差1(若有两个这样的顶点,则必须其中一个出度大于入度,另一个入度大于出度),其他顶点的入度与出度相等。
01背包问题解决方法
法I:回溯法递归
public:
void Knapsack(int *w,int *v, int c,int n){//w:容量;v:value
this->c = c;
this->n = n;
bestv = ;
bool x[n] = {false}; //x: 是否选择这个物品
backtracking(w,v,x,);
} void backtracking(int depth, int *w, int *v, bool *x){
if(depth >= n){
if(tmpV > bestv){
bestv = tmpV;
for(int i = ; i < n; i++){
bestx[i] = x[i];
}
}
return;
}
if(tmpW + w[depth] <= c){ //加入当前元素
x[i] = true;
tmpW += w[depth];
tmpV += v[depth];
backtracking(depth+, w, v, x);
tmpV -= v[depth]; //backtrack
tmpW -= w[depth];
x[i] = false;
} backtracking(depth+, w, v, x);//不加入当前元素
} private:
int bestv; //最优方法的价值
int* bestx; //最优方法选取的物品
int tmpV; //已有价值
int tmpW; //已使用的容量
int c; //背包容量
int n; //物品数量
法II:动态规划
1。定义阶段:v[i-1]表示第i个物品的价值
2。定义状态:V[n+1][C]前i个物品装入容量为j的背包中获得的最大价值
3。状态转移方程:V[i][j]=max(V[i-1][j],V[i-1][j-w[i-1]]+v[i-1]);
4。定义边界条件:V[i][0]=0;V[0][j]=0;
int KnapSack(int n,int w[],int v[],int x[],int C){
int V[n+][C];//前i个物品装入容量为j的背包中获得的最大价值
int i,j;
for(i=;i<=n;i++)
V[i][]=;
for(j=;j<=C;j++)
V[][j]=;
for(i=;i<=n-;i++)
for(j=;j<=C;j++)
if(j < w[j]) V[i][j]=V[i-][j];
else V[i][j]=max(V[i-][j],V[i-][j-w[i-]]+v[i-]);
//标示哪些物品被放入
j=C;
for(i=n;i>;i--)
if(V[i][j]>V[i-][j]){
x[i-]=;
j=j-w[i-];
}
else x[i-]=;
return V[n][C];
}
法III: 贪心法解决普通背包问题
普通背包问题:与0-1背包问题类似,所不同的是在选择物品i装入背包时,可以选择物品i的一部分,而不一定要全部装入背包,1≤i≤n。
贪心准则:每一项计算yi=vi/wi,再按比值的降序来排序,从第一项开始装背包,然后是第二项,依次类推,尽可能的多放,直到装满背包。适用于普通背包问题,但不适用于01背包问题。
旅行商问题解决方法
法I:回溯法递归
int bestd;
vector< int > bestv;//保存最优解路径上的节点
int tmpSum;
vector< int > tmpV; //暂时保存路径上的节点
unordered_set visited; void shortest( int **d,int n){
bestd = INT_MAX;
dfs(d,n,);
} void dfs (int **d,int n, int depth){
tmpV.push_back(depth);
if(tmpV.size()==n){
tmpSum += d[depth][]; if(tmpSum < bestd){
bestd = tmpSum;
bestv = tmpV;
}
tmpSum -= d[depth][]; //backtrack
tmpV.pop();
return;
}
visited.insert(depth);
for(int i = ; i < n; i ++){
if(visited.find(i) != visited.end() && tmpSum + d[depth][i] < bestd){
tmpSum += d[depth][i];
dfs(d,n,i);
tmpSum -= d[depth][i]; //backtrack
}
}
visited.erase(depth); //backtrack
tmpV.pop_back();
}
法II:动态规划
1。定义阶段:v[i-1]表示第i个物品的价值
2。定义状态:F[i][j]表示当前从i结点出发已访问j中节点的情况下的最短距离。其中,i表示当前访问的节点,i∈[0,n-1];j=已访问的节点的bitmap,j∈[0,2^(n-1)-1]
3。状态转移方程:F[i][j] = min{ min,D[i][k] + F[k][j-(int)pow(2,k-1)]) }
4。定义边界条件:F[i][0] = D[i][0]即表示节点i到第一个节点的距离,D是原图的邻接矩阵
void tsp(int** D, int n){
int i,j,k,min,temp;
int b=(int)pow(,n-); //已遍历的节点bitmap(除了最后一个节点,每个节点有选择及不选择两种情况)
//申请二维数组F和M
int ** F = new int* [n];//n行b列的二维数组,存放阶段最优值
int ** M = new int* [n];//n行b列的二维数组,存放最优策略
for(i=;i < n; i++){
F[i] = new int[b];
M[i] = new int[b];
}
//初始化F[][]和M[][]
for(i=;i < n; i++)
for(j=;j < n; j++){
F[j][i] = -;
M[j][i] = -;
}
for(i=;i < n; i++) F[i][] = D[i][];
//状态转移
for(i=;i < b; i++)
for(j=;j < n; j++){
if( ((int)pow(,j-) & i) == ){//结点j不在i表示的集合中
min=INT_MAX;
for(k=;k < n; k++ ){ //从已访问过的节点中找出一个到节点j的距离最短
if( (int)pow(,k-) & i ){//非零表示结点k在集合中
temp = D[j][k] + F[k][i-(int)pow(,k-)];//去掉k结点即将k对应的二进制位置0
if(temp < min){
min = temp;
F[j][i] = min;//保存阶段最优值
M[j][i] = k;//保存最优决策
}
}
}
}
//最后一列,即总最优值的计算
min=INT_MAX;
for(k=;k < n; k++ ){
//b-1的二进制全1,表示全集
temp = D[][k] + F[k][b- - (int)pow(,k-)]; //去掉k
if(temp < min){
min = temp;
F[][b-] = min;
M[][b-] = k;
}
}
cout<<"最短路径长度:"<<F[][b-]<<endl;//最短路径长度
cout<<"最短路径(编号0—n-1):"<<""; //最短路径上的节点
for(i=b-,j=; i>; ){//i的二进制是5个1,表示集合{1,2,3,4,5}
j = M[j][i];//下一步去往哪个结点
i = i - (int)pow(,j-);//从i中去掉j结点
cout<<"->"<<j;
}
cout<<"->0"<<endl;
}
法III: 启发式贪心法
采用启发式贪心算法。对于那些受大自然的运行规律或者面向具体问题的经验、规则启发出来的方法,人们常常称之为启发式算法(Heuristic Algorithm)。启发式算法得到的解只是近似最优解。步骤:
(1)从旅行商问题的n个城市中选择1个城市构成部分解序列T1={c1},共有n种初始组合。
(2)从部分解序列之外的城市中选择一个新的城市k,插到原有的部分解序列Tk-1={c1,c2,…,ck-1}中,得到新的部分解列Tk={c1,c2,…,ck,…,ck-1}。新的城市ck及插入位置由改进的贪心法确定。
用Tk-1={c1, c2,…,ck-1}表示已确定的部分解序列,则由min(d(ci,ck)+d(ck,ci+1) -d(ci,ci+1)),ci,ci+1∈Tk-1,ck∈NP完全问题确定插入的城市ck及插入位置(ci,ck,ci+1)
(3)用冒泡法对新的部分解序列Tk中的每个城市进行可能优化游路的换位、移位和倒位操作,直到不再能通过这些操作优化游路。
对于旅行商问题的一个解序列,可以通过换位、移位和倒位三种基本的次序变换操作,改变原来解序列的排列次序,得到新的解序列。其它游路改进的启发式操作,都可以由这三种基本操作组合而成。
换
位操作(exchange):将解序列中第i个元素ci与第j个元素cj的位置交换。ΔD换位=(d(ci-
1,ci)+d(ci,ci+1)+d(cj-1,cj)+d(cj,cj+1))-(d(ci-1,cj)+d(cj,ci+1)+d(cj-1,ci)+d(ci,cj+1))
移
位操作move
:移位操作相当于选择(Or2opt)操作,它将解序列中第i个元素ci移动到第j个元素cj之后的位置上。ΔD移位=(d(ci-
1,ci)+d(ci,ci+1)+d(cj,cj+1))-(d(ci-1,ci+1)+d(cj,ci)+d(ci,cj+1))
倒位操作(inverse) :倒位操作相当于选择操作取r=2的情况,它将解序列中从第i个元素ci到第j个元素cj之间的元素的顺序前后颠倒。倒位操作的性能指标为:
ΔD倒位=(d(ci-1,ci)+d(cj,cj+1))-(d(ci-1,cj)+d(ci,cj+1))
(4)如果部分解序列的长度k
欧拉路径求解方法
法I:Fleury算法(深度优先遍历)
数据结构:栈
int stk[];
int top;
int N, M, ss, tt;
int mp[][]; void dfs(int x) { //深度优先遍历
stk[top++] = x;
for (int i = ; i <= N; ++i) {
if (mp[x][i]) {
mp[x][i] = mp[i][x] = ; // 删除此边
dfs(i);
break;
}
}
} void fleury(int start) {
bool brige;
top = ; //top永远指向下一个要入栈元素的存放位置
stk[top++] = start; // 将起点放入Euler路径中
while (top > ) {
brige = true; //割边(桥,最后一条连通外界的边)也已经遍历了
for (int i = ; i <= N; ++i) { // 遍历节点
if (mp[stk[top-]][i]) { //如果与栈顶节点有边
brige = false;
break;
}
}
if (brige) { // 如果没有点可以扩展,输出并出栈,下一个while循环的时候会搜索下一个栈顶元素的其他路径
printf("%d ", stk[--top]);
} else { // 否则继续搜索欧拉路径
dfs(stk[--top]);
} //从dfs返回,说明从节点stk[top-1]开始的深度遍历已结束,下面找与它连通的下一个节点(广度遍历)。
}
} int main() {
int x, y, deg, num;
while (scanf("%d %d", &N, &M) != EOF) {
memset(mp, , sizeof (mp));
for (int i = ; i < M; ++i) {
scanf("%d %d", &x, &y);
mp[x][y] = mp[y][x] = ;
}
for (int i = ; i <= N; ++i) { //计算节点度数,判断是否符合欧拉路径/欧拉回路的条件
deg = num = ;
for (int j = ; j <= N; ++j) {
deg += mp[i][j];
}
if (deg % == ) {
start = i, ++num; //设置起始点
printf("%d\n", i);
}
}
if (num == || num == ) {
fleury(start);
} else {
puts("No Euler path");
}
}
return ;
}
NPC问题及其解决方法(回溯法、动态规划、贪心法、深度优先遍历)的更多相关文章
- Python基于回溯法解决01背包问题实例
Python基于回溯法解决01背包问题实例 这篇文章主要介绍了Python基于回溯法解决01背包问题,结合实例形式分析了Python回溯法采用深度优先策略搜索解决01背包问题的相关操作技巧,需要的朋友 ...
- 01背包问题(回溯法)python实现
接上一篇,相同的01背包问题,上一篇採用动态规划的方法,如今用回溯法解决. 回溯法採用深度优先策略搜索问题的解.不多说.代码例如以下: bestV=0 curW=0 curV=0 bestx=None ...
- [Leetcode] Backtracking回溯法解题思路
碎碎念: 最近终于开始刷middle的题了,对于我这个小渣渣确实有点难度,经常一两个小时写出一道题来.在开始写的几道题中,发现大神在discuss中用到回溯法(Backtracking)的概率明显增大 ...
- sudo: unable to resolve host XXX 解决方法
执行sudo命令时候,总是提示sudo: unable to resolve host xxx 解决方法: 法1. 在/etc/hosts/添加hosts映射, 如127.0.0.1 xxx 法2. ...
- 使用PDO连接数据库 查询和插入乱码的解决方法
问题:PDO连接数据库后,查询和插入中文到数据库,出现乱码,如图: 解决方法: 法1: try{ $opts_values = array(PDO::MYSQL_ATTR_INIT_COMMAND=& ...
- aaalogo写入中文出错的解决方法
一.软件名称: 二.软件用途: 制作小logo 三.问题: aaalog软件不能支持中文输入. 简单的说该软件不能使用中文纯粹是因为字体不支持的原因, 只要导入相应字体就可以 不知道其他人使用aaal ...
- (回溯法)和为n的所有不增正整数和式分解算法
题目: 利用递归算法输出正整数和为n的所有不增的正整数和式.例如当n=5时,不增的和式如下: 5=5 5=4+1 5=3+2 5=3+1+1 5=2+2+1 5=2+1+1+1 5=1+1+1+1+1 ...
- python常用算法(7)——动态规划,回溯法
引言:从斐波那契数列看动态规划 斐波那契数列:Fn = Fn-1 + Fn-2 ( n = 1,2 fib(1) = fib(2) = 1) 练习:使用递归和非递归的方法来求解斐波那契数 ...
- 回溯法解决N皇后问题(以四皇后为例)
以4皇后为例,其他的N皇后问题以此类推.所谓4皇后问题就是求解如何在4×4的棋盘上无冲突的摆放4个皇后棋子.在国际象棋中,皇后的移动方式为横竖交叉的,因此在任意一个皇后所在位置的水平.竖直.以及45度 ...
随机推荐
- caddy server 默认https && http2的验证
1. 下载 https://caddyserver.com/ 注意选择应该包含的模块,此次包含了git 插件 2. 配置 使用 Caddyfile 内容如下: ro ...
- springboot: 集成freemark模板引擎
1.freemark简介(摘自:http://blog.csdn.net/liaomin416100569/article/details/78349072) 在互联网软件内容网站中 一般首页的访问量 ...
- Fiddler+Firefox
配置置代理了,发现还是不好用!无法抓包: 配置就是在firefox的“选项”,拉到最下面,就能够看到“网络代理”,点进去:手动代理里面输入Fiddler的代理信息(默认127.0.0.1:8888) ...
- Android screencap截屏指令
查看帮助(注意:有的网友错误使用 screencap -v ,结果差不多,因为系统不能识别-v,就自动打印出帮助信息) # screencap -hscreencap -husage: screenc ...
- Linux 根文件系统目录结构
/:根目录 /bin:linux的常用命令 /sbin:linux的常用命令 /lib:库文件(so.elf) /etc:系统配置文件和脚本文件 /sys:驱动相关的信息 /dev:设备节点目录 /p ...
- for, while的用法
for循环求1+2+3+4+....+100 # include <stdio.h> int main(void) { int i; //循环中更新的变量i不能定义成浮点型 ; ; i&l ...
- pandas之DateFrame
float_df = pd.DataFrame((0.45*np.arange(1,9)).reshape(4,2), index=[1,2,3,4], columns=['col_one', 'co ...
- JVM内存管理之JAVA语言的内存管理概述
引言 内存管理一直是JAVA语言自豪与骄傲的资本,它让JAVA程序员基本上可以彻底忽略与内存管理相关的细节,只专注于业务逻辑.不过世界上不存在十全十美的好事,在带来了便利的同时,也因此引入了很多令人抓 ...
- java代码---------打印正三角形
总结:手打一遍 public class aaa{ public static void main(String []args){ for(int i=1;i<10;i++){ for(int ...
- python学习(二十二) Python 中boolean