机器智能实验

一.实验目的

了解A*树搜索和贪婪最佳优先搜索算法的基本原理

能够运用计算机语言实现A*树搜索和贪婪最佳优先搜索算法

应用搜索算法解决罗马尼亚问题

能够通过实验分析了解算法性能的优劣

二.实验硬件,操作平台

操作系统:WINDOWS 10
编程语言:C++
应用软件:DevC++

三.算法的基本原理

1.贪婪最佳优先搜索的基本原理

贪婪最佳优先搜索属于一种有信息搜索,它能够利用除了问题本身之外的条件,进行更加有效的问题求解。贪婪最佳优先搜索每次选择扩展距离目标结点最近的结点,用评估函数h(n)去评估每个结点n到目标结点的“距离”远近。
每次从待选择结点队列Q中选择h(n)最小的结点进行扩展,并将其子结点加入待选择结点队列Q,重复直到找到目标结点。

2.A*搜索的基本原理

A*搜索也是属于一种有信息搜索,相比于贪婪最佳优先搜索,它增加了达到结点n的实际距离,和n到目标结点估计距离作为距离评估,即f(n)=g(n)+h(n),其中f(n)表示经过结点n的总评估距离,g(n)表示达到结点n的实际距离,h(n)表示结点n到目标结点的评估距离。

每次从待选择结点队列Q中选择f(n)最小的结点进行扩展,并将其子结点加入待选择结点队列Q,重复直到找到目标结点。

四.算法的实验结果

1.A*搜索和贪婪最佳优先搜索求解Arad到Bucharest

A搜索算法的求解的路径长度为418,路径为Arad->Sibiu->Rimnuicu_Vilcea->Pitesti->Bucharest,时间花费为13.2213ms,贪婪最佳优先搜索算法求解的路径长度则为450,Arad->Sibiu->Fagaras->Bucharest,时间花费为6.0506ms。可以看到贪婪最佳优先搜索不一定找到最优解,但是搜索速度较快,而A算法可以找到最优解,但是时间花费较大。
在这里插入图片描述

2.A*搜索和贪婪最佳优先搜索求解Fagaras到Craiova

A*搜索算法的求解的路径长度为325,路径为Fagaras->Sibiu->Rimnuicu_Vilcea->Craiova,时间花费为9.6716ms,贪婪最佳优先搜索算法求解的路径长度也是325,Fagaras->Sibiu-> Rimnuicu_Vilcea ->Craiova,时间花费为8.1186ms。两种算法均找到了最优解,且算法花费的时间相近。
在这里插入图片描述

五.实验分析和思考题

1.实验分析

1) 对问题建立模型

状态:在罗马尼亚问题中,我们将每个城市定义为一个状态,其中起始城市是初始状态,目标城市是终止状态。

行动:城市之间的穿梭。实验中对应的是选择评估函数最小的结点的操作。

状态转移:从当前城市移动到下一个城市的过程就是状态转移。

状态空间:所有可能的到达目标城市状态的访问顺序。

路径:一系列有顺序的状态组成的集合。

目标测试:用于评估当前状态是否为所求解的目标状态/终止状态。

实验中要求我们求解起初状态到终止状态的搜索路径。

2) 数据结构的使用分析

罗马尼亚地图的存储:地图本质上将是一个图,实验中使用邻接矩阵G来存储图,Gij非零表示i到j的实际距离,Gij为零表示i和j没有路径相连。将所有城市的首字母按照字母表的顺序排列,编号为0-19,如0号表示Arad城市。

结点信息的存储:定义了一个结构体Node,结构体变量和函数说明 如下
在这里插入图片描述
待选择队列:考虑到每次从带选择队列中取出代价最小的结点(贪婪最佳优先搜索选择h(n),A*搜索选择f(n)),这里选择使用优先队列priority_queue solve,可以按照我们定义的优先级,在加入队列后由其内部实现排序。

已经访问的结点的存储和标记:定义一个vector数组visited用来存储已经访问段结点。由于visited查找结点需要一个线性的时间,所以增加数组visited_f来标记结点是否被访问。

3) 算法思路和实现

算法思路(图搜索实现)

a)	初始时将起始状态加入队列solve中,此时f(0)=g(0)+h(0),g(0)=0
b)	如果队列solve为空,则说明上一步没有可扩展的结点,结束搜索。
c)	将队列solve中的队首结点i取出,放入visited表中,visited_f中标记为1
d)	检测结点i是否为目标结点,如果是,则找到问题,结束搜索。
e)	如果结点i不可扩展,则转到第b)步。
f)	将与结点i相邻的,且没有被标记过(visited_f[i]=0)的所有结点加入solve表中,计算每个计算的f(i),h(i)和g(i),其中g(i)为父结点的g(n)加上i到n的距离G[i][n]
g)	转到第b)步。

算法实现
在这里插入图片描述
路径寻找的代码如下:从已访问结点中倒序搜索,当找到和父节点城市一致的结点时,将其加入路径中,并更新父节点城市find_p。
在这里插入图片描述

4) 其他

实验代码中增加了对算法时间的测量,可以对比不同算法求解同一个问题的时间效率。
在这里插入图片描述

2.思考题

1) 分析评估函数对A*搜索算法的影响

评估函数将会影响A搜索算法的准确程度。如果不考虑g(n)或者g(n)远小于h(n),那A搜索就会逐渐趋向于贪婪最佳优先搜索算法,搜索的最优性将会受到影响;如果不考虑h(n)或者h(n)远小于g(n),那A搜索算法就是趋向于Dijstra算法,可以保证找到最优解,但是需要巨大的时间消耗。因此,在实际使用A搜索算法时,选择一个合理的h(n),平衡两者,既保证最优性,也可以在有效时间内求解得出。

2) 分析A*搜索和贪婪最佳优先搜索的时间,空间复杂度,是否能得到最优解及理由。

假设b为分支因子(任何结点的后继的最大个数),d为最浅的目标结点的深度,m为状态空间中任何路径的最大长度。

对于贪婪最佳优先搜索,最坏的情况就是走状态空间中最长的路径,故时间,空间复杂度均为 O ( b m ) \mathrm{O}\left(\mathrm{b}^{m}\right) O(bm) 。它不是完备的,因为它可能沿着一条路径不断走下去,而不回溯搜索其他路径,同时它也不是最优的,不一定能找到状态空间中最优的。

对于A搜索,在最坏的情况下A也是需要走状态空间中最长的路径,故时间,空间复杂度均为 O ( b m ) \mathrm{O}\left(\mathrm{b}^{m}\right) O(bm) 。它既是最优的,也是完备的,最优性的证明如下

假设h(n)是可采纳的,则h(n)<h*(n)。h(n)也是一致的。
则对于从结点n到结点nj满足h(n)<=h(nj)+c(n,a,nj),其中c(n,a,nj)表示n通过动作a产生的后继nj。
因此有f(nj)=g(nj)+h(nj)=(g(n)+c(n,a,nj))+h(nj)>=g(n)+h(n)=f(n).
说明当前结点n是最优的,由此找到的目标结点也是最优的。

3) 深度优先搜索、一致代价搜索、A*搜索和贪婪最佳优先搜索分别属于无信息搜索还是有信息搜索?这两种搜索的策略有什么区别?

深度优先搜索,一致代价搜索属于无信息搜索,A*搜索,贪婪最佳优先搜索属于有信息搜索。

无信息搜索指的是除了问题定义中提供的状态信息外没有任何附加信息,只是简单计算达到各个结点所需要的消耗值并比较,有信息搜索策略使用问题本身的定义之外的特定知识,通过这些特定知识实现比无信息的搜索策略更有效的进行问题求解。

4) 对实验内容3的自定义启发函数证明其可采纳性。

启发式函数定义为h(n)=|hSD(Craiova)-hSD(n)|

假设m为编号为n城市到Craiova的直线距离,根据三角形两边之差小于第三边可以得到h(n)<m。

而根据两点之间直线段最短可以知道n到Craiova的直线距离小于n到Craiova的实际距离h*(n),即m< h*(n)。

所以h(n)<m< h*(n),故该启发式函数是可采纳的。

附录

输入文件

20
Arad 366
Bucharest 0
Craiova 160
Dobreta 242
Eforie 161
Fagaras 176
Giurgiu 77
Hirsova 151
Iasi 226
Lugoj 244
Mehadia 241
Neamt 234
Oradea 380
Pitesti 100
Rimnicu_Vilcea 193
Sibiu 253
Timisoara 329
Urziceni 80
Vaslui 199
Zerind 374
20 23
Arad Zerind 75
Arad Timisoara 118
Arad Sibiu 140
Zerind Oradea 71
Oradea Sibiu 151
Timisoara Lugoj 111
Lugoj Mehadia 70
Mehadia Dobreta 75
Dobreta Craiova 120
Craiova Rimnicu_Vilcea 146
Craiova Pitesti 138
Sibiu Fagaras 99
Sibiu Rimnicu_Vilcea 80
Rimnicu_Vilcea Pitesti 97
Fagaras Bucharest 211
Pitesti Bucharest 101
Giurgiu Bucharest 90
Bucharest Urziceni 85
Urziceni Hirsova 98
Hirsova Eforie 86
Urziceni Vaslui 142
Vaslui Iasi 92
Iasi Neamt 87

A*搜索的图搜索实现

#include <iostream>
#include <fstream>
#include <cstring> //memset 
#include <queue>
#include <vector>
#include <map>
#include <math.h>
#include <algorithm>
#include <windows.h> 
using namespace std;
LARGE_INTEGER fre;
LARGE_INTEGER start,end0; 
double dft=0;//时间 
struct Node {
	string city;
	int hsd;//到Bucharest的直线距离
	int gn; //到达当前结点n花费的实际代价
	int hn;//结点n到目标结点的评估代价
	int fn;//通过n到达目标结点的总评估代价
	string father_city; //记录父亲结点
	Node(string c,int h1,int g,int h,int f0,string f_c) { //构造函数
		city=c;
		hsd=h1;
		gn=g;
		hn=h;
		fn=f0;
		father_city=f_c;
	}
	friend bool operator<(Node a,Node b) { //自定义优先级
		return a.fn>b.fn;//按照估计代价从小到达进行排序
	}
	void display() {//输出调试 
		cout<<city<<" hsd:"<<hsd<<" gn:"<<gn<<" hn:"<<hn<<" fn:"<<fn<<endl;
	}

};
priority_queue<Node> solve;//存储可能选择的结点
vector<Node> visited;//存储已经访问的结点 
int visited_f[20]={0};
map<string,int> to_B;
int G[20][20];
int h_flag;
string city[20]= {"Arad","Bucharest","Craiova","Dobreta","Eforie","Fagaras","Giurgiu","Hirsova","Iasi","Lugoj","Mehadia","Neamt","Oradea",
                  "Pitesti","Rimnicu_Vilcea","Sibiu","Timisoara","Urziceni","Vaslui","Zerind"};
int find_city(string str) {
	for(int i=0; i<20; i++) if(city[i]==str) return i;
}
void input() 
{
	ifstream in("input0.txt",ios::in); //利用文件流读入数据
	string src,des;
	int n,m,dis,u,v,i=0;
	in>>n;
	for(i=0; i<n; i++) {
		in>>src>>dis;
		to_B[src]=dis;
		//cout<<src<<" "<<dis<<endl;
	}
	in>>n>>m;
	for(i=0; i<m; i++) {
		in>>src>>des>>dis;
		u=find_city(src);
		v=find_city(des);
		G[u][v]=dis;
		G[v][u]=dis;
//		cout<<src<<" "<<des<<" "<<dis<<endl;
	}
}
int cal_h(int flag,string city,string des_city)//选择启发式函数的计算方式 
{
	if(flag==1)
	   return to_B[city];//返回直线距离
	else if(flag==2) return  abs(to_B[des_city]-to_B[city]);//返回距离之差 
}
void path_print(vector<string> path) 
{
	for(int k=path.size()-1;k>=0;k--)
	{
		if(k)
			cout<<path[k]<<"-->"; 
		else cout<<path[k];
	}
	cout<<"\n";
}
void A_star(string src_city,string des_city,vector<string> &path)
{
	int hn=cal_h(h_flag,src_city,des_city); 
	Node s=Node(src_city,to_B[src_city],0,hn,0+hn,"null");
	solve.push(s);
	Node temp=s;
	int find_solution=0;
	while(!solve.empty()) 
	{
		temp=solve.top();//取出f(n)最小的一个结点
		visited.push_back(temp); //加入已访问 
		visited_f[find_city(temp.city)]=1; 
		solve.pop();//取出temp后要弹出队列
		if(temp.city==des_city) {//找到问题解,结束算法 
			find_solution=1;break;
		} 
		//将和当前结点 相邻的结点加入队列,即扩展当前结点 
		cout<<temp.city<<" 扩展的结点有:\n"; 
		for(int j=0; j<20; j++) 
		{
			int u=find_city(temp.city),real_dis;
			if(G[u][j]!=0 && !visited_f[j]) 
			{
				real_dis=temp.gn+G[u][j];//到达结点j的实际距离
				hn=cal_h(h_flag,city[j],des_city); //计算h(n) 
				Node ts=Node(city[j] , to_B[city[j]] , real_dis , hn ,real_dis+hn, temp.city);  
				solve.push(ts);
				ts.display();
			}
		}
		cout<<"---------------------------------------------------------\n";
	}
	path.push_back(temp.city); 
	string find_p=temp.father_city;
	for(int k=visited.size()-1;k>=0;k--)
	{
		if(visited[k].city==find_p)	{ 
			path.push_back(find_p);find_p=visited[k].father_city;
		}
	}	
	if(find_solution)
	{
		cout<<"路径长度为:"<<temp.gn<<endl; 
		path_print(path);
	}
} 
int main() 
{
	cout<<"请选择启发式函数h(n)\n1. h(n)=hSD(n)   2. h(n)=|hSD(des_city)-hSD(n)| \n";
	cin>>h_flag; 
	memset(G,0,sizeof(G));
	input();
	string des_city,src_city;
	vector<string> path;
	cout<<"\n请输入起始城市和目标城市\n";// Arad Bucharest   Fagaras Craiova
	cin>>src_city>>des_city;
	cout<<"\n";
	
	QueryPerformanceFrequency(&fre);//获得时钟频率
	QueryPerformanceCounter(&start);
	
	A_star(src_city,des_city,path);
	
	QueryPerformanceCounter(&end0);
	dft=(double)(end0.QuadPart-start.QuadPart)/(double)fre.QuadPart;
	cout<<"\nA*搜索算法求解所用时间:"<<dft*1000<<" ms\n";
	return 0;
}

贪婪最佳优先搜索的图搜索实现

#include <iostream>
#include <fstream>
#include <cstring> //memset 
#include <queue>
#include <vector>
#include <map>
#include <math.h>
#include <windows.h> 
using namespace std;
LARGE_INTEGER fre;
LARGE_INTEGER start,end0; 
double dft=0;//时间 
struct Node {
		string city;
	int hsd;//到Bucharest的直线距离
	int gn; //当前结点花费的实际代价
	int hn;//结点n到目标结点的评估代价
	string father_city; //记录父亲结点
	Node(string c,int h1,int g,int h,string f_c) { //构造函数
		city=c;
		hsd=h1;
		gn=g;
		hn=h;
		father_city=f_c;
	}
	friend bool operator<(Node a,Node b) { //自定义优先级
		return a.hn>b.hn;//按照估计代价从小到达进行排序
	}
	void display() {//调试 
		cout<<city<<" hsd:"<<hsd<<" gn:"<<gn<<" hn:"<<hn<<endl;
	}

};
priority_queue<Node> solve;//存储可能选择的结点
vector<Node> visited;//存储已经访问的结点 
int visited_f[20]={0};
map<string,int> to_B;
int G[20][20];
int h_flag;
string city[20]= {"Arad","Bucharest","Craiova","Dobreta","Eforie","Fagaras","Giurgiu","Hirsova","Iasi","Lugoj","Mehadia","Neamt","Oradea",
                  "Pitesti","Rimnicu_Vilcea","Sibiu","Timisoara","Urziceni","Vaslui","Zerind"};
int find_city(string str) {
	for(int i=0; i<20; i++) if(city[i]==str) return i;
}
void input() {
	ifstream in("input0.txt",ios::in); //利用文件流读入数据
	string src,des;
	int n,m,dis,u,v,i=0;
	in>>n;
	for(i=0; i<n; i++) {
		in>>src>>dis;
		to_B[src]=dis;
		//cout<<src<<" "<<dis<<endl;
	}
	in>>n>>m;
	for(i=0; i<m; i++) {
		in>>src>>des>>dis;
		u=find_city(src);
		v=find_city(des);
		G[u][v]=dis;
		G[v][u]=dis;
//		cout<<src<<" "<<des<<" "<<dis<<endl;
	}

}
int cal_h(int flag,string city,string des_city)//选择启发式函数的计算方式 
{
	if(flag==1)
	   return to_B[city];//返回直线距离
	else if(flag==2) return  abs(to_B[des_city]-to_B[city]);//返回距离之差 
}
void path_print(vector<string> path) 
{
	for(int k=path.size()-1;k>=0;k--)
	{
		if(k)
			cout<<path[k]<<"-->"; 
		else cout<<path[k];
	}
	cout<<"\n";
}
void Greed_search(string src_city,string des_city,vector<string> &path)
{
	int hn=cal_h(h_flag,src_city,des_city); 
	Node s=Node(src_city,to_B[src_city],0,hn,"null"); 
	solve.push(s);
	Node temp=s;
	int find_solution=0;
	while(!solve.empty()) 
	{
		temp=solve.top();//取出f(n)最小的一个结点 
		visited.push_back(temp); //加入已访问 
		visited_f[find_city(temp.city)]=1; 
		solve.pop();//取出temp后要弹出队列
		if(temp.city==des_city) {//找到问题解,结束算法 
			find_solution=1;break;
		} 
		//将和当前结点 相邻的结点加入队列,即扩展当前结点 
		cout<<temp.city<<" 扩展的结点有:\n"; 
		for(int j=0; j<20; j++) 
		{
			int u=find_city(temp.city),real_dis;
			if(G[u][j]!=0&& !visited_f[j]) 
			{
				real_dis=temp.gn+G[u][j];//到达结点j的实际距离
				hn=cal_h(h_flag,city[j],des_city);//计算h(n) 
				Node ts=Node(city[j] , to_B[city[j]] , real_dis , hn , temp.city);   
				solve.push(ts);//将temp的子节点入队 
				ts.display();//s输出显示 
			}
		}
		cout<<"---------------------------------------------------------\n";
	}
	//记录搜索路径 
	path.push_back(temp.city); 
	string find_p=temp.father_city;
	for(int k=visited.size()-1;k>=0;k--)
	{
		if(visited[k].city==find_p)	{ 
			path.push_back(find_p);find_p=visited[k].father_city;
		}
	}	
	if(find_solution)
	{
		cout<<"路径长度为:"<<temp.gn<<endl; 
		path_print(path);
	}
	else cout<<"没有找到解\n";
} 
int main() 
{
	cout<<"请选择启发式函数h(n)\n1. h(n)=hSD(n)   2. h(n)=|hSD(des_city)-hSD(n)| \n";
	cin>>h_flag; 
	memset(G,0,sizeof(G));
	input();
	string des_city,src_city;
	vector<string> path;
	cout<<"\n请输入起始城市和目标城市\n";// Arad Bucharest   Fagaras Craiova
	cin>>src_city>>des_city;
	cout<<"\n";
	
	QueryPerformanceFrequency(&fre);//获得时钟频率
	QueryPerformanceCounter(&start);
	
	Greed_search(src_city,des_city,path); 
	 
	QueryPerformanceCounter(&end0);
	dft=(double)(end0.QuadPart-start.QuadPart)/(double)fre.QuadPart;//计算时间 
	cout<<"\n贪婪最佳优先算法求解所用时间:"<<dft*1000<<" ms\n";
	return 0;
}

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值