软考考点之下午题算法 2017年下半年 哈密尔顿回路

如题:

Vido Hamilton(int n,int x[MAX],int c[MAX][MAX]){//这里更正为它,看来资料也有错误 

//这里开始分析解题: 空1,从上下文,x[0]=0,判断前面有可能是个判断,先填上if(c!=null)

空2,提示是x[k]末被访问过   Visited[x[k]]==0

答:先从审题开始吧,从算法描述可以知道,“若Vi的所有邻接顶点都被访问,则返回到顶点Vi-1”;

所用的数据结构 

n :图G中的顶点数
c[][]:图G的邻接矩阵//看到这里应该可以脑补出一个无向图的邻接矩阵和一个无向图,可以手画一个简单点的
K:统计变量,当期已经访问的定点数为k+1//当期已经访问的是K+1,那么K是从0开始的
x[k]:第k个访问的顶点编号,从0开始//是不是k<=n呢??

Visited[x[k]]:第k个顶点的访问标志,0表示未访问,1表示已访问

空1,空2见题中分析;空3,提示是找到一条哈密尔顿回路,那是不是访问到V0的前一个结点,并且这个结点和V0有连接;所以Visited[x[k]]==1

空4,根据提示Visited[x[k]]=1;

空5,根据提示,k--;

下面算法策略:其实就是分解成同样的问题,最后再合起来,所以是分治算法,第二问,因为是无向图,肯定是广度优先

 

以上是没看正确答案自己分析的结果,下面是正确答案:丢分严重?似乎在意料之中,为什么会丢分?

第一空,正确答案是 Visited[0]=1;是不是要是根据提示这个空不至于失分吧!

空2,空4,空5,对,其中空5 答案给出的是k=k-1;

看空3,正确答案是 C[x[0]x[k]],真的脑补出矩阵图来了吗??脑补出来的话,这个空应该能填对。

 

第二问,答广度优先,实在是不应该,从题的描述来看,也应该是深度优先。一定不要脱离开题目的描述。

如果再仔细读遍描述,很容易,发现有一个返回,所以就是回溯法。不能想当然,仔细品读下描述,应该能得到正确的结果。

 

失分的总结:

这种题,一要要清晰的脑补出算法描述的内容,不能没读完描述就开始做题,千成不要想当然的去做题。一些选择题,也是这样的,一定要清晰的脑补出题意来。这样才不容易失分。

 

扩展:

  1859年,爱尔兰数学家哈密尔顿(Hamilton)提出下列周游世界的游戏:在正十二面体的二十个顶点上依次标记伦敦、巴黎、莫斯科等世界著名大城市,正十二面体的棱表示连接这些城市的路线。试问能否在图中做一次旅行,从顶点到顶点,沿着边行走,经过每个城市恰好一次之后再回到出发点。这就是著名的哈密尔顿问题,见图8-18。

        从这里也可以看出,算法是来源于实际生活的问题的,和生活是息息相关的。也是一种数学的抽象。

哥尼斯堡七桥问题是在寻找一条遍历图中所有边的简单路径,而哈密尔顿的周游世界问题则是在寻找一条遍历图中所有点的基本路径。在无向图G=<V,E>中,遍历G中每个顶点一次且仅一次的路径称为哈密尔顿路径,遍历G中每个顶点一次且仅一次的回路称为哈密尔顿回路。具有哈密尔顿回路的图称为哈密尔顿图。哈密尔顿问题是一类问题的总称。


这个问题即一个图是否为哈密尔顿图的判定问题至今悬而未决。哈密尔顿问题一直是图论中的世界性难题,到目前为止仍然没有找到一个像欧拉图那样简单的充分必要条件。目前的研究结果仅仅可以分别给出哈密尔顿回路存在的必要条件和充分条件。

如果图G=<V,E>是哈密尔顿图,则它必然具备下述性质:

       对V的每个非空真子集 S均有w(G-S)<=|S| ,其中|S|是S中的顶点数, w(G-S)表示G删去顶点集S后得到的图的连通分图个数。

       上述条件是一个必要条件,但不是一个充分条件。因此,可以运用它来判定某些图不是哈密尔顿图。即哈密尔顿图都满足这样的条件,但符合该条件的图不一定是哈密尔顿图,彼得森图就不是一个哈密尔顿图,但它却满足上述条件。

       下面给出一个判定哈密尔顿回路存在的充分条件:

       如果图G=<V,E>是具有n>=3个顶点的简单无向图,且在图G中每一对顶点的度数和都不小于n ,那么 G中必然存在一条哈密尔顿回路。

       上述条件是一个充分条件,但不是一个必要条件。也就是说满足该条件的图必然是哈密尔顿图,但不满足该条件的图也有可能是哈密尔顿图。

   在有向图中也可以定义哈密尔顿有向路径和哈密尔顿有向回路。最后,我们来考虑哈密尔顿回路的一个特例——骑士周游问题。

       骑士周游问题(又称骑士漫游或马踏棋盘问题)是算法设计的经典问题。现在已知的关于该问题的最早记录可追溯到公元九世纪,印度诗人和文艺理论家楼陀罗托(Rudrata)在其著作《诗庄严论》中对此给出了直观的描述。欧拉是西方最早研究骑士周游问题的数学家之一。但第一个系统化解决骑士周游问题的方法则是由德国学者冯·恩斯多夫(H. C. von Warnsdorf)于1823年提出的,这个方法现在被称为恩斯多夫规则或恩斯多夫算法。

 国际象棋棋盘横向8 列,纵向8行,总计64个格子。假设有一个马从棋盘上任意起点出发,要求经过63步之后,不重复地遍历整个棋盘上除初始点以外的每个格子。这个问题用现代语言描述的话,其实就是在一个具体的图中找寻哈密尔顿路径。这里的哈密尔顿路径指的是经过图中每个顶点且只经过一次的一条轨迹。如果该轨迹是一条闭合路径,即从起点出发不重复的遍历所有点后仍能回到起始点,那么这条路径则称为哈密尔顿回路。图6-18显示的是一个马所能跳走的八个位置。需要说明的是对于棋盘上任意初始点该问题都有解,且解不唯一。有文献中指出对于一个 8*8的棋盘,它拥有的不同巡游路径的个数大约是1.305*10的35次方 。
下面给出一些关于骑士周游问题的定理。

       定理1:在 8*8棋盘中,令 aij表示第 i行第 j列的棋格,其中1<=i, j<=8 ; akl表示第k 行第l 列的棋格,其中 1<=k,l<=8。由移动法则可知,从aij到 akl是合法的移动,当且仅当 |i-k|=1,|j-l|=2  或者|i-k|=2 ,|j-l|=1 。

       定理2:当 |(i+j)-(k+l)|为偶数时,从 aij 到  akl绝不是合法移动;也就是说,只有当 i+j与k+l 的奇偶性不同时, aij到 akl才可能是合法的移动。

考虑将骑士周游问题拓展到 n*n的棋盘上,则有以下定理:

       定理3:对于骑士周游问题,当n>=5 ,且为偶数时,以任意点作为初始点都有解。

       骑士周游问题在求解过程中需要用到前面介绍的递归,而且事实上,这还是一个分治与回溯相结合的典型例子。从图8-19可知,马每次跳走的选择可能有八种,按着其中一种进行尝试,当发现一种情况走不通时再回溯到先前的某一种情况继续尝试其它的选择。如此继续下去即可得到最终的结果。图8-20给出了骑士周游问题的两种解的情况,特别地其中右图给出的是一个哈密尔顿回路解,即巡游路径的起始点和终结点为同一个棋格。
 

 下面考虑编程解决骑士周游问题。前面提到过,欧拉曾经对该问题进行了研究。欧拉把 棋盘上的骑士周游问题转化为在 的表格上能否按一定规则排列数字 的数学问题。我们的程序也基于这种方法来实现。另外,易知该算法的时间复杂度为 ,有鉴于如果单纯使用回溯方法而不进行优化,程序效率将是非常低的,于是不妨考虑采用一种启发式的遍历规则:即向前看两步,当每准备跳一步时,设准备跳到 点,计算 这一点可能往几个方向跳,将这个数目设为 点的权值,并将所有可能的 按权值进行排序,从最小的开始,循环遍历所有可能的 ,回溯求出结果。该算法可以求出所有可能的骑士周游路径,算出一个可行的结果很快。当然,其不足在于时间复杂度本质上并没有改变,所以要求出所有可能结果时,仍然很慢。后面的示例程序正是基于这种思想设计完成的。
 

#include "stdafx.h"
#include "conio.h"
#include <iostream>
 
using namespace std;
 
class Board
{
private:
	int board[8][8];  //棋盘
	int step;         //当前走的步数
	int No;	          //当前解的编号
	int direct[8][2]; //各前进方向的坐标偏移
	int wayCount[8][8];  //棋盘上每个位置可跳出的方向数目
	int startX;          //起始点坐标x
	int startY;			//起始点坐标y
	int dir[8][8][8];   //保存最优的搜索方向顺序
	
 
	void init() 
	{ 
		int i,j,k;
		int x,y;
		//确定从棋盘上每个位置可跳出的方向数目
		for(j=0;j<8;j++)
		{
			for(i=0;i<8;i++) 
			{
				wayCount[j][i]=0;
				for(k=0;k<8;k++) 
				{  
					x=i+direct[k][0]; 
					y=j+direct[k][1];
					if(check(x,y)) 
						wayCount[j][i]++; 
				} 
			}
		}
 
		//为棋盘上每个位置确定搜索的方向顺序
		for(y=0;y<8;y++)
		{
			for(x=0;x<8;x++) 
			{
				//默认搜索顺序为顺时针方向
				for(k=0;k<8;k++)
				{
					dir[y][x][k]=k;
				}
				//寻找最优搜索顺序
				for(i=0;i<7;i++)
				{
					k=i;
					int x1=x+direct[dir[y][x][k]][0];
					int y1=y+direct[dir[y][x][k]][1];
					//为各搜索方向按规则排序
					//希望搜索时优先到达下一步可能性较少的位置
					for(j=i+1;j<8;j++)
					{
						int x2=x+direct[dir[y][x][j]][0];
						int y2=y+direct[dir[y][x][j]][1];
						//如果从当前位置出发 方向j优于方向k 则将k替换为j
						if( (!check(x1,y1) && check(x2,y2))
							|| ( check(x1,y1) && check(x2,y2) &&
							wayCount[x1][y1]>wayCount[x2][y2]) )
						{
							k=j;
							x1=x+direct[dir[y][x][k]][0];
							y1=y+direct[dir[y][x][k]][1];
						}
					}
					j=dir[y][x][k];
					dir[y][x][k]=dir[y][x][i];
					dir[y][x][i]=j;
				}
			}
		}
	}
 
	//检查x,y是否为合法位置
	int check(int x,int y) 
	{ 
		if(x<0||x>7||y<0||y>7)
		{
			return 0;
		}
		else
		{
			return 1;
		}
	}
 
	//从指定位置(x,y)出发寻找路径
	void dg(int x, int y)
	{
		int i,nx,ny;
		//如果当前为最后一步 则终止递归
		if(step==64)
		{
			printPath();
			return;
		}
 
		//按照最优的搜索方向顺序 依次向各可能方向搜索
		for(i=0;i<8;i++)
		{
			nx=x+direct[dir[y][x][i]][0];
			ny=y+direct[dir[y][x][i]][1];
			if(nx>=0 && nx<8 && ny>=0 && ny<8)
			{
				//如果成功到达下一位置 则从新位置开始继续搜索
				if(board[ny][nx]<0)
				{
					board[ny][nx]=step;
					step++;
					dg(nx,ny);
					board[ny][nx]=-1;
					step--;
				}
			}
		}
	}
 
	void printPath()
	{
		int i,j;
		No++;
		cout<<"No"<<No<<":"<<endl;
		for(j=0;j<8;j++)
		{
			for(i=0;i<8;i++)
			{
				cout<<board[j][i]<<" ";
				if(board[j][i]<10) 
					cout<<" "; 
			}
			cout<<endl;
		}
		cout<<"Press anykey to continue...";
		getch();
		cout<<endl;
	}
	void printwc()
	{
		int i,j;
		No++;
		cout<<"No"<<No<<":"<<endl;
		for(j=0;j<8;j++)
		{
			for(i=0;i<8;i++)
			{
				cout<<wayCount[j][i]<<" ";
				if(wayCount[j][i]<10) 
					cout<<" "; 
			}
			cout<<endl;
		}
		cout<<"Press anykey to continue...";
		getch();
		cout<<endl;
	}
 
public:
	Board(int x, int y)
	{
		int i,j;
		startX=x;
		startY=y;
		direct[0][0]=1;		direct[0][1]=-2;
		direct[1][0]=2;		direct[1][1]=-1;
		direct[2][0]=2;		direct[2][1]=1;
		direct[3][0]=1;		direct[3][1]=2;
		direct[4][0]=-1;	direct[4][1]=2;
		direct[5][0]=-2;	direct[5][1]=1;
		direct[6][0]=-2;	direct[6][1]=-1;
		direct[7][0]=-1;	direct[7][1]=-2;
		step=1;
		No=0;
		for(j=0;j<8;j++)
		{
			for(i=0;i<8;i++)
			{
				board[j][i]=-1;
			}
		}
		board[y][x]=0;
	}
 
	void GetPath()
	{
		init();
		dg(startX,startY);
		if(No==0)
		{
			cout<<"no result"<<endl;
		}
	}
};
 
int _tmain(int argc, _TCHAR* argv[])
{
	int x,y;
	cout<<"Please input the start point (x,y)."
		<<endl<<"x=";
	cin>>x;
	getchar();
	cout<<"y=";
	cin>>y;
	getchar();
	Board board(x,y);
	board.GetPath();
 
	return 0;
}

https://blog.csdn.net/baimafujinji/article/details/49687061

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

guangod

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值