用回溯法求解一般哈密尔顿回路问题

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

目录
引言........................................................ - 1 - 1 需求分析.................................................. - 2 -
1.1问题的提出........................................... - 2 -
1.2问题的描述........................................... - 2 -
1.3算法的描述........................................... - 3 -
2 概要设计................................................. - 4 -
2.1系统运行环境......................................... - 4 -
2.2算法的实现........................................... - 4 -
2.3接口设计............................................ - 12 -
2.4出错处理设计........................................ - 12 -
2.5维护设计............................................ - 13 -
3 详细设计................................................. - 1
4 -
3.1算法的分析.......................................... - 14 -
3.2程序的思路.......................................... - 15 -
3.3程序的实现.......................................... - 15 -
3.4设计环境............................................ - 19 -
4 调试分析................................................. - 20 -
4.1有哈密尔顿回路连通图................................ - 20 -
4.2无哈密尔顿回路连通图................................ - 22 -
5 总结..................................................... - 23 - 参考文献................................................... - 25 - 附录....................................................... - 2
6 -
摘要
回溯法是一种按照深度优先的策略从根结点开始搜索解空间树的算法,该算法既带有系统性又带有跳跃性,它在包含问题的所有解的解空间树中,按照深度优先的策略,从根节点出发搜索解空间树。

算法搜索至解空间树的任一节点时,总是先判定该节点是否肯定不包含问题的解,如果肯定不包含,则跳过对以该节点为跟的子树的系统搜索,逐层向其祖先节点回溯。

否则,进入该子树,继续按深度优先的策略进行探索。

这种以深度优先的方式系统地搜索问题的解的算法称为回溯法。

回溯法可以用来求出问题的全部解,也可以在求出问题的一个解之后停止对问题的求解,即只求该问题是否有解。

它适用于解一些组合数较大的问题。

哈密尔顿回路路就是判断图中是否存在一条通过所有顶点一次且仅一次的路径。

本文主要讲的就是用回溯法来求解一个任意的图中是否存在一条哈密顿通路的
问题,并用具体的算法来实现它。

关键词:回溯法哈密尔顿回路空间树
引言
回溯法是一个既带有系统性又带有跳跃性的的搜索算法。

它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树[1]。

算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。

如果肯定不包含,则跳过对以该结点为根的子树的系统搜索,逐层向其祖先结点回溯。

否则,进入该子树,继续按深度优先的策略进行搜索。

回溯法在用来求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束 [2]。

而回溯法在用来求问题的任一解时,只要搜索到问题的一个解就可以结束。

这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适用于解一些组合数较大的问题。

1 需求分析
1.1问题的提出
在求解一些问题(如走迷宫、地图着色等问题)时,题目的要求可能是求出原问题的一种或所有可能的解决方案。

这类问题的解往往是由一个一个的步骤或状态所构成的,每一步骤又有若干种可能的决策方案;由于没有固定、明确的数学解析方法,往往要采用搜索的做法,即从某一个初始状态出发,不断地向前(即下一个状态)搜索,以期最终达到目标状态,从而得到原问题的一个解或所有的解。

在搜索的过程中,由于问题本身及所采取的搜索方法的特点(如在缺乏全局及足够的前瞻信息的情况下进行搜索等)[3],会导致走到某一状态就走不下去的情况,这时,就必须回头(即回到上一步,而不是回到最初的状态),再尝试其他的可能性,换一个方向或方法再试试。

这样,不断地向前探索、回溯,再向前、再回溯,直至最终得出问题的解,或者一路回溯到出发点(出现这种情况即表示原问题无解)[4] 。

注意,这种搜索过程并不是尝试搜索问题解空间中所有的可能状态和路径,而是采用深度优先的方式,沿着一条路径,尽可能深入地向前探索。

1.2问题的描述
1.设计内容:
给出内存分类法的多种应用,给出这些应用对应的算法并编程实现。

2.设计要求:
(1)给出各种分类法的求解算法;
(2)编程实现各种分类法的算法;
(3)对所写的每个算法给出时空复杂性分析。

1.3算法的描述
该算法讲的就是用回溯法求解一个无向图中是否存在哈密尔顿回路的问题。

所谓哈密尔顿回路就是指经过图(有向图或无向图)中所有顶点一次且仅一次的通路。

用回溯法解哈密尔顿回路问题首先要画出问题的解空间树,该解空间树是一棵最大度是 n 的树(其中 n 为图中的顶点数),树中只有第一个结点的度是 n,其余结点的度都为 n-1(该结点不用与其自身相连)。

在编写算法时可以通过判断该边在图的邻接矩阵中的值来剪枝,如果其值不是 1 则说明该边不存在则剪枝不用搜索。

由于在求图的哈密尔顿回路时走过的顶点不能再重复走,所以要对已经遍历过的顶点做一个标记,如果在搜索时找到的是一个带有标记的顶点,那么该路径也是不可行的,应剪去。

2 概要设计
此算法设计为用回溯法求解一般哈密尔顿回路问题,设计的主要内容为:给出内存分类法的多种应用,给出各类分类法的求解算法,编程实现各种分类法的算法,对所写的每一个算法给出时间空间复杂性分析。

2.1系统运行环境
根据目前市场上能够提供的硬件。

我们设计系统的硬件环境如下:
●IBM PC286及以上档次微机、便携机、各种品牌兼容机,最佳
档次为386以上微机。

●512M或512M以上内存,最好具备扩展内存
●EGA、VGA、TVGA、所有SUPERVGA彩色显示器。

●20M以上硬盘。

●任何光电鼠或机械鼠。

软件环境如下:
●MS(PC)DOS3.3或以上版本;
●采用VC++进行开发;
2.2算法的实现
1.个体类
class Cind
{
public:
int& operator []( int index ){ return data[index]; }
Cind & Cind::operator = (Cind &a);
bool operator == (Cind &ind);
void CrossWith(Cind &a);
void Mut();
void show();
void SetArc();
double fun();
Cind();
virtual ~Cind();
int * data;
static int n;
double fitness;
};
// ind.cpp: implementation of the Cind class.
//
///////////////////////////////////////////////////////////////// #include "stdafx.h"
#include "GrWndapp.h"
#include "shapelist.h"
#include "ind.h"
#include "node.h"
#include "arc.h"
#include "math.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
int Cind::n;
extern CShapeList node_list,arc_list;
// Construction/Destruction
Cind::Cind()
{
data = new int[n];
bool * flag = new bool[n];
for ( int i=0; i<n; i++ )
flag= true;
int k=0;
for ( int m=n; m>0; m-- )
{
int t = rand()%m;
int j = 0;
for ( i = 0; i<n; i++ )
if ( flag )
{
if ( j == t )
{
data[k++] = i;
flag =false;
break;
}
j++;
}
}
delete[] flag;
}
Cind::~Cind()
{
delete[] data;
}
double Cind::fun()
{
double v = 0;
CNode * pn1,*pn2;
for ( int i=0; i<n-1; i++ )
{
pn1 = (CNode *)node_list.GetShape(data);
pn2 = (CNode *)node_list.GetShape(data[i+1]);
v += sqrt((pn1->x - pn2->x)*(pn1->x - pn2->x)+ (pn1->y-pn2->y)*(pn1->y-pn2->y));
}
pn1 = (CNode *)node_list.GetShape(data[0]);
v += sqrt((pn1->x - pn2->x)*(pn1->x - pn2->x)+ (pn1->y-pn2->y)*(pn1->y-pn2->y));
return v;
}
void Cind::SetArc()
{
arc_list.Clear();
CArc * parc;
for ( int i=0; i<n-1; i++ )
{
parc =
new CArc((CNode *)node_list.GetShape(data), (CNode *)node_list.GetShape(data[i+1]));
arc_list.Add(parc);
}
parc =
new CArc((CNode *)node_list.GetShape(data), (CNode *)node_list.GetShape(data[0]));
arc_list.Add(parc);
}
void Cind::show()
{
TRACE("The ind:\n");
for ( int i=0; i<n; i++ )
TRACE ( "%d->",data );
TRACE("\n");
}
void Cind::Mut()
{
int p1 = rand() % n;
int p2 = rand() % n;
int t = data[p1];
data[p1]=data[p2];
data[p2]=t;
}
void Cind::CrossWith(Cind &a)
{
int pos = rand() % (n-1);
pos ++;
int * t1, * t2;
t1 = new int[n];
t2 = new int[n];
int k=0;
for ( int i=0; i<n; i++ )
{
for ( int j=0; j<pos; j++ )
{
if ( data == a[j] )
break;
}
if ( j == pos ) // find none
t1[k++] = data;
}
for ( i=0; i<pos; i++ )
t1[k++] = a;
k=0;
for ( i=0; i<n; i++ )
{
for ( int j=0; j<pos; j++ )
{
if ( data[j] == a )
break;
}
if ( j == pos ) // find none
t2[k++] = a;
}
for ( i=0; i<pos; i++ )
t2[k++] = data;
for ( i=0; i<n; i++ )
{
data = t1;
a = t2;
}
delete[] t1;
delete[] t2;
}
Cind & Cind::operator =(Cind &a) {
if ( this == &a )
return * this;
for ( int i=0; i<n; i++ )
data=a;
fitness=a.fitness;
return * this;
}
bool Cind::operator ==(Cind &ind) {
for ( int i=0; i<n; i++ )
if ( data != ind )
return false;
return true;
}
2.群体类
#include "ind.h"
class CGroup
{
public:
void Reserve();
void Grow();
Cind * GetBest();
void Select();
void CalFitness();
CGroup();
virtual ~CGroup();
int n,m,ml;
Cind * group;
Cind * pool;
int nres;
static int gn; // group length
static double miu;
static double gg; // gerneration gap
static double pmut; // probability of mutant
static double pres;
};
// Group.cpp: implementation of the CGroup class.
//
////////////////////////////////////////////////////////////////// ////
#include "stdafx.h"
#include "GrWndapp.h"
#include "Group.h"
#include "shapelist.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
extern CShapeList node_list,arc_list;
int CGroup::gn;
double CGroup::gg;
double CGroup::miu;
double CGroup::pres;
double CGroup::pmut;
////////////////////////////////////////////////////////////////// ///
// Construction/Destruction
////////////////////////////////////////////////////////////////// ///
CGroup::CGroup()
{
gn=100; // group length
pmut=0.3;
pres = 0.1;
srand( (unsigned)time( NULL ) );
Cind::n = node_list.n;
group = new Cind[gn];
pool = new Cind[gn];
nres = gn*pres;
}
CGroup::~CGroup()
{
delete[] group;
delete[] pool;
}
void CGroup::CalFitness()
{
double maxfst=0;
for ( int i=0; i<gn; i++ )
{
group.fitness=group.fun();
maxfst=__max(maxfst,group.fitness);
}
for ( i=0; i<gn; i++ )
group.fitness=maxfst-group.fitness;
}
void CGroup::Select()
{
CalFitness();
// calculate the sum of fitness of the individul double sum=0;
for ( int i=0; i<gn; i++ )
sum += group.fitness;
// create a position
for ( i=0; i<gn-nres; i++ )
{
double pos = rand()*sum/RAND_MAX;
double t=group[0].fitness;
for ( int j=0; j<gn; j++ ) // find the indvidul if ( pos <= t )
{
pool=group[j];
break;
}
else
t += group[j+1].fitness;
}
}
Cind * CGroup::GetBest()
{
double v=group[0].fun();
Cind * pind = &group[0];
for ( int i=1; i<gn; i++ )
if ( group.fun() < v )
{
pind = &group;
v = group.fun();
}
return pind;
}
void CGroup::Grow()
{
Select();
Reserve();
// cross over--------------
for ( int i=0; i<gn-nres; i+=2 )
{
pool.CrossWith(pool[i+1]);
group=pool;
group[i+1]=pool[i+1];
}
// mutant------------------------
int num = gn * pmut; // 要变异的个体数
for ( i=0; i<num; i++ )
{
int ind = rand()%gn; // random select
group[ind].Mut();
}
}
void CGroup::Reserve()
{
Cind t;
for ( int i=0; i<nres; i++ )
for ( int j=0; j<gn-1; j++ )
if ( group[j].fitness > group[j+1].fitness )
{
t=group[j];
group[j]=group[j+1];
group[j+1]=t;
}
}
2.3接口设计
1.外部接口
(1) 用户界面
采用图形用户界面(GUI),包含菜单、按钮、对话框等元素。

(2) 软件接口
软件运行于windows XP极其以上版本之上。

(3) 硬件接口
运行于IBM PC386及兼容机以上。

2.4出错处理设计
1.系统应具有相当健壮性,避免或降低由系统错误所造成的损坏。

2.对关键性操作。

2.5维护设计
系统严格按照设计规范进行设计,并保持各阶段文档的完整性,
为以后对软件的维护打好基础。

3 详细设计
在以上工作的基础上,我们根据任务要求编程实现用回溯法求解一般哈密尔顿回路问题。

对有输出要求的全部数据进行分析,进一步研究整个算法的实现。

3.1算法的分析
各函数功能简要分析:函数 FirstAdjVex()用来求出图的邻接矩阵的某一行存在的第一条边所对应的第一个顶点;函数NextAdjVex()用来求出跟第一个顶点在解空间树中同行相邻的下一个顶点;函数 initStatus()初始化跟踪栈,把所有已经遍历过的并且当前已经符合条件的点都放在这里面;函数 backtrack()进行回溯;函数 bool testHami()判断一下该图有没有哈密顿通路;函数 Hami()是该算法的入口点,它通过调用函数 backtrack()
来实现层层递归。

根据程序中判断哈密尔顿回路的条件——对每一个节点访问一次且仅仅一次即visitTag[0...N]都等于 1,对 Hami(G)函数的简要分析:
1.判断是不是连通图
2.对每一个节点进行以下操作
(1) 初始化访问标志 visitTag
(2) 调用试探每一个节点出发能不能找到哈密顿通路,如果能立即输出并跳出程序
3.到这的时候说明不存在哈密尔顿回路
3.2程序的思路
3.3程序的实现
#include<stdio.h>
#include<process.h>
#include<math.h>
//全局变量声明
int m=1; //用于标志哈密尔顿回路的总个数int n; //
int x[128];
int graph[128][128];
void nextvalue(int k)
{
int j;
while(1)
{
x[k]=(int)fmod(x[k]+1,n+1);
if(x[k]==0) return;
if(graph[x[k-1]][x[k]])
{
for(j=1;j<=k-1;j++)
{
if(x[j]==x[k]) break;
}
if(j==k)
{
if(k<n||(k==n&&graph[x[n]][1]))
return;
}
}
}
}
void print(int x[],int n)
{
int i=1;
printf("回路%d:",m);
for(;i<=n;i++)
printf("%d ",x[i]);
printf("\n");
m++;
}
void hamiltonian(int k)
{
while(1)
{
nextvalue(k);
if(x[k]==0) return;
if(k==n)
print(x,n);
else
hamiltonian(k+1);
}
}
void main()
{
int i,j,e,a,b;
printf("***********哈密顿回路递归回溯算法***********\n");
printf("********************************************\n");
printf("请先创建一个n结点的连通图graph[n][n]\n");
printf("请输入顶点n的值:");
scanf("%d",&n);
printf("图中一共有几条边?请输入以便我们创建图:");
scanf("%d",&e);
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
graph[i][j]=0;
for(i=1;i<=e;i++)
{
printf("\n创建第%d条边:\n",i);
printf("构成此边的一个顶点号(1~%d):",n);
scanf("%d",&a);
printf("另一个顶点号(1~%d):",n);
scanf("%d",&b);
graph[a][b]=graph[b][a]=1;
}
x[1]=1;
for(i=2;i<=n;i++)
x[i]=0;
printf("\n以下为所求hamiltonian回路的所有解\n");
hamiltonian(2);
}
3.4设计环境
操作系统:Windows XP
设计工具:Microsoft Visual C++
4 调试分析
4.1有哈密尔顿回路连通图
4.2无哈密尔顿回路连通图
5 总结
通过本次课程设计,本人对算法设计与分析基础有了更深的认识,基本掌握了回溯法求解一般哈密尔顿回路的算法思路以及编程原理,提高了程序开发的能力,让能切实体会到算法在编程过程中的指导作用。

通过课程设计,学会了按课程设计的任务要求完成各项程序的开发,对提高自身编程能力和项目管理能力有重要的现实意义。

在本次课程设计中,由于对回溯法以及哈密尔顿回路的不熟悉,走了很多弯路,花了一些时间去了解其相关知识。

但是最后终于完成了自己的课程设计,自己觉得还是比较有成就感的。

与此同时,我也发现用回溯法求解一般哈密尔顿回路问题是一个 np 问题,其时间复杂度是 O(n ),空间复杂度是 O(2 )。

通过这次的课程设计,我学到了很多东西:
1.多和别人交流,毕竟一个人所能想到的东西是有限的,但是通过和别人的交流,我发现我可以看到更多的东西了,例如:
可能在程序的实现过程中,我并没有注意到程序在运行过程
中会产生异常,但是在和别人的交流过程中,我发现了这个
异常,促使我去捕获它,使所开发的系统有了更好的健壮性。

2.一个程序的开发,需要开发人员用心去完成每一个阶段的工作,这样才不会出现开发到一半又忽然发现在某个地方出现
了致命的错误,需要重新开发的严重错误,我想这就好像是
在编程的时候需要避免“回溯”是一个道理,前期多做些工
作,后期的开发才会水到渠成,缩短开发周期。

致谢
能完成这个课程设计,我首先要感谢我的指导老师谭三老师,在他严格的监督和细心指导中,使我能够坚持完成本课程,并能够较好的实现要求任务书上所要求的功能,最后,我要感谢班上同学对我的帮助和指点。

没有他们的帮助和提供资料对于我一个对编程知识不是很精通的人来说要想在短短的的时间里完成一个算法的设计是几乎不可能的事情。

在此表示最诚挚的感谢!
参考文献
[1]《算法设计与分析》宋文等编重庆大学出版社,2001。

[2]《算法设计与分析》周培德电子工业出版社,2000。

[3]《算法设计与分析》王晓东电子工业出版社,2004
[4] 李登信;关于 Cayley 图的 Hamilton 性的一个猜想[J];渝州大学学报(自然科学版);1999 年 04 期
[5] 百度网站
[6] google网站
附录
该算法的具体实现主要代码如下:
int FirstAdjVex(const MGraph& G,int v){//查找 v 的第一个邻接点
for(int i=0;i<G.vexnum;i++){
if(G.arcs[v][i].adj!=0){
return i;
}
}
return -1;
}
int NextAdjVex(const MGraph& G,int v,int w){//查找 v 的(相对于 w 的)下一个邻接点
for(int i=w+1;i<G.vexnum;i++){
if(G.arcs[v][i].adj!=0){
return i;
}
}
return -1;
}
void initStatus(){
for(int i=0;i<MAX_VERTEX_NUM;i++){
visitTag[i]=0;
x[i]=-1;//初始化跟踪栈,把所有已经遍历过的并且当前已经符合条件的点都放在这里面
}
}
bool testHami(const MGraph& G){//用来测试是不是 Hami?
for(int i=0;i<G.vexnum;i++){
if(!visitTag[i]){
return 0;
}
}
return 1;
}
void backtrack(const MGraph& G,int t,int idxsum){
int i;
visitTag[t]=1;
x[idxsum]=t;//将节点的坐标放到当前栈里
for(i=FirstAdjVex(G,t);i>=0;i=NextAdjVex(G,t,i)){
if(!visitTag[i])backtrack(G,i,++idxsum);
}
if(testHami(G)){//找到 HAMI 通路
cout<<"图中存在的一条哈密顿通路是:"<<endl;
for(i=0;i<G.vexnum;i++){//输出通路中相应的节点
if(x[i]>=0&&x[i]<=G.vexnum)
cout<<G.vexs[x[i] ]<<" ";
}
cout<<endl;
exit(0);//提前结束
}
visitTag[t]=0;//回朔也使它的访问标志为 0,为了下一次的访问x[idxsum]=-1;//清空它,说明当前点是不符合条件的,
}
void Hami(const MGraph& G){
if(G.arcnum<G.vexnum-1){
cout<<"非连通图"<<endl;;
return;
}
for(int i=0;i<G.vexnum;i++){//必须查看从所有点开始的所有路径initStatus();//设置状态
backtrack(G,i,0);//试探由 G.vexs[i]出发是不是有通路
}
cout<<"没有找到通路"<<endl;
}
void main()
{
MGraph G;
CreatUDN(G);
dispMGraph(G);
Hami(G);
}。

相关文档
最新文档