天天看点

人工智能实验一

人工智能实验一

一.概述及要求

对于我们的实验,先进行一个总的说明,之后进行各个算法的详细介绍。由老师提供的readme文档可得,我们的这次试验要求我们对于书中学过的罗马尼亚问题进行所有经典搜索的实现,并对比他们的算法效率优劣。具体要求和参考步骤如下:

1:创建搜索树;

2:实现搜索树的宽度优先搜索,深度优先搜索,一致代价搜索,迭代加深的深度优先搜索算法;

3:实现贪婪最佳优先搜索和A*搜索

4:使用编写的搜索算法代码求解罗马尼亚问题;

5:记录各种算法的时间复杂度并绘制直方图

二.问题描述

使用搜索算法实现罗马尼亚问题的求解

人工智能实验一

根据书中说明我们假定起点是ARAD,终点是BUCHAREST。我们需要找到一条从起点到终点的路径。

三.数据结构及输入输出

1.数据结构

首先根据罗马尼亚问题的特点,我们把这个问题归结为图搜索问题,所以建树时不能忘记标记。同时我们注意到每个节点都有一个很长的英文名字,这不利于我们进行查找及搜索,想到使用map的相关操作来实现序号(数字)与他名字的对应。有了这些启示,我们可以建立如下的数据结构来容纳我们的算法。

人工智能实验一

建立标志数组,记录图中边的情况的二维矩阵,规定输入输出流,用input,output代替cin,cout。

同时规定了初始节点和终点。

优先队列比较标准:

由于我们的搜索算法是基于结点的不同信息,所以我们要为每一个节点建立结构体数组,分别保存他们的名字,序号,深度,从起点到该点的路径代价,从该点到终点的估计代价以及特定为优先队列设计的扩展标准,即,先扩展f较小的那一个。

Map操作:

这里比较重要的是使用了map的操作,这里利于我学习的map操作的知识如下:

map 容器的insert成员与顺序容器的类似,但有一点要注意:必须考虑键的作用。键影响了实参的类型:插入单个元素的insert版本使用键–值 pair 类型的参数。类似的,对于参数为一对迭代器的版本,迭代器必须指向键–值 pair 类型的元素。另一个差别则是:map容器的接受单个值的insert版本的返回类型。map对象中一个给定键只对应一个元素。如果试图插入的元素所对应的键已经在容器中,则insert 将不做任何操作。这个insert 函数的实参是map<string,int>::value_type(“Anna”,1)。是一个新创建的pair 对象,将直接插入到map 容器中。谨记 value_type是pair< const K, V>类型的同义词,K为键类型,V为键所关联的值的类型。

所以我们这里先定义一下map操作的名称,name_id表示name转id,id_name表示id转name。

2.输入函数设计

与之对应的,之后我们对map容器进行填充,两个map都进行填充,这样就实现了名字与编号的一一对应,并且是相互对应。

有了map这个基础后输入函数就可以直接输入名字来建立节点之间的关系。

如图,在城市数量限制下循环输入,直接输入名字后利于map操作将他们转换为id,因此可以直接用名字后的数字来表示点点之间的路径长度。同理,我们可以将每个节点到终点的估计代价填充完毕。具体输入文件如下:

四.不同算法实现

1.DFS

实验名称

用深度优先搜索算法实现罗马尼亚问题

实验目的 尝试用深搜解决路径最短问题。从而发现不同算法对于这个问题的优劣性。为以后的问题解决积累经验。

实验原理

从一个顶点v出发,首先将v标记为已遍历的顶点,然后选择一个邻接于v的尚未遍历的顶点u,如果u不存在,本次搜素终止。如果u存在,那么从u又开始一次DFS。如此循环直到不存在这样的顶点。

假设有如下树,深度优先举例:

实验步骤

初始准备:建立栈,之后为节点访问数组动态分配好空间,初始化起点,将起点加入栈中,同时输出起点信息。

扩展:只要栈不为空,先进行终点判断,如果是终点,就输出路径代价,这个路径代价记录在结构体的g中。如果不是终点,我们就看这个节点与其他节点是否有边且没有访问过,如果满足条件,就扩展该节点到边连接的下一节点,实现扩展。

建立关系:下一节点要与之前的节点建立关系,首先是定义它的父节点为当前节点,再将这个节点定义为当前节点,最后在输出这个节点的信息。

回溯:如果搜索完所有其他节点都没有满足判断条件,就将该节点出栈,实现回溯。

重点:使用栈的结构,后进先出,所以刚刚扩展的节点会被接着扩展,不断加深,实现深搜。

关键代码

void DFS(){ //深度优先搜索,使用栈,后进先出,lifo

stacks;//建立栈,内部数据存储类型为node

memset(mark,0,sizeof(mark));//节点访问标志

node start;//定义初始节点

start.id = name_id[Start];

start.g = 0;

s.push(start);//初始节点入栈

output<<“DFSmodel:”<<endl;

output<<Start<<"->";

while(!s.empty()){

node temp = s.top();

if(temp.id == name_id[End]){

output<<“end”<<endl;

output<<“路径代价为:”<<temp.g<<endl;

break;

}

mark[temp.id] = 1;//访问过添加标记

int x=0;

for(;x<num;x++){

if(!mark[x]&&bian[temp.id][x]!=0){//没有访问过且当前节点与要加入的新节点有边相连

node t;

t.id = x;

t.g = temp.g + bian[temp.id][x];//路径代价累计

fu[t.id] = temp.id;//当前节点变为下一节点的父节点

s.push(t);

string name=id_name[t.id];

output<<name<<"->" ;

break;

}

}

if(x==num)

s.pop();//出栈,当搜索完所有其他所有未标记节点也没有找到可行节点时出栈,实现回溯。

}

} }

2.BFS

实验名称

用宽度优先搜索算法实现罗马尼亚问题

实验目的

尝试用宽搜解决路径最短问题。从而发现不同算法对于这个问题的优劣性。为以后的问题解决积累经验。

实验原理

按层次来遍历的,先是根节点,然后是第二层子节点,依次是第三层子节点,将节点分别放入队列中,每次从队列中探出首元素,遍历后的点放入closed表中,还未遍历的店放入open表中,当open表为空时,则整个数遍历完全。

假设有如下树,宽度优先举例:

实验步骤

初始准备:建立队列,之后为节点访问数组动态分配好空间,初始化起点,将起点加入队列中,同时输出起点信息。

扩展:只要队列不为空,先进行终点判断,如果是终点,就输出路径代价,这个路径代价记录在结构体的g中。如果不是终点,我们就看这个节点与其他节点是否有边且没有访问过,如果满足条件,就扩展该节点到边连接的下一节点,实现扩展。

建立关系:下一节点要与之前的节点建立关系,首先是定义它的父节点为当前节点,再将这个节点定义为当前节点,最后在输出这个节点的信息。

重点:使用队列的结构,先进先出,所以最后扩展的节点会被首先扩展,将一层节点扩展完后进入下一层,实现宽搜。

关键代码

void BFS(){ //宽度优先搜索

memset(mark,0,sizeof(mark));

node q[num*4];//建立node型数组,实际上作为队列,实现先进先出的扩展顺序。

for(int x=0;x<num;x++) fu[x]=x;

int l=0,r=1;

q[l].id = name_id[Start];

q[l].g = 0;

output<<“BFSmodel:”<<endl;

while(l<r){

node temp = q[l];

if(temp.id==name_id[End]){

output<<“end”<<endl;

output<<“路径代价为:”<<q[l].g<<endl;

break;

}

mark[temp.id]=1;

for(int x=0;x<num;x++){

if(!mark[x]&&bian[temp.id][x]!=0){

q[r].id = x;

q[r].g = temp.g + bian[temp.id][x];

fu[q[r].id] = temp.id;//当前节点为扩展出节点的父节点

r++;//扩展出来的节点逐渐后排

}
    }
	string name=id_name[temp.id];
    output<<name<<"->" ;
    l++;//扩展下一节点 
           

}

}

3.UCS

实验名称

用一致代价搜索算法实现罗马尼亚问题

==实验目的 ==

尝试用UCS解决路径最短问题。从而发现不同算法对于这个问题的优劣性。为以后的问题解决积累经验。

实验原理

在BFS的基础上,一致代价搜索不在扩展深度最浅的节点,而是通过比较路径消耗,并选择当前代价最小的节点进行扩展,因此可以保证无论每一步代价是否一致,都能够找到最优解。

假设有如下树,一致代价优先举例:

实验步骤

初始准备:建立队列,之后为节点访问数组动态分配好空间,初始化起点,将起点加入队列中,同时输出起点信息。

扩展:只要队列不为空,先进行终点判断,如果是终点,就输出路径代价,这个路径代价记录在结构体的g中。如果不是终点,我们就看这个节点与其他节点是否有边且没有访问过,如果满足条件,就扩展该节点到边连接的下一节点,实现扩展。

建立关系:下一节点要与之前的节点建立关系,首先是定义它的父节点为当前节点,再将这个节点定义为当前节点,最后在输出这个节点的信息。

重点:同样使用队列的结构,但不是先进先出,他用了优先队列,会根据启发式函数进行扩展,这里定义F是G,所以总路径代价最小的节点会被首先扩展。

关键代码

void UCS(){ //一致代价搜索

memset(mark,0,sizeof(mark));

for(int x=0;x<num;x++) fu[x]=x;

node start;

start.g = 0;

start.f = 0;

start.id = name_id[Start];

output<<“UCSmodel:”<<endl;

output<<Start<<"->";

priority_queue<node, vector, greater >p;//优先队列

p.push(start);

while(!p.empty()){

node temp = p.top();

if(temp.id==name_id[End]){

output<<“end”<<endl;

output<<“路径代价为:”<<p.top().g<<endl;

break;

}

mark[temp.id]=1;

p.pop();

for(int x=0;x<num;x++){

if(!mark[x]&&bian[temp.id][x]!=0){

node t;

t.id = x;

t.g = temp.g + bian[temp.id][x];

t.f = t.g;//启发式函数定义为路径耗散的加和。

fu[t.id] = temp.id;

string name=id_name[t.id];

output<<name<<"->" ;

p.push(t);

}

}

}

}

4.IDS

实验名称

用迭代加深搜索算法实现罗马尼亚问题

实验目的

尝试用迭代加深解决路径最短问题。从而发现不同算法对于这个问题的优劣性。为以后的问题解决积累经验。

==实验原理 ==

迭代加深搜索是以DFS为基础的,它限制DFS递归的层数。

迭代加深搜索的基本步骤是:

1、设置一个固定的深度depth,通常是depth = 1,即只搜索初始状态

2、DFS进行搜索,限制层数为depth,如果找到答案,则结束,如果没有找到答案 则继续下一步

3、如果DFS途中遇到过更深的层,则++depth,并重复2;如果没有遇到,说明搜 索已经结束,没有答案。

实验步骤

初始准备:建立栈,之后为节点访问数组动态分配好空间,初始化起点,将起点加入栈中,同时输出起点信息。不同的是还要规定最大深度。

扩展:只要栈不为空,先进行终点判断,如果是终点,就输出路径代价,这个路径代价记录在结构体的g中。如果不是终点,我们就看这个节点与其他节点是否有边且没有访问过,如果满足条件,就扩展该节点到边连接的下一节点,实现扩展。

建立关系:下一节点要与之前的节点建立关系,首先是定义它的父节点为当前节点,再将这个节点定义为当前节点,最后在输出这个节点的信息。

回溯:如果搜索完所有其他节点都没有满足判断条件,就将该节点出栈,实现回溯。

深度判断:首先是扩展节点时有是否超过当前深度的判断,其次是最后再加一层当前深度是否超过最大深度的判断,如果已经超过,就脱出函数。不超过重新载入,当前深度加一。

重点:同样使用栈的结构,后进先出,所以刚刚扩展的节点会被接着扩展,不断加深,实现深搜。但是再这个基础上加上深度判断。

关键代码

void IDS(){ //迭代加深的深度优先搜索

memset(mark,0,sizeof(mark));

int cur_dep = 1;

node start;

start.id = name_id[Start];

start.g = 0;

start.dep = 0;

stacks;//建立栈

s.push(start);

output<<“IDSmodel:”<<endl;

output<<Start<<"->";

while(!s.empty()){

node temp = s.top();

if(temp.id == name_id[End]){

output<<“end”<<endl;

output<<“路径代价为:”<<temp.g<<endl;

break;

}

mark[temp.id] = 1;

int x=0;

for(;x<num;x++){

if(!mark[x]&&bian[temp.id][x]!=0&&temp.dep <= cur_dep){

node t;

t.id = x;

t.g = temp.g + bian[temp.id][x];

fu[t.id] = temp.id;

t.dep = temp.dep + 1;

string name=id_name[t.id];

output<<name<<"->" ;

s.push(t);

break;

}

}

if(x==num)

s.pop();//同样的回溯过程

if(s.empty()){//遍历完所有节点后仍找不到目标节点,增加迭代加深过程,而不是直接退出

memset(mark,0,sizeof(mark));

s.push(start);//所有过程重新来

cur_dep++;//增加深度上限

}

}

}

5.GBFS

实验名称

用贪婪最佳优先搜索算法实现罗马尼亚问题

实验目的

尝试解决路径最短问题。从而发现不同算法对于这个问题的优劣性。为以后的问题解决积累经验。

实验原理

最佳优先搜索(Best First Search),是一种启发式搜索算法(Heuristic Algorithm),我们也可以将它看做广度优先搜索算法的一种改进;最佳优先搜索算法在广度优先搜索的基础上,用启发估价函数对将要被遍历到的点进行估价,然后选择代价小的进行遍历,直到找到目标节点或者遍历完所有点,算法结束。

实验步骤 初始准备:建立队列,之后为节点访问数组动态分配好空间,初始化起点,将起点加入队列中,同时输出起点信息。

扩展:只要队列不为空,先进行终点判断,如果是终点,就输出路径代价,这个路径代价记录在结构体的g中。如果不是终点,我们就看这个节点与其他节点是否有边且没有访问过,如果满足条件,就扩展该节点到边连接的下一节点,实现扩展。

建立关系:下一节点要与之前的节点建立关系,首先是定义它的父节点为当前节点,再将这个节点定义为当前节点,最后在输出这个节点的信息。

重点:同样使用队列的结构,但不是先进先出,他用了优先队列,会根据启发式函数进行扩展,这里定义F是估计值gu[],所以到终点估计距离最小的节点会被首先扩展。

关键代码

void GBFS(){ //贪婪最佳优先搜索

memset(mark,0,sizeof(mark));

node start;

start.id = name_id[Start];

start.g = 0;

start.f = gu[start.id];

priority_queue<node, vector, greater >p;

p.push(start);

output<<“GBFSmodel:”<<endl;

output<<Start<<"->";

int sum=0;

while(!p.empty()){

node temp = p.top();
       if(temp.id==name_id[End]){
           output<<"end"<<endl;
           output<<"路径代价为:"<<p.top().g<<endl;
           break;
      }
      mark[temp.id]=1;
      p.pop();
      for(int x=0;x<num;x++){
         if(!mark[x]&&bian[temp.id][x]!=0){
            node t;
            t.id = x;
            t.g = temp.g + bian[temp.id][x];
            fu[t.id]=temp.id;
            t.f = gu[t.id];//启发式函数定义为路径估计值。 
            p.push(t);
            string name=id_name[t.id];
            output<<name<<"->" ;
        }
    }
 }
           

}

6.AS

实验名称

用A*搜索算法实现罗马尼亚问题

实验目的 尝试用宽搜解决路径最短问题。从而发现不同算法对于这个问题的优劣性。为以后的问题解决积累经验。

实验原理

基本原理:

公式表示为: f(n)=g(n)+h(n),

其中 f(n) 是从初始点经由节点n到目标点的估价函数,

g(n) 是在状态空间中从初始节点到n节点的实际代价,

h(n) 是从n到目标节点最佳路径的估计代价。

保证找到最短路径(最优解的)条件,关键在于估价函数f(n)的选取

实验步骤

初始准备:建立队列,之后为节点访问数组动态分配好空间,初始化起点,将起点加入队列中,同时输出起点信息。

扩展:只要队列不为空,先进行终点判断,如果是终点,就输出路径代价,这个路径代价记录在结构体的g中。如果不是终点,我们就看这个节点与其他节点是否有边且没有访问过,如果满足条件,就扩展该节点到边连接的下一节点,实现扩展。

建立关系:下一节点要与之前的节点建立关系,首先是定义它的父节点为当前节点,再将这个节点定义为当前节点,最后在输出这个节点的信息。

重点:同样使用队列的结构,但不是先进先出,他用了优先队列,会根据启发式函数进行扩展,这里定义F是估计值gu[]加上当前路径总代价G,所以F最小的节点会被首先扩展。

关键代码

void AS(){ //A*搜索

memset(mark,0,sizeof(mark));

node start;

start.id = name_id[Start];

start.g = 0;

start.f = start.g + gu[start.id];

priority_queue<node, vector, greater >p;

p.push(start);

output<<“ASmodel:”<<endl;

output<<Start<<"->";

while(!p.empty()){

node temp = p.top();

if(temp.id==name_id[End]){

output<<“end”<<endl;

output<<“路径代价为:”<<p.top().g<<endl;

break;

}

mark[temp.id]=1;

p.pop();

for(int x=0;x<num;x++){

if(!mark[x]&&bian[temp.id][x]!=0){

node t;

t.id = x;

t.g = temp.g + bian[temp.id][x];

fu[t.id]=temp.id;

t.f = t.g + gu[t.id];

p.push(t);

string name=id_name[t.id];

output<<name<<"->" ;

}

}

}

}

五.输出结果总览及对比:

人工智能实验一

六.思考题

1:宽度优先搜索,深度优先搜索,一致代价搜索,迭代加深的深度优先搜索算法哪种方法最优?

针对于我们这个罗马尼亚问题来说,一致代价搜索找到了最优解,不过对于其他问题并不一定,要看具体构建好的树的结构。如果是比较深的解,用深搜好点,比较浅的解用宽搜。深搜宽搜是两种基础思考模式,优化的话可以用基于此思想的其他搜索模式

2:贪婪最佳优先搜索和A搜索那种方法最优?

A搜索更优,由于A搜索考虑到的f(n)=g(n)+h(n),但是贪婪最佳优先只考虑了h(n),而且A搜索既是最优的,又是完备的。贪婪最佳优先没有这种特性。

3:分析比较无信息搜索策略和有信息搜索策略。

人工智能中的搜索策略大体分为两种:无信息搜索和有信息搜索。无信息搜索是指我们不知道接下来要搜索的状态哪一个更加接近目标的搜索策略,因此也常被成为盲目搜索;而有信息搜索则是用启发函数f(n)来衡量哪一个状态更加接近目标状态,并优先对该状态进行搜索,因此与无信息搜索相比往往能够更加高效得解决问题。

继续阅读