算法设计与分析实验四:利用回溯法求解01背包问题和TSP问题


【实验内容】
(1)利用回溯法编程求解0-1背包问题,并对算法进行时间复杂性分析;
(2)利用回溯法编程求解TSP问题,并对算法进行时间复杂性分析。
【编译环境】devc++ 5.11
【解题思路】
01背包问题:
1.获取背包容量packvol,物品数量n,物品价值和重量value[],weigth[]

2.确定剪枝方法

3.回溯法求解01背包问题

4.对于复杂性
【参考内容】
一、
什么是剪枝?
剪枝一种可以提高搜索算法时间和空间效率的技巧,经过剪枝和其他优化策略优化过的算法在执行效率上远超一般未经剪枝的算法。甚至有些暴力搜索过不了时限的算法,也可以通过各种剪枝+优化大大缩短算法运行时间,成功通过时效限制。由此可见剪枝对于搜索算法的重要性。因此,剪枝对于学习算法和在工作中与算法打交道的人来说都是一类不得不学的知识点。
对于本问题来说:
剪枝的具体实现在于,如何尽量减少不能得到正确答案的运算。例如在使用回溯法穷举物品装入背包时,已知目前装入的物品总重量已经超过了背包容量,就可以直接判断之后的物品不会在装入背包。从而达到提前回溯的目的。
二、
算法参考了《算法设计与分析第二版》154页的例题素数环问题。但是例题的时间复杂度为n的n次方,所以进行了一些改进,最终时间复杂度达到了2的n次方。
【代码分析】

一、01背包问题

数据生成和表示

数据写入到cpp对应目录下的txt文本文件中
头文件和用于生成数据的函数声明

#include<stdio.h>
#include<time.h>
#include<random>

//宏定义区 
#define MAX 30//背包最大容量 
#define MAXN 30//最大物品数 
#define MAXW 7//物品最大重量 
#define MAXV 7//物品最大价值 

//自定义函数区 
void randomint(int,int,int*,int*);//生成随机数并调用fwritein函数写入文件 
void fwritein(int,int,int*,int*);//将数据写入文件方便分析 

函数定义和调用

void fwritein(int packvol,int n,int* weight,int* value)
{
	FILE* fp = fopen("demo4-1.TXT","w");
	fprintf(fp,"背包容量:%d\n",packvol);
	fprintf(fp,"物品数量:%d\n",n);
	for(int i=0;i<n;i++)
	{
		fprintf(fp,"%d\t%d\t%d\n",i,weight[i],value[i]);
	}
	fclose(fp);
}

void randomint(int packvol,int n,int* weight,int* value)
{
	for(int i=0;i<n;i++)
	{
		weight[i]=rand()%MAXW+1;
		value[i]=rand()%MAXV+1;
	}
	fwritein(packvol,n,weight,value);
	return;
}


int main()
{
	//输入背包问题所需数组信息 
	int n,packvol;
	srand(time(NULL));
	n = rand()%MAXN+1;
	packvol = rand()%MAX+1;
	int w[n],v[n];
	randomint(packvol,n,w,v);
	Pack(n,packvol,w,v);//01背包问题算法
	
	return 0;
}

处理数据需要使用的函数和功能

检查背包中是否有要装入的物品

bool check(int packvol,int w,int k,int*packin)//背包容量,当前背包重量,当前遍历层数,记录遍历情况的数组 
{
	if(w>packvol)
		return false;
	for(int i=0;i<k;i++)
	{
		if(packin[i]==packin[k])
			return false;
	}
	return true;
}

输出运算结果

void print(int* packin,int maxv,int n)
{
	for(int i=0;i<n;i++)
	{
		if(packin[i]!=-1)
		{
			printf("装入物品:编号%-4d\n",packin[i]);
		}
	}
	printf("背包最大价值:%d\n\n",maxv);
}

01背包问题算法实现

推荐在看下面代码之前看一下相应算法书上的约瑟夫环算法的实现方法。
下面的方法并不是模仿二叉树进行的回溯法。

//借鉴算法书154页素数环问题做的.
void Pack(int n,int packvol,int* weight,int* value)//物品数,背包容量,重量数组,价值数组
{
	int packin[n];//记录物品的装入情况
	int w=0;//每次回溯过程中记录物品重量 
	int v=0;//物品价值
	int maxv=0;//最大价值 
	int k=0; 
	for(int i=0;i<n;i++)//初始化 
	{
		packin[i]=-1;
	}
	while(k>=0)
	{
		//packin[k]!=-1表示此种情况可能是经过回溯得到的,或者正在遍历 
		if(packin[k]!=-1)
		{
			w=w-weight[packin[k]];
			v=v-value[packin[k]];
			packin[k]=packin[k]+1;
		}
		//packin[k]==-1表示此种情况还未经过遍历 
		else
		{
			//这两个if else时为了减少时间复杂度
			//k!=0时可以直接在 packin[k-1]+1进行后面的遍历,因为背包中物品装入的顺序不会影响背包的价值。 
			if(k==0)
			{
				packin[k]=packin[k]+1;
			}
			else
				packin[k]=packin[k-1]+1;
		}
		//packin[k]表示物品编号,最大为n-1,当其值为n时表示当前情况已经全部遍历 
		if(packin[k]!=n)
		{
			w=w+weight[packin[k]];
			v=v+value[packin[k]];
		}
		//packin[k]所代表的物品是否额可以装入装入背包 
		while(packin[k]<n) 
		{
			if(check(packvol,w,k,packin)==true)
			{
				break;
			}
			else
			{
				w=w-weight[packin[k]];
				v=v-value[packin[k]];
				packin[k]=packin[k]+1;
				if(packin[k]!=n)
				{
					w=w+weight[packin[k]];
					v=v+value[packin[k]];
				}
			}
		}
		//三种情况 
		//满足剪枝条件,判断当前结果是否相对最优 
		//背包满了 
		if(packin[k]==n)
		{
			if(v>maxv)
			{
				maxv = v;
				print(packin,maxv,n);
			} 
			packin[k--]=-1;
		}
		//到达叶子节点,进行回溯 
		//背包不一定满但是物品装完了 
		else if(k==n-1) 
		{
			if(v>maxv)
			{
				maxv = v;
				print(packin,maxv,n);
			}
			w=w-weight[packin[k]];
			v=v-value[packin[k]];
			packin[k--]=-1;
		}
		//判断下一个节点 
		//背包没满物品也没装完 
		else if(k<n-1)
		{
			k=k+1;
		}
	}
}

数据

回溯法01背包问题数据

输出

回溯法01背包问题输出

复杂度

回溯法01背包复杂度分析

01背包问题源代码

#include<stdio.h>
#include<time.h>
#include<random>

//宏定义区 
#define MAX 20//背包最大容量 
#define MAXN 15//最大物品数 
#define MAXW 7//物品最大重量 
#define MAXV 7//物品最大价值 

//自定义函数区 
void randomint(int,int,int*,int*);//生成随机数并调用fwritein函数写入文件 
void fwritein(int,int,int*,int*);//将数据写入文件方便分析 
void Pack(int,int,int*,int*);//01背包问题主要算法	参数含义: 物品数,背包容量,重量数组,价值数组
bool check(int,int,int,int*);//检查某个物品是否可以装入当前背包  参数含义: 背包容量,当前背包重量,当前遍历层数,记录遍历情况的数组 
void print(int*,int,int); //自定义打印函数 



bool check(int packvol,int w,int k,int*packin)//背包容量,当前背包重量,当前遍历层数,记录遍历情况的数组 
{
	if(w>packvol)
		return false;
	for(int i=0;i<k;i++)
	{
		if(packin[i]==packin[k])
			return false;
	}
	return true;
}

void print(int* packin,int maxv,int n)
{
	for(int i=0;i<n;i++)
	{
		if(packin[i]!=-1)
		{
			printf("装入物品:编号%-4d\n",packin[i]);
		}
	}
	printf("背包最大价值:%d\n\n",maxv);
}

//借鉴算法书154页素数环问题做的.
void Pack(int n,int packvol,int* weight,int* value)//物品数,背包容量,重量数组,价值数组
{
	int packin[n];//记录物品的装入情况
	int w=0;//每次回溯过程中记录物品重量 
	int v=0;//物品价值
	int maxv=0;//最大价值 
	int k=0; 
	for(int i=0;i<n;i++)//初始化 
	{
		packin[i]=-1;
	}
	while(k>=0)
	{
		//packin[k]!=-1表示此种情况可能是经过回溯得到的,或者正在遍历 
		if(packin[k]!=-1)
		{
			w=w-weight[packin[k]];
			v=v-value[packin[k]];
			packin[k]=packin[k]+1;
		}
		//packin[k]==-1表示此种情况还未经过遍历 
		else
		{
			//这两个if else时为了减少时间复杂度
			//k!=0时可以直接在 packin[k-1]+1进行后面的遍历,因为背包中物品装入的顺序不会影响背包的价值。 
			if(k==0)
			{
				packin[k]=packin[k]+1;
			}
			else
				packin[k]=packin[k-1]+1;
		}
		//packin[k]表示物品编号,最大为n-1,当其值为n时表示当前情况已经全部遍历 
		if(packin[k]!=n)
		{
			w=w+weight[packin[k]];
			v=v+value[packin[k]];
		}
		//packin[k]所代表的物品是否额可以装入装入背包 
		while(packin[k]<n) 
		{
			if(check(packvol,w,k,packin)==true)
			{
				break;
			}
			else
			{
				w=w-weight[packin[k]];
				v=v-value[packin[k]];
				packin[k]=packin[k]+1;
				if(packin[k]!=n)
				{
					w=w+weight[packin[k]];
					v=v+value[packin[k]];
				}
			}
		}
		//三种情况 
		//满足剪枝条件,判断当前结果是否相对最优 
		//背包满了 
		if(packin[k]==n)
		{
			if(v>maxv)
			{
				maxv = v;
				print(packin,maxv,n);
			} 
			packin[k--]=-1;
		}
		//到达叶子节点,进行回溯 
		//背包不一定满但是物品装完了 
		else if(k==n-1) 
		{
			if(v>maxv)
			{
				maxv = v;
				print(packin,maxv,n);
			}
			w=w-weight[packin[k]];
			v=v-value[packin[k]];
			packin[k--]=-1;
		}
		//判断下一个节点 
		//背包没满物品也没装完 
		else if(k<n-1)
		{
			k=k+1;
		}
	}
}

 
void fwritein(int packvol,int n,int* weight,int* value)
{
	FILE* fp = fopen("demo4-1.TXT","w");
	fprintf(fp,"背包容量:%d\n",packvol);
	fprintf(fp,"物品数量:%d\n",n);
	for(int i=0;i<n;i++)
	{
		fprintf(fp,"%d\t%d\t%d\n",i,weight[i],value[i]);
	}
	fclose(fp);
}

void randomint(int packvol,int n,int* weight,int* value)
{
	for(int i=0;i<n;i++)
	{
		weight[i]=rand()%MAXW+1;
		value[i]=rand()%MAXV+1;
	}
	fwritein(packvol,n,weight,value);
	return;
}


int main()
{
	//输入背包问题所需数组信息 
	int n,packvol;
	srand(time(NULL));
	n = rand()%MAXN+1;
	packvol = rand()%MAX+1;
	int w[n],v[n];
	randomint(packvol,n,w,v);
	Pack(n,packvol,w,v);//01背包问题算法
	
	return 0;
}

二、TSP问题

【解题思路】
tsp问题:
1.输入图的结点个数和代价矩阵

2.回溯法依次处理结点

3.对于复杂性
【参考内容】
算法书《算法设计与分析第二版》158页中哈密顿回路问题的思路可以用来近似解决tsp问题
参考伪代码如下
回溯法tsp问题参考内容
伪代码第二点x[0]=1;个人认为改成x[0]=0;比较合适,下面代码中也是这么实现的
【代码分析】

TSP算法实现

//算法书158页解决哈密顿回路问题的伪代码可以近似看作解决tsp问题的来使用 
//下面的算法实现是依照书上的伪代码来写的 
void tsp(int* arc,int n)//参数:代价矩阵,结点数 
{
	int k;//k代表经过的顶点数,书上伪代码中没有提到但是代码中使用了 
	int x[n];//存储tsp路径上的顶点
	int visited[n];//存储顶点访问标志 
	int v=0;//tsp问题剪枝要使用到的权值 
	int minv=INF;//记录最小的权值情况 
	//算法书上默认从顶点0开始出发,所以下面也从顶点0开始出发
	//初始化
	for(int i=0;i<n;i++)
	{
		x[i]=0;
		visited[i]=0; 	
	}
	k=1;
	visited[0]=1;
	//伪代码这里写的x[0]=1;建议改正一下 
	x[0]=0;
	while(k>=1)
	{
		x[k]=x[k]+1;
		//若n个节点没有被穷举完 
		if(x[k]<n)
		{
			//若顶点x[k]不在tsp回路上,并且存在路径(x[k],x[k-1])
			//tsp问题是完全图所以一定存在这条路径,故这里省略一个判断 
			//判断权值是否比最小权值大,如果大那么一定不是最短路径直接跳过 
			//从x[k-1]节点到x[k]节点的代价 
			v=v+arc[(x[k-1])*n+x[k]];
			//if(visited[x[k]]==0 && v<minv)
			if(visited[x[k]]==0)
			{
				visited[x[k]]=1;
				//已经形成tsp路径
				if(k==n-1 && v+arc[x[k]*n+0]<minv)
				{
					minv=v+arc[x[k]*n+0];
					print(x,arc,n,0);
					v=v-arc[(x[k-1])*n+x[k]];
					visited[x[k]]=0;
					continue;
				}
				//构成tsp路径部分解 
				else if(v<minv)
				{
					k=k+1;
					continue;
				}
				//不能成为最短路径,直接跳过不处理 
				else
				{
					visited[x[k]]=0;
					v=v-arc[(x[k-1])*n+x[k]];
					continue;
				}
			}
			else//否则搜索下一个节点 
			{
				v=v-arc[(x[k-1])*n+x[k]];
				continue;
			}
		}
		//取消x[k]的访问标志,重置x[k],k=k-1,
		else
		{
			v=v-arc[(x[k-2])*n+x[k-1]];
			visited[x[k-1]]=0;
			x[k]=0;
			k=k-1;
		}
	}
}

数据生成和输出和处理

生成和处理方式同实验二

生成数据

int main() { 
	int node;
	//自动生成方式
	//数据写入文件 
	FILE* fp = fopen("demo4-2.TXT","w");
	//生成随机数 
	srand((unsigned)time(NULL));
	node = MAXNODE;
	//生成数组矩阵,用一维数组表示二维数组 
	int *d = (int*)malloc(sizeof(int)*INF*INF);
	for(int i=0;i<node;i++)
	{
		for(int j=0;j<node;j++)
		{ 
			//行列相等默认最大值 
			if(j == i)
				d[i*node+j] = INF;
			else
				d[i*node+j] = rand()%MAX+1;
			fprintf(fp, "%d\t",d[i*node+j]);
			fprintf(fp, " ");
		}
		fprintf(fp, "\n");
	}
	//关闭文件 
	fclose(fp);
	//调用tsp函数 
   	tsp(d,node);
}

输出数据

void print(int* x,int* arc,int n,int start)//回路上的顶点数组,权值数值,最大节点,起点 
{
	int sum=0;
	printf("起点---代价---终点\n"); 
	for(int i=0;i<n;i++)
	{
		if(i==0)
		{
			printf("%-8d%-8d%-8d\n",start,arc[x[i]*n+x[i+1]],x[i+1]);
			sum+=arc[x[i]*n+x[i+1]];
		}
		else if(i==n-1)
		{
			printf("%-8d%-8d%-8d\n",x[i],arc[x[i]*n+x[0]],start);
			sum+=arc[x[i]*n+0];
		}
		else
		{
			printf("%-8d%-8d%-8d\n",x[i],arc[x[i]*n+x[i+1]],x[i+1]);
			sum+=arc[x[i]*n+x[i+1]];
		}
	}
	printf("总代价:%d\n\n",sum);
}

数据

回溯法tsp问题数据

输出

回溯法tsp问题输出

复杂度

回溯法tsp问题复杂度分析

TSP问题源代码

#include <stdio.h>
#include<malloc.h>
#include<time.h> 
#include<random>
#include<stdlib.h>

//宏定义区 
#define MAX 99//设置最大权值   
#define MAXNODE 7//节点数 
#define INF 9999//默认最大值 

//全局变量区

//函数声明区
void tsp(int*,int); 
void print(int*,int*,int,int);//回路上的顶点数组,权值数值,最大节点,起点 

int main() { 
	int node;
	//自动生成方式
	//数据写入文件 
	FILE* fp = fopen("demo4-2.TXT","w");
	//生成随机数 
	srand((unsigned)time(NULL));
	node = MAXNODE;
	//生成数组矩阵,用一维数组表示二维数组 
	int *d = (int*)malloc(sizeof(int)*INF*INF);
	for(int i=0;i<node;i++)
	{
		for(int j=0;j<node;j++)
		{ 
			//行列相等默认最大值 
			if(j == i)
				d[i*node+j] = INF;
			else
				d[i*node+j] = rand()%MAX+1;
			fprintf(fp, "%d\t",d[i*node+j]);
			fprintf(fp, " ");
		}
		fprintf(fp, "\n");
	}
	//关闭文件 
	fclose(fp);
	//调用tsp函数 
   	tsp(d,node);
}

void print(int* x,int* arc,int n,int start)//回路上的顶点数组,权值数值,最大节点,起点 
{
	int sum=0;
	printf("起点---代价---终点\n"); 
	for(int i=0;i<n;i++)
	{
		if(i==0)
		{
			printf("%-8d%-8d%-8d\n",start,arc[x[i]*n+x[i+1]],x[i+1]);
			sum+=arc[x[i]*n+x[i+1]];
		}
		else if(i==n-1)
		{
			printf("%-8d%-8d%-8d\n",x[i],arc[x[i]*n+x[0]],start);
			sum+=arc[x[i]*n+0];
		}
		else
		{
			printf("%-8d%-8d%-8d\n",x[i],arc[x[i]*n+x[i+1]],x[i+1]);
			sum+=arc[x[i]*n+x[i+1]];
		}
	}
	printf("总代价:%d\n\n",sum);
}

//算法书158页解决哈密顿回路问题的伪代码可以近似看作解决tsp问题的来使用 
//下面的算法实现是依照书上的伪代码来写的 
void tsp(int* arc,int n)//参数:代价矩阵,结点数 
{
	int k;//k代表经过的顶点数,书上伪代码中没有提到但是代码中使用了 
	int x[n];//存储tsp路径上的顶点
	int visited[n];//存储顶点访问标志 
	int v=0;//tsp问题剪枝要使用到的权值 
	int minv=INF;//记录最小的权值情况 
	//算法书上默认从顶点0开始出发,所以下面也从顶点0开始出发
	//初始化
	for(int i=0;i<n;i++)
	{
		x[i]=0;
		visited[i]=0; 	
	}
	k=1;
	visited[0]=1;
	//伪代码这里写的x[0]=1;建议改正一下 
	x[0]=0;
	while(k>=1)
	{
		x[k]=x[k]+1;
		//若n个节点没有被穷举完 
		if(x[k]<n)
		{
			//若顶点x[k]不在tsp回路上,并且存在路径(x[k],x[k-1])
			//tsp问题是完全图所以一定存在这条路径,故这里省略一个判断 
			//判断权值是否比最小权值大,如果大那么一定不是最短路径直接跳过 
			//从x[k-1]节点到x[k]节点的代价 
			v=v+arc[(x[k-1])*n+x[k]];
			//if(visited[x[k]]==0 && v<minv)
			if(visited[x[k]]==0)
			{
				visited[x[k]]=1;
				//已经形成tsp路径
				if(k==n-1 && v+arc[x[k]*n+0]<minv)
				{
					minv=v+arc[x[k]*n+0];
					print(x,arc,n,0);
					v=v-arc[(x[k-1])*n+x[k]];
					visited[x[k]]=0;
					continue;
				}
				//构成tsp路径部分解 
				else if(v<minv)
				{
					k=k+1;
					continue;
				}
				//不能成为最短路径,直接跳过不处理 
				else
				{
					visited[x[k]]=0;
					v=v-arc[(x[k-1])*n+x[k]];
					continue;
				}
			}
			else//否则搜索下一个节点 
			{
				v=v-arc[(x[k-1])*n+x[k]];
				continue;
			}
		}
		//取消x[k]的访问标志,重置x[k],k=k-1,
		else
		{
			v=v-arc[(x[k-2])*n+x[k-1]];
			visited[x[k-1]]=0;
			x[k]=0;
			k=k-1;
		}
	}
}
  • 0
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值