人工智能 实验一:搜索算法问题求解 罗马尼亚问题(宽度优先搜索,深度优先搜索,一致代价搜索,迭代加深的深度优先搜索,贪婪最佳优先搜索和A*搜索) 算法详解 + 完整代码

人工智能 实验一:搜索算法问题求解(最后有完整代码!)

实验标题:

实验一:搜索算法问题求解

实验目的:

  • 了解4种无信息搜索策略和2种有信息搜索策略的算法思想;
  • 能够运用计算机语言实现搜索算法;
  • 应用搜索算法解决实际问题(如罗马尼亚问题);
  • 学会对算法性能的分析和比较

实验的硬件、软件平台:

硬件:计算机
软件:操作系统:WINDOWS
应用软件:C++

实验内容及步骤:

使用搜索算法实现罗马尼亚问题的求解
在这里插入图片描述
1) 创建搜索树;
2) 实现搜索树的宽度优先搜索,深度优先搜索,一致代价搜索,迭代加深的深度优先搜索算法;
3) 实现贪婪最佳优先搜索和A*搜索
4) 使用编写的搜索算法代码求解罗马尼亚问题;
5) 记录各种算法的时间复杂度并绘制直方图

思考题:

1) 宽度优先搜索,深度优先搜索,一致代价搜索,迭代加深的深度优先搜索算法哪种方法最优?
2) 贪婪最佳优先搜索和A*搜索那种方法最优?
3) 分析比较无信息搜索策略和有信息搜索策略。

罗马尼亚问题的图的构建:

罗马尼亚问题中的每一个城市的城市信息:
罗马尼亚问题中有多个城市,城市和城市之间是有直接或者间接的联系,而且每个城市和目标城市也有相关的联系,所以需要全面具体地记录保存罗马尼亚问题中的每个城市的城市信息,方便后期的各种搜索算法和搜索树的构建。

每一个城市的城市信息应该包含:

  • 这个城市的城市名
  • 有几个城市和当前这个城市相邻,即通过一条边和当前这个城市相连
  • 记录保存每个和当前这个城市相邻的城市的城市名和相应的路径代价
  • 保存该城市到目标城市的最小代价路径的估计值

每一个城市的城市信息保存代码实现如下:

typedef pair<string,int> neighbor;  //当前城市的相邻城市的城市名和对应的路径代价
struct state            //罗马尼亚问题中的每一个城市的城市信息
{
    string name;        //该城市的城市名
    int neighbor_num;   //与该城市相邻的有几个城市
    map<int,neighbor> nextstate;    //记录每个相邻的城市和对应的路径代价
    int h;              //该城市到目标城市的最小代价路径的估计值
};

罗马尼亚问题的图:
在罗马尼亚问题中,我们需要知道总共有多少个城市并记录保存。我们使用 邻接表 法对罗马尼亚问题的图进行保存,即一个城市名对应该城市的城市信息,可以根据该城市的信息到达其相邻的城市,即完成整个罗马尼亚问题的图。

在进行图搜索的过程中,我们需要知道一个城市是否已经被讨论并拓展过,所以需要记录保存各个城市的状态,得知该城市是否已经被拓展过。

罗马尼亚问题的图的各个变量定义如下:

int state_num = 0;          //罗马尼亚问题中的每一个城市
map<string,state> graph;    //邻接表保存罗马尼亚问题的图:<城市名,城市信息>
map<string,int> explored;   //罗马尼亚问题中的城市是否被拓展过

罗马尼亚问题的图的构建:
首先,知道罗马尼亚问题的图中有多少个城市,然后再依次对每一个城市的城市信息进行初始化,输入各个城市的城市信息过程包括:

  • 输入当前城市的城市名
  • 输入当前城市到目标城市的最小代价路径的估计值
  • 输入当前城市有几个相邻的城市
  • 输入保存每一个相邻的城市(城市名 + 到相邻城市的路径代价)

在对各个城市的城市信息输入结束后,在罗马尼亚问题的图中添加该城市,即将城市名和对应城市的城市信息保存在图中,并且初始化当前城市未被拓展过,然后继续下一个城市的城市信息输入。

罗马尼亚问题的图的构建代码实现如下(文件输入):

void File_Input(string s)
{
    ifstream f(s);      //导入描述罗马尼亚问题详细信息的文件
    f >>state_num;      //输入罗马尼亚问题的总城市数目
    for(int i=0;i<state_num;i++){   //初始化每一个城市的城市信息
        state t;
        f >> t.name;            //当前城市的城市名
        f >> t.h;               //当前城市到目标城市的最小代价路径的估计值
        f >> t.neighbor_num;    //当前城市有几个相邻的城市
        for(int j=0;j<t.neighbor_num;j++){  //保存每一个相邻的城市
            f >>t.nextstate[j].first;       //保存相邻城市的城市名
            f >>t.nextstate[j].second;      //保存到相邻城市的路径代价
        }
        graph[t.name] = t;      //在罗马尼亚问题的图中添加当前城市
        explored[t.name] = 0;   //初始化当前城市未被拓展过
    }
}

描述罗马尼亚问题详细城市信息的文件( Romania.txt )如下:

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

创建搜索树:

搜索树中的树结点定义:
在搜索树中,每一个树结点应该记录的信息包括:

  • 该树结点有几个孩子结点
  • 该树结点的代价估计值(启发式)
  • 该树结点的深度(主要用于迭代加深的深度优先搜索算法)
  • 该树结点对应在哪个城市(包含该城市的城市信息)
  • 保存该树结点的父亲结点(指针)
  • 保存该树结点的所有孩子结点(指针)

搜索树中的树结点代码实现:

struct Treenode         //搜索树中的树结点定义
{
    int child_num;      //该树结点有几个孩子结点
    int value;          //该树结点的代价估计值(启发式)
    int depth;          //该树结点的深度
    state theState;     //该树结点对应在哪个城市
    Treenode * father;  //保存该树结点的父亲结点(指针)
    map<int,Treenode *> child;  //保存该树结点的所有孩子结点(指针)
};

创建一个新的树结点:
创建一个新的树结点,需要初始化树结点中的各种信息,代码实现如下:

Treenode * Create_node()    //创建一个树结点,返回该树结点的指针
{
    Treenode * node = new Treenode; //申请一个新的树结点
    node->child_num = 0;    //初始化树结点的孩子结点数目为 0
    node->value = 0;        //初始化树结点的代价估计值(启发式)为 0
    node->depth = 0;        //初始化树结点的深度为 0
    node->father = NULL;    //初始化树结点的父亲结点(指针)为空
    node->child.clear();    //初始化树结点不存在孩子结点(指针)
    return node;            //返回该树结点的指针
}

无信息搜索策略

宽度优先搜索算法:

算法原理说明:

宽度优先搜索是简单搜索策略,先扩展根结点,接着扩展根结点的所有后继,然后再扩展它们的后继,依此类推。一般地,在下一层的任何结点扩展之前,搜索树上本层深度的所有结点都应该已经扩展过。

宽度优先搜索是一般图搜索算法的一个实例,每次总是扩展深度最浅的结点。这可以通过将边缘组织成 FIFO 队列来实现。就是说,新结点(结点比其父结点深)加入到队列尾,这意味着浅层的老结点会在深层结点之前被扩展。

宽度优先搜索算法具有一般的图搜索框架,忽视所有到边缘结点或已经扩展结点的新路径,可以看出这样的路径至少和已经找到的一样深,所以,宽度优先搜索总是有到每一个边缘结点的最浅路径。

如果最浅的目标结点处于一个有限深度 d,那么宽度优先搜索是完备的,宽度优先搜索在扩展完比它浅的所有结点之后最终一定能找到该目标结点。但是,最浅的目标结点不一定是最优的目标结点。

时间复杂度:O(b^d )
空间复杂度:O(b^d )

程序实现和说明:

实现 宽度优先搜索算法 的关键数据结构是 队列,具体的流程如下:

  1. 获取开始城市的城市信息,创建搜索树的根结点,将根结点的城市设置为开始城市,将开始城市加入待拓展的城市集合中。
  2. 判断此时还存在待拓展的城市:
    2.1. 取出 当前待拓展的城市队列中队首 的需要拓展的城市,获取当前需要拓展的城市城市信息,并将当前城市设置为已经被拓展过。
    2.2. 此时判断当前城市是不是目标城市,如果是,则输出搜索的路径,成功搜索到目标,搜索结束。若不是目标城市,则继续下一步。
    2.3. 拓展当前城市的所有相邻城市。对于每一个相邻的城市,获取相邻城市的城市名,并创建一个新的子结点,将当前相邻的城市城市信息、代价估计值赋给子结点,子结点的父亲结点是当前正在拓展的结点,并且在当前正在拓展的结点中加入该子结点,将当前正在拓展的结点中孩子结点数加一。
    2.4. 判断该相邻城市是否已经被拓展过,如果该相邻城市没被拓展过,则将该相邻城市加入待拓展的城市队列中
  3. 判断已经不存在待拓展的城市:找不到可以到目标城市的路径,搜索失败。
代码程序如下:
void Breadth_First_Search(string start,string goal) //宽度优先搜索算法
{
    state start_state = graph[start];   //获取开始城市的城市信息
    Treenode *root = Create_node();     //创建搜索树一个根结点
    queue<Treenode *> Q;                //记录保存待拓展的城市集合
    root->theState = start_state;       //根结点的城市为开始城市
    Q.push(root);                       //将开始城市加入待拓展的城市集合(根结点)
    while(!Q.empty()){                  //还存在待拓展的城市
        Treenode * current = Q.front();             //取出当前需要拓展的城市
        Q.pop();
        state current_state = current->theState;    //获取当前城市的城市信息
        cout <<"State name : " <<current_state.name <<"\tValue : " <<current->value <<endl;
        explored[current_state.name] = 1;           //将当前城市设置为已经拓展过
        
        if(current_state.name == goal){             //判断当前城市是不是目标城市
                cout <<endl <<endl <<"Breadth_First_Search Route : ";
                Route(current,start);               //输出搜索的路径
                return;                             //搜索结束,返回
        }
        for(int i=0;i<current_state.neighbor_num;i++){                  //拓展当前城市的所有相邻城市
            string nextstate_name = current_state.nextstate[i].first;   //获取相邻城市的城市名
            Treenode * current_child = Create_node();                   //创建一个新的子结点
            current_child->theState = graph[nextstate_name];            //子结点的城市信息
            current_child->value = current->value + current_state.nextstate[i].second;  //子结点的代价估计值
            current_child->father = current;                            //子结点的父亲结点是当前正在拓展的结点
            current->child[current->child_num] = current_child;         //在当前正在拓展的结点中加入该子结点
            current->child_num++;                   //当前正在拓展的结点中孩子结点数加一

            cout <<"\tChild State : " <<nextstate_name <<"\tValue : " <<current_child->value;
            if(explored[nextstate_name] == 0){      //判断该相邻城市是否已经被拓展过
                Q.push(current_child);              //没被拓展过,加入待拓展的城市集合(树结点)
            }
        }
        cout <<endl<<endl;
    }
    cout <<"Failure" <<endl;        //找不到可以到目标城市的路径,搜索失败
    return;
}
输出搜索路径的函数代码实现如下:
void Route(Treenode * c,string s)   //递归得到搜索路径
{
    if(c->theState.name == s){      //当前城市为目标城市
        //输出当前城市和代价估计值(启发式)
        cout <<c->theState.name <<" v : "<<c->value <<" -> ";
        return;
    }
    Route(c->father,s);     //当前城市还不是目标城市,继续递归
    //输出当前城市和代价估计值(启发式)
    cout <<c->theState.name <<" v : "<<c->value <<" -> ";
    return;
}
实验结果分析:

宽度优先搜索算法解决罗马尼亚问题的搜索路径:
Breadth_First_Search Route :
Arad v : 0 -> Sibiu v : 140 -> Fagaras v : 239 -> Bucharest v : 450 ->

程序运行时间 Run Time :
36.000 ms

一致代价搜索算法:

算法原理说明:

当每一步的行动代价都相等时宽度优先搜索是最优的,因为它总是先扩展深度最浅的未扩展结点。更进一步,我们可以找到一个对任何单步代价函数都是最优的算法。不再扩展深度最浅的结点,一致代价搜索扩展的是路径消耗最小的结点。这可以通过将边缘结点集组织成按单步代价值排序的优先队列来实现。

除了按路径代价对队列进行排序外,一致代价搜索和宽度优先搜索有两个显著不同。第一是目标检测应用于结点被选择扩展时,而不是在结点生成的时候进行。理由是第一个生成的目标结点可能在次优的路径上。第二个不同是如果边缘中的结点有更好的路径到达该结点那么会引入一个测试。

时间复杂度:O(b^(1+[C*/ε] ) )
空间复杂度:O(b^(1+[C*/ε] ) )

程序实现和说明:

实现 一致代价搜索算法 的关键数据结构是 优先队列,具体的流程如下:

  1. 获取开始城市的城市信息,创建搜索树的根结点,将根结点的城市设置为开始城市,将开始城市加入待拓展的城市集合中。
  2. 判断此时还存在待拓展的城市:
    2.1. 取出 当前待拓展的城市优先队列中队首 的需要拓展的城市,获取当前需要拓展的城市城市信息,并将当前城市设置为已经被拓展过。
    2.2. 此时判断当前城市是不是目标城市,如果是,则输出搜索的路径,成功搜索到目标,搜索结束。若不是目标城市,则继续下一步。
    2.3. 拓展当前城市的所有相邻城市。对于每一个相邻的城市,获取相邻城市的城市名,并创建一个新的子结点,将当前相邻的城市城市信息、代价估计值赋给子结点,子结点的父亲结点是当前正在拓展的结点,并且在当前正在拓展的结点中加入该子结点,将当前正在拓展的结点中孩子结点数加一。
    2.4. 判断该相邻城市是否已经被拓展过,如果该相邻城市没被拓展过,则将该相邻城市加入待拓展的城市优先队列中
  3. 判断已经不存在待拓展的城市:找不到可以到目标城市的路径,搜索失败。
代码程序如下:

程序中使用自己写的 sort() 排序实现优先队列的功能。

void Uniform_Cost_Search(string start,string goal)  //一致代价搜索算法
{
    state start_state = graph[start];   //获取开始城市的城市信息
    Treenode *root = Create_node();     //创建搜索树一个根结点
    queue<Treenode *> Q;                //记录保存待拓展的城市集合
    root->theState = start_state;       //根结点的城市为开始城市
    Q.push(root);                       //将开始城市加入待拓展的城市集合(根结点)
    while(!Q.empty()){                  //还存在待拓展的城市
        Treenode * current = Q.front();             //取出当前需要拓展的城市
        Q.pop();
        state current_state = current->theState;    //获取当前城市的城市信息
        cout <<"State name : " <<current_state.name <<"\tValue : " <<current->value <<endl;
        explored[current_state.name] = 1;           //将当前城市设置为已经拓展过
        
        if(current_state.name == goal){             //判断当前城市是不是目标城市
                cout <<endl <<endl <<"Uniform_Cost_Search Route : ";
                Route(current,start);               //输出搜索的路径
                return;                             //搜索结束,返回
        }
        for(int i=0;i<current_state.neighbor_num;i++){                  //拓展当前城市的所有相邻城市
            string nextstate_name = current_state.nextstate[i].first;   //获取相邻城市的城市名
            Treenode * current_child = Create_node();                   //创建一个新的子结点
            current_child->theState = graph[nextstate_name];            //子结点的城市信息
            current_child->value = current->value + current_state.nextstate[i].second;  //子结点的代价估计值
            current_child->father = current;                            //子结点的父亲结点是当前正在拓展的结点
            current->child[current->child_num] = current_child;         //在当前正在拓展的结点中加入该子结点
            current->child_num++;                   //当前正在拓展的结点中孩子结点数加一

            cout <<"\tChild State : " <<nextstate_name <<"\tValue : " <<current_child->value;
            if(explored[nextstate_name] == 0){      //判断该相邻城市是否已经被拓展过
                Q.push(current_child);              //没被拓展过,加入待拓展的城市集合(树结点)
            }
        }
        Q = sort(Q);            //进行优先队列的排序,实现优先队列的功能
        cout <<endl<<endl;
    }
    cout <<"Failure" <<endl;    //找不到可以到目标城市的路径,搜索失败
    return;
}
queue<Treenode *> sort(queue<Treenode *> q)
{
    queue<Treenode *> s;
    map<float,Treenode *> m;
    int i=0;
    while(!q.empty()){
        i++;
        Treenode * t = q.front();
        q.pop();
        if(m[t->value] == NULL)
            m[t->value] = t;
        else{
            m[t->value + i * 0.01] = t;
        }
    }
    map<float,Treenode *>::iterator mit;
    for(mit=m.begin();mit!=m.end();mit++)
        s.push(mit->second);
    return s;
}
输出搜索路径的函数代码实现如下:
void Route(Treenode * c,string s)   //递归得到搜索路径
{
    if(c->theState.name == s){      //当前城市为目标城市
        //输出当前城市和代价估计值(启发式)
        cout <<c->theState.name <<" v : "<<c->value <<" -> ";
        return;
    }
    Route(c->father,s);     //当前城市还不是目标城市,继续递归
    //输出当前城市和代价估计值(启发式)
    cout <<c->theState.name <<" v : "<<c->value <<" -> ";
    return;
}
实验结果分析:

一致代价搜索算法解决罗马尼亚问题的搜索路径:
Uniform_Cost_Search Route :
Arad v : 0 -> Sibiu v : 140 -> RimnicuVilcea v : 220 -> Pitesti v : 317 -> Bucharest v : 418

程序运行时间 Run Time :
224.000 ms

深度优先搜索算法:

算法原理说明:

深度优先搜索总是扩展搜索树的当前边缘结点集中最深的结点。搜索很快推进到搜索树的最深层,那里的结点没有后继。当那些结点扩展完之后,就从边缘结点集中去掉,然后搜索算法回溯到下一个还有未扩展后继的深度稍浅的结点。

宽度优先搜索使用 FIFO 队列,深度优先搜索使用 LIFO 队列。LIFO 队列指的是最新生成的结点最早被选择扩展。这一定是最深的未被扩展的结点,因为它比它的父结点深 1,上一次扩展的则是这个父结点因为它最深。

深度优先搜索算法的效率严重依赖于使用的是图搜索还是树搜索。避免重复状态和冗余路径的图搜索,在有限状态空间是完备的,因为它至多扩展所有结点。而树搜索,则不完备,算法会陷入死循环。同样的原因,无论是基于图搜索还是树搜索的深度优先搜索都不是最优的。深度优先搜索的时间复杂度受限于状态空间的规模。

时间复杂度:O(b^m )
空间复杂度:O(bm)

程序实现和说明:

实现 深度优先搜索算法 的关键数据结构是 ,具体的流程如下:

  1. 获取开始城市的城市信息,创建搜索树的根结点,将根结点的城市设置为开始城市,将开始城市加入待拓展的城市集合中。
  2. 判断此时还存在待拓展的城市:
    2.1. 取出 当前待拓展的城市栈中栈顶 的需要拓展的城市,获取当前需要拓展的城市城市信息,并将当前城市设置为已经被拓展过。
    2.2. 此时判断当前城市是不是目标城市,如果是,则输出搜索的路径,成功搜索到目标,搜索结束。若不是目标城市,则继续下一步。
    2.3. 拓展当前城市的所有相邻城市。对于每一个相邻的城市,获取相邻城市的城市名,并创建一个新的子结点,将当前相邻的城市城市信息、代价估计值赋给子结点,子结点的父亲结点是当前正在拓展的结点,并且在当前正在拓展的结点中加入该子结点,将当前正在拓展的结点中孩子结点数加一。
    2.4. 判断该相邻城市是否已经被拓展过,如果该相邻城市没被拓展过,则将该相邻城市加入待拓展的城市栈中
  3. 判断已经不存在待拓展的城市:找不到可以到目标城市的路径,搜索失败。
代码程序如下:
void Depth_First_Search(string start,string goal) //深度优先搜索算法
{
    state start_state = graph[start];   //获取开始城市的城市信息
    Treenode *root = Create_node();     //创建搜索树一个根结点
    stack<Treenode *> Q;                //记录保存待拓展的城市集合
    root->theState = start_state;       //根结点的城市为开始城市
    Q.push(root);                       //将开始城市加入待拓展的城市集合(根结点)
    while(!Q.empty()){                  //还存在待拓展的城市
        Treenode * current = Q.top();   //取出当前需要拓展的城市
        Q.pop();
        state current_state = current->theState;    //获取当前城市的城市信息
        cout <<"State name : " <<current_state.name <<"\tValue : " <<current->value <<"\tDepth : " <<current->depth <<endl;
        explored[current_state.name] = 1;           //将当前城市设置为已经拓展过
        
        if(current_state.name == goal){             //判断当前城市是不是目标城市
                cout <<endl <<endl <<"Depth_First_Search Route : ";
                Route(current,start);               //输出搜索的路径
                return;                             //搜索结束,返回
        }
        for(int i=0;i<current_state.neighbor_num;i++){                  //拓展当前城市的所有相邻城市
            string nextstate_name = current_state.nextstate[i].first;   //获取相邻城市的城市名
            Treenode * current_child = Create_node();                   //创建一个新的子结点
            current_child->theState = graph[nextstate_name];            //子结点的城市信息
            current_child->value = current->value + current_state.nextstate[i].second;  //子结点的代价估计值
            current_child->father = current;                            //子结点的父亲结点是当前正在拓展的结点
            current->child[current->child_num] = current_child;         //在当前正在拓展的结点中加入该子结点
            current->child_num++;                   //当前正在拓展的结点中孩子结点数加一

            cout <<"\tChild State : " <<nextstate_name <<"\tValue : " <<current_child->value;
            if(explored[nextstate_name] == 0){      //判断该相邻城市是否已经被拓展过
                Q.push(current_child);              //没被拓展过,加入待拓展的城市集合(树结点)
            }
        }
        cout <<endl<<endl;
    }
    cout <<"Failure" <<endl;        //找不到可以到目标城市的路径,搜索失败
    return;
}
输出搜索路径的函数代码实现如下:
void Route(Treenode * c,string s)   //递归得到搜索路径
{
    if(c->theState.name == s){      //当前城市为目标城市
        //输出当前城市和代价估计值(启发式)
        cout <<c->theState.name <<" v : "<<c->value <<" -> ";
        return;
    }
    Route(c->father,s);     //当前城市还不是目标城市,继续递归
    //输出当前城市和代价估计值(启发式)
    cout <<c->theState.name <<" v : "<<c->value <<" -> ";
    return;
}
实验结果分析:

深度优先搜索算法解决罗马尼亚问题的搜索路径:
Depth_First_Search Route :
Arad v : 0 -> Timisoara v : 118 -> Lugoj v : 229 -> Mehadia v : 299 -> Dobreta v : 374 -> Craiova v : 494 -> RimnicuVilcea v : 640 -> Pitesti v : 737 -> Bucharest v : 838 ->
程序运行时间 Run Time :
185.000 ms

迭代加深的深度优先搜索算法:

算法原理说明:

迭代加深的深度优先搜索是一种常用策略,它经常和深度优先搜索结合使用来确定最好的深度界限。做法是不断地增大深度限制——首先为 0,接着为 1,然后是 2,以此类推直到找到目标。当深度界限达到 d,即最浅的目标结点所在深度时,就能找到目标结点。

迭代加深的深度优先搜索算法结合了深度优先搜索和宽度优先搜索的优点,它的空间需求是合适的:O(bd)。和宽度优先搜索一样,当分支因子有限时是该搜索算法是完备的,当路径代价是结点深度的非递减函数时该算法是最优的。

也许迭代加深的深度优先搜索看起来比较浪费,因为状态被多次重复生成。但事实上代价并不是多大。原因是在分支因子相同(或者近似)的搜索树中,绝大多数的结点都在底层,所以上层的结点重复生成多次影响不大。

时间复杂度:O(b^d )

如果你确实担忧状态的重复生成,可以混合使用两种搜索算法,先用宽度优先搜索直到有效内存耗尽,然后对边缘集中的所有结点应用迭代加深的深度优先搜索。一般来讲,当搜索空间较大并且不知道解所在的深度时,迭代加深的深度优先搜索是首选的无信息搜索算法

程序实现和说明:

实现 迭代加深的深度优先搜索算法 的关键数据结构是 ,具体的流程如下:

  1. 获取开始城市的城市信息,创建搜索树的根结点,将根结点的城市设置为开始城市,此时根结点的深度初始化为 0,将开始城市加入待拓展的城市集合中。
  2. 判断此时还存在待拓展的城市:
    2.1. 取出 当前待拓展的城市栈中栈顶 的需要拓展的城市,判断当前结点的深度是否超过限界,如果深度超过限界,跳过该结点,不进行讨论。如果深度没有超过限界,则获取当前需要拓展的城市城市信息,并将当前城市设置为已经被拓展过。
    2.2. 此时判断当前城市是不是目标城市,如果是,则输出搜索的路径,成功搜索到目标,返回搜索成功,搜索结束。若不是目标城市,则继续下一步。
    2.3. 拓展当前城市的所有相邻城市。对于每一个相邻的城市,获取相邻城市的城市名,并创建一个新的子结点,将当前相邻的城市城市信息、代价估计值赋给子结点,同时子结点的深度为父亲结点的深度加一。子结点的父亲结点是当前正在拓展的结点,并且在当前正在拓展的结点中加入该子结点,将当前正在拓展的结点中孩子结点数加一。
    2.4. 判断该相邻城市是否已经被拓展过,如果该相邻城市没被拓展过,则将该相邻城市加入待拓展的城市栈中
  3. 判断已经不存在待拓展的城市:找不到可以到目标城市的路径,返回搜索失败,需要继续迭代加深
代码程序如下:
int Iterative_Deepening_Depth_First_Search(string start,string goal,int D)  //迭代加深的深度优先搜索算法
{
    state start_state = graph[start];   //获取开始城市的城市信息
    Treenode *root = Create_node();     //创建搜索树一个根结点
    stack<Treenode *> Q;                //记录保存待拓展的城市集合
    root->theState = start_state;       //根结点的城市为开始城市
    Q.push(root);                       //将开始城市加入待拓展的城市集合(根结点)
    while(!Q.empty()){                  //还存在待拓展的城市
        Treenode * current = Q.top();   //取出当前需要拓展的城市
        Q.pop();
        if(current->depth > D)          //判断当前结点的深度是否超过限界
            continue;                   //超过限界,跳过该结点,不进行讨论
        state current_state = current->theState;    //获取当前城市的城市信息
        cout <<"State name : " <<current_state.name <<"\tValue : " <<current->value <<"\tDepth : " <<current->depth <<endl;
        explored[current_state.name] = 1;           //将当前城市设置为已经拓展过
        
        if(current_state.name == goal){             //判断当前城市是不是目标城市
            cout <<endl <<endl <<"Iterative_Deepening_Depth_First_Search Route : ";
            Route(current,start);                   //输出搜索的路径
            return 1;                               //搜索结束,返回 1 表示搜索成功
        }
        for(int i=0;i<current_state.neighbor_num;i++){                  //拓展当前城市的所有相邻城市
            string nextstate_name = current_state.nextstate[i].first;   //获取相邻城市的城市名
            Treenode * current_child = Create_node();                   //创建一个新的子结点
            current_child->theState = graph[nextstate_name];            //子结点的城市信息
            current_child->value = current->value + current_state.nextstate[i].second;  //子结点的代价估计值
            current_child->depth = current->depth + 1;                  //子结点的深度为父亲结点的深度加一
            current_child->father = current;                            //子结点的父亲结点是当前正在拓展的结点
            current->child[current->child_num] = current_child;         //在当前正在拓展的结点中加入该子结点
            current->child_num++;                   //当前正在拓展的结点中孩子结点数加一

            cout <<"\tChild State : " <<nextstate_name <<"\tValue : " <<current_child->value;
            if(explored[nextstate_name] == 0){      //判断该相邻城市是否已经被拓展过
                Q.push(current_child);              //没被拓展过,加入待拓展的城市集合(树结点)
            }
        }
        cout <<endl<<endl;
    }
    cout <<"Failure" <<endl;    //找不到可以到目标城市的路径,搜索失败
    return 0;                   //返回 0 表示搜索失败,需要继续加深
}
输出搜索路径的函数代码实现如下:
void Route(Treenode * c,string s)   //递归得到搜索路径
{
    if(c->theState.name == s){      //当前城市为目标城市
        //输出当前城市和代价估计值(启发式)
        cout <<c->theState.name <<" v : "<<c->value <<" -> ";
        return;
    }
    Route(c->father,s);     //当前城市还不是目标城市,继续递归
    //输出当前城市和代价估计值(启发式)
    cout <<c->theState.name <<" v : "<<c->value <<" -> ";
    return;
}
实验结果分析:

深度界限为 0 : 结果为 Failure
深度界限为 1 : 结果为 Failure
深度界限为 2 : 结果为 Failure
深度界限为 3 :
迭代加深的深度优先搜索算法解决罗马尼亚问题的搜索路径:
Iterative_Deepening_Depth_First_Search Route :
Arad v : 0 -> Sibiu v : 140 -> Fagaras v : 239 -> Bucharest v : 450 ->

程序运行时间 Run Time :
501.000 ms

有信息(启发式)的搜索策略:

贪婪最佳优先搜索算法:

算法原理说明:

有信息搜索策略使用问题本身的定义之外的特定知识,比无信息的搜索策略更有效的进行问题求解。一般考虑的算法称为最佳优先搜索。最佳优先搜索中,结点是基于评价函数值被选择扩展的。评价函数被看作是代价评估,因此评估值最低的结点被选择首先进行扩展。最佳优先图搜索的实现与一致代价搜索类似,不过最佳优先是根据评价函数值对优先级队列进行排队。

大多数的最佳优先搜索算法的评价函数是由启发函数构成:
启发函数 = 结点到目标结点的最小代价路径的代价评估值

贪婪最佳优先搜索试图扩展离目标最近的结点,理由是这样可能可以很快找到解。因此,它只用启发式信息。对应到罗马尼亚问题中,使用直线距离启发式:
在这里插入图片描述
时间复杂度:O(b^m )
空间复杂度:O(b^m )

程序实现和说明:

实现 贪婪最佳优先搜索算法 的关键数据结构是 优先队列,具体的流程如下:

  1. 获取开始城市的城市信息,创建搜索树的根结点,将根结点的城市设置为开始城市,将根结点的启发函数估计值设置为开始城市的启发函数估计值,将开始城市加入待拓展的城市集合中。
  2. 判断此时还存在待拓展的城市:
    2.1. 取出 当前待拓展的城市优先队列中队首 的需要拓展的城市,获取当前需要拓展的城市城市信息,并将当前城市设置为已经被拓展过。
    2.2. 此时判断当前城市是不是目标城市,如果是,则输出搜索的路径,成功搜索到目标,搜索结束。若不是目标城市,则继续下一步。
    2.3. 拓展当前城市的所有相邻城市。对于每一个相邻的城市,获取相邻城市的城市名,并创建一个新的子结点,将当前相邻的城市城市信息、启发函数估计值赋给子结点,子结点的父亲结点是当前正在拓展的结点,并且在当前正在拓展的结点中加入该子结点,将当前正在拓展的结点中孩子结点数加一。
    2.4. 判断该相邻城市是否已经被拓展过,如果该相邻城市没被拓展过,则将该相邻城市加入待拓展的城市优先队列中。
  3. 判断已经不存在待拓展的城市:找不到可以到目标城市的路径,搜索失败。
代码程序如下:

程序中使用自己写的 sort() 排序实现优先队列的功能。

void Greedy_Best_First_Search(string start,string goal) //贪婪最佳优先搜索算法
{
    state start_state = graph[start];   //获取开始城市的城市信息
    Treenode *root = Create_node();     //创建搜索树一个根结点
    queue<Treenode *> Q;                //记录保存待拓展的城市集合
    root->theState = start_state;       //根结点的城市为开始城市
    root->value = start_state.h;        //根结点的启发函数估计值
    Q.push(root);                       //将开始城市加入待拓展的城市集合(根结点)
    while(!Q.empty()){                  //还存在待拓展的城市
        Treenode * current = Q.front();             //取出当前需要拓展的城市
        Q.pop();
        state current_state = current->theState;    //获取当前城市的城市信息
        cout <<"State name : " <<current_state.name <<"\tValue : " <<current->value <<endl;
        explored[current_state.name] = 1;           //将当前城市设置为已经拓展过
        
        if(current_state.name == goal){             //判断当前城市是不是目标城市
                cout <<endl <<endl <<"Greedy_Best_First_Search Route : ";
                Route(current,start);               //输出搜索的路径
                return;                             //搜索结束,返回
        }
        for(int i=0;i<current_state.neighbor_num;i++){                  //拓展当前城市的所有相邻城市
            string nextstate_name = current_state.nextstate[i].first;   //获取相邻城市的城市名
            Treenode * current_child = Create_node();                   //创建一个新的子结点
            current_child->theState = graph[nextstate_name];            //子结点的城市信息
            current_child->value = graph[nextstate_name].h;             //子结点的启发函数估计值
            current_child->father = current;                            //子结点的父亲结点是当前正在拓展的结点
            current->child[current->child_num] = current_child;         //在当前正在拓展的结点中加入该子结点
            current->child_num++;                   //当前正在拓展的结点中孩子结点数加一

            cout <<"\tChild State : " <<nextstate_name <<"\tValue : " <<current_child->value;
            if(explored[nextstate_name] == 0){      //判断该相邻城市是否已经被拓展过
                Q.push(current_child);              //没被拓展过,加入待拓展的城市集合(树结点)
            }
        }
        Q = sort(Q);            //进行优先队列的排序,实现优先队列的功能
        cout <<endl<<endl;
    }
    cout <<"Failure" <<endl;    //找不到可以到目标城市的路径,搜索失败
    return;
}
输出搜索路径的函数代码实现如下:
void Route(Treenode * c,string s)   //递归得到搜索路径
{
    if(c->theState.name == s){      //当前城市为目标城市
        //输出当前城市和代价估计值(启发式)
        cout <<c->theState.name <<" v : "<<c->value <<" -> ";
        return;
    }
    Route(c->father,s);     //当前城市还不是目标城市,继续递归
    //输出当前城市和代价估计值(启发式)
    cout <<c->theState.name <<" v : "<<c->value <<" -> ";
    return;
}
实验结果分析:

贪婪最佳优先搜索算法解决罗马尼亚问题的搜索路径:
Greedy_Best_First_Search Route :
Arad v : 366 -> Sibiu v : 253 -> Fagaras v : 176 -> Bucharest v : 0 ->

程序运行时间 Run Time :
101.000 ms

A*搜索算法:

算法原理说明:

最佳优先搜索的最广为人知的形式称为 A* 搜索。它对结点的评估结合了到达此结点已经花费的代价,和从该结点到目标结点所花代价。由于结合了从开始结点到结点 n 的路径代价和从结点 n 到目标结点的最小代价路径的评估值,因此评估函数值等于经过结点 n 的最小代价解的估计代价。

这样,如果我们想要找到最小代价的解,首先扩展评估函数值最小的结点是合理的。可以发现这个策略不仅仅合理:假设启发式函数满足特定的条件,A* 搜索既是完备的也是最优的。

其中,A* 搜索算法的最优性取决于:如果启发式函数是可采纳的,那么 A* 的树搜索版本是最优的,如果启发式函数是一致的,那么图搜索的 A* 算法是最优的。

程序实现和说明:

实现 A*搜索算法 的关键数据结构是 优先队列,具体的流程如下:

  1. 获取开始城市的城市信息,创建搜索树的根结点,将根结点的城市设置为开始城市,将根结点的启发函数估计值设置为开始城市的启发函数估计值,将开始城市加入待拓展的城市集合中。
  2. 判断此时还存在待拓展的城市:
    2.1. 取出 当前待拓展的城市优先队列中队首 的需要拓展的城市,获取当前需要拓展的城市城市信息,并将当前城市设置为已经被拓展过。
    2.2. 此时判断当前城市是不是目标城市,如果是,则输出搜索的路径,成功搜索到目标,搜索结束。若不是目标城市,则继续下一步。
    2.3. 拓展当前城市的所有相邻城市。对于每一个相邻的城市,获取相邻城市的城市名,并创建一个新的子结点,将当前相邻的城市城市信息、启发函数估计值赋给子结点,启发函数估计值为代价值加上距离目标城市的估计值,子结点的父亲结点是当前正在拓展的结点,并且在当前正在拓展的结点中加入该子结点,将当前正在拓展的结点中孩子结点数加一。
    2.4. 判断该相邻城市是否已经被拓展过,如果该相邻城市没被拓展过,则将该相邻城市加入待拓展的城市优先队列中
  3. 判断已经不存在待拓展的城市:找不到可以到目标城市的路径,搜索失败。
代码程序如下:

程序中使用自己写的 sort() 排序实现优先队列的功能。

void A_Star_Search(string start,string goal)
{
    state start_state = graph[start];   //获取开始城市的城市信息
    Treenode *root = Create_node();     //创建搜索树一个根结点
    queue<Treenode *> Q;                //记录保存待拓展的城市集合
    root->theState = start_state;       //根结点的城市为开始城市
    root->value = start_state.h;        //根结点的启发函数估计值
    Q.push(root);                       //将开始城市加入待拓展的城市集合(根结点)
    while(!Q.empty()){                  //还存在待拓展的城市
        Treenode * current = Q.front();             //取出当前需要拓展的城市
        Q.pop();
        state current_state = current->theState;    //获取当前城市的城市信息
        cout <<"State name : " <<current_state.name <<"\tValue : " <<current->value <<endl;
        explored[current_state.name] = 1;           //将当前城市设置为已经拓展过
        if(current_state.name == goal){             //判断当前城市是不是目标城市
                cout <<endl <<endl <<"A_Star_Search Route : ";
                Route(current,start);               //输出搜索的路径
                return;                             //搜索结束,返回
        }
        
        for(int i=0;i<current_state.neighbor_num;i++){                  //拓展当前城市的所有相邻城市
            string nextstate_name = current_state.nextstate[i].first;   //获取相邻城市的城市名
            Treenode * current_child = Create_node();                   //创建一个新的子结点
            current_child->theState = graph[nextstate_name];            //子结点的城市信息
            //子结点的启发函数估计值
            current_child->value = current->value - current_state.h + current_state.nextstate[i].second + graph[nextstate_name].h;
            current_child->father = current;                            //子结点的父亲结点是当前正在拓展的结点
            current->child[current->child_num] = current_child;         //在当前正在拓展的结点中加入该子结点
            current->child_num++;                   //当前正在拓展的结点中孩子结点数加一

            cout <<"\tChild State : " <<nextstate_name <<"\tValue : " <<current_child->value;
            if(explored[nextstate_name] == 0){      //判断该相邻城市是否已经被拓展过
                Q.push(current_child);              //没被拓展过,加入待拓展的城市集合(树结点)
            }
        }
        Q = sort(Q);            //进行优先队列的排序,实现优先队列的功能
        cout <<endl<<endl;
    }
    cout <<"Failure" <<endl;    //找不到可以到目标城市的路径,搜索失败
    return;
}
输出搜索路径的函数代码实现如下:
void Route(Treenode * c,string s)   //递归得到搜索路径
{
    if(c->theState.name == s){      //当前城市为目标城市
        //输出当前城市和代价估计值(启发式)
        cout <<c->theState.name <<" v : "<<c->value <<" -> ";
        return;
    }
    Route(c->father,s);     //当前城市还不是目标城市,继续递归
    //输出当前城市和代价估计值(启发式)
    cout <<c->theState.name <<" v : "<<c->value <<" -> ";
    return;
}
实验结果分析:

A*搜索算法解决罗马尼亚问题的搜索路径:
A_Star_Search Route :
Arad v : 366 -> Sibiu v : 393 -> RimnicuVilcea v : 413 -> Pitesti v : 417 -> Bucharest v : 418 ->

程序运行时间 Run Time :
103.000 ms

实验思考题解答:

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

在这里插入图片描述

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

根据我的程序跑出来的结果,在时间上明显是宽度优先搜索最优,宽度优先搜索总是有到每一个边缘结点的最浅路径。因为找到最浅的目标结点,所以最快。

一致代价搜索扩展的是当前路径代价最小的结点,对于一般性的步骤代价而言算法是最优的。

但是宽度优先搜索的实际答案并不是最优的,因为它没有考虑路径代价。并且此次的罗马尼亚问题规模太小,针对大规模的问题搜索时,可以混合使用两种搜索算法,先用宽度优先搜索直到有效内存耗尽,然后对边缘集中的所有结点应用迭代加深的深度优先搜索。一般来讲,当搜索空间较大并且不知道解所在的深度时,迭代加深的深度优先搜索是首选的无信息搜索算法。

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

通过我的程序跑出来的结果无法准确地判断那种算法最优,因为问题的规模太小了。

考虑到实际问题的搜索求解时,A* 搜索算法是最优的。如果我们想要找到最小代价的解,首先扩展评估函数值最小的结点是合理的。可以发现这个策略不仅仅合理:假设启发式函数满足特定的条件,A* 搜索既是完备的也是最优的。

其中,A* 搜索算法的最优性取决于:如果启发式函数是可采纳的,那么 A* 的树搜索版本是最优的,如果启发式函数是一致的,那么图搜索的 A* 算法是最优的。

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

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

完整代码:

Search.cpp

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <ctime>
using namespace std;

typedef pair<string,int> neighbor;  //当前城市的相邻城市的城市名和对应的路径代价
struct state            //罗马尼亚问题中的每一个城市的城市信息
{
    string name;        //该城市的城市名
    int neighbor_num;   //与该城市相邻的有几个城市
    map<int,neighbor> nextstate;    //记录每个相邻的城市和对应的路径代价
    int h;              //该城市到目标城市的最小代价路径的估计值
};

struct Treenode         //搜索树中的树结点定义
{
    int child_num;      //该树结点有几个孩子结点
    int value;          //该树结点的代价估计值(启发式)
    int depth;          //该树结点的深度
    state theState;     //该树结点对应在哪个城市
    Treenode * father;  //保存该树结点的父亲结点(指针)
    map<int,Treenode *> child;  //保存该树结点的所有孩子结点(指针)
};

int state_num = 0;          //罗马尼亚问题中的每一个城市
map<string,state> graph;    //邻接表保存罗马尼亚问题的图:<城市名,城市信息>
map<string,int> explored;   //罗马尼亚问题中的城市是否被拓展过

Treenode * Create_node()    //创建一个树结点,返回该树结点的指针
{
    Treenode * node = new Treenode; //申请一个新的树结点
    node->child_num = 0;    //初始化树结点的孩子结点数目为 0
    node->value = 0;        //初始化树结点的代价估计值(启发式)为 0
    node->depth = 0;        //初始化树结点的深度为 0
    node->father = NULL;    //初始化树结点的父亲结点(指针)为空
    node->child.clear();    //初始化树结点不存在孩子结点(指针)
    return node;            //返回该树结点的指针
}

void File_Input(string s)
{
    ifstream f(s);      //导入描述罗马尼亚问题详细信息的文件
    f >>state_num;      //输入罗马尼亚问题的总城市数目
    for(int i=0;i<state_num;i++){   //初始化每一个城市的城市信息
        state t;
        f >> t.name;            //当前城市的城市名
        f >> t.h;               //当前城市到目标城市的最小代价路径的估计值
        f >> t.neighbor_num;    //当前城市有几个相邻的城市
        for(int j=0;j<t.neighbor_num;j++){  //保存每一个相邻的城市
            f >>t.nextstate[j].first;       //保存相邻城市的城市名
            f >>t.nextstate[j].second;      //保存到相邻城市的路径代价
        }
        graph[t.name] = t;      //在罗马尼亚问题的图中添加当前城市
        explored[t.name] = 0;   //初始化当前城市未被拓展过
    }
}

void Show_Graph()
{
    cout <<" State number : " <<state_num <<endl;
    map<string,state>::iterator mit;
    for(mit=graph.begin();mit!=graph.end();mit++){
        cout <<"State : " <<" name : " <<mit->second.name <<endl;
        cout <<"        " <<" h : " <<mit->second.h <<endl;
        for(int j=0; j<mit->second.neighbor_num; j++){
            cout <<"        " <<" naighbor " <<j <<" : name :" <<mit->second.nextstate[j].first
                    <<"\tg : " <<mit->second.nextstate[j].second <<endl;
        } 
    }
}

queue<Treenode *> sort(queue<Treenode *> q)
{
    queue<Treenode *> s;
    map<float,Treenode *> m;
    int i=0;
    while(!q.empty()){
        i++;
        Treenode * t = q.front();
        q.pop();
        if(m[t->value] == NULL)
            m[t->value] = t;
        else{
            m[t->value + i * 0.01] = t;
        }
    }
    map<float,Treenode *>::iterator mit;
    for(mit=m.begin();mit!=m.end();mit++)
        s.push(mit->second);
    return s;
}

void Route(Treenode * c,string s)   //递归得到搜索路径
{
    if(c->theState.name == s){      //当前城市为目标城市
        //输出当前城市和代价估计值(启发式)
        cout <<c->theState.name <<" v : "<<c->value <<" -> ";
        return;
    }
    Route(c->father,s);     //当前城市还不是目标城市,继续递归
    //输出当前城市和代价估计值(启发式)
    cout <<c->theState.name <<" v : "<<c->value <<" -> ";
    return;
}

void Breadth_First_Search(string start,string goal) //宽度优先搜索算法
{
    state start_state = graph[start];   //获取开始城市的城市信息
    Treenode *root = Create_node();     //创建搜索树一个根结点
    queue<Treenode *> Q;                //记录保存待拓展的城市集合
    root->theState = start_state;       //根结点的城市为开始城市
    Q.push(root);                       //将开始城市加入待拓展的城市集合(根结点)
    while(!Q.empty()){                  //还存在待拓展的城市
        Treenode * current = Q.front();             //取出当前需要拓展的城市
        Q.pop();
        state current_state = current->theState;    //获取当前城市的城市信息
        cout <<"State name : " <<current_state.name <<"\tValue : " <<current->value <<endl;
        explored[current_state.name] = 1;           //将当前城市设置为已经拓展过
        
        if(current_state.name == goal){             //判断当前城市是不是目标城市
                cout <<endl <<endl <<"Breadth_First_Search Route : ";
                Route(current,start);               //输出搜索的路径
                return;                             //搜索结束,返回
        }
        for(int i=0;i<current_state.neighbor_num;i++){                  //拓展当前城市的所有相邻城市
            string nextstate_name = current_state.nextstate[i].first;   //获取相邻城市的城市名
            Treenode * current_child = Create_node();                   //创建一个新的子结点
            current_child->theState = graph[nextstate_name];            //子结点的城市信息
            current_child->value = current->value + current_state.nextstate[i].second;  //子结点的代价估计值
            current_child->father = current;                            //子结点的父亲结点是当前正在拓展的结点
            current->child[current->child_num] = current_child;         //在当前正在拓展的结点中加入该子结点
            current->child_num++;                   //当前正在拓展的结点中孩子结点数加一

            cout <<"\tChild State : " <<nextstate_name <<"\tValue : " <<current_child->value;
            if(explored[nextstate_name] == 0){      //判断该相邻城市是否已经被拓展过
                Q.push(current_child);              //没被拓展过,加入待拓展的城市集合(树结点)
            }
        }
        cout <<endl<<endl;
    }
    cout <<"Failure" <<endl;        //找不到可以到目标城市的路径,搜索失败
    return;
}

void Uniform_Cost_Search(string start,string goal)  //一致代价搜索算法
{
    state start_state = graph[start];   //获取开始城市的城市信息
    Treenode *root = Create_node();     //创建搜索树一个根结点
    queue<Treenode *> Q;                //记录保存待拓展的城市集合
    root->theState = start_state;       //根结点的城市为开始城市
    Q.push(root);                       //将开始城市加入待拓展的城市集合(根结点)
    while(!Q.empty()){                  //还存在待拓展的城市
        Treenode * current = Q.front();             //取出当前需要拓展的城市
        Q.pop();
        state current_state = current->theState;    //获取当前城市的城市信息
        cout <<"State name : " <<current_state.name <<"\tValue : " <<current->value <<endl;
        explored[current_state.name] = 1;           //将当前城市设置为已经拓展过
        
        if(current_state.name == goal){             //判断当前城市是不是目标城市
                cout <<endl <<endl <<"Uniform_Cost_Search Route : ";
                Route(current,start);               //输出搜索的路径
                return;                             //搜索结束,返回
        }
        for(int i=0;i<current_state.neighbor_num;i++){                  //拓展当前城市的所有相邻城市
            string nextstate_name = current_state.nextstate[i].first;   //获取相邻城市的城市名
            Treenode * current_child = Create_node();                   //创建一个新的子结点
            current_child->theState = graph[nextstate_name];            //子结点的城市信息
            current_child->value = current->value + current_state.nextstate[i].second;  //子结点的代价估计值
            current_child->father = current;                            //子结点的父亲结点是当前正在拓展的结点
            current->child[current->child_num] = current_child;         //在当前正在拓展的结点中加入该子结点
            current->child_num++;                   //当前正在拓展的结点中孩子结点数加一

            cout <<"\tChild State : " <<nextstate_name <<"\tValue : " <<current_child->value;
            if(explored[nextstate_name] == 0){      //判断该相邻城市是否已经被拓展过
                Q.push(current_child);              //没被拓展过,加入待拓展的城市集合(树结点)
            }
        }
        Q = sort(Q);            //进行优先队列的排序,实现优先队列的功能
        cout <<endl<<endl;
    }
    cout <<"Failure" <<endl;    //找不到可以到目标城市的路径,搜索失败
    return;
}

void Depth_First_Search(string start,string goal) //深度优先搜索算法
{
    state start_state = graph[start];   //获取开始城市的城市信息
    Treenode *root = Create_node();     //创建搜索树一个根结点
    stack<Treenode *> Q;                //记录保存待拓展的城市集合
    root->theState = start_state;       //根结点的城市为开始城市
    Q.push(root);                       //将开始城市加入待拓展的城市集合(根结点)
    while(!Q.empty()){                  //还存在待拓展的城市
        Treenode * current = Q.top();   //取出当前需要拓展的城市
        Q.pop();
        state current_state = current->theState;    //获取当前城市的城市信息
        cout <<"State name : " <<current_state.name <<"\tValue : " <<current->value <<"\tDepth : " <<current->depth <<endl;
        explored[current_state.name] = 1;           //将当前城市设置为已经拓展过
        
        if(current_state.name == goal){             //判断当前城市是不是目标城市
                cout <<endl <<endl <<"Depth_First_Search Route : ";
                Route(current,start);               //输出搜索的路径
                return;                             //搜索结束,返回
        }
        for(int i=0;i<current_state.neighbor_num;i++){                  //拓展当前城市的所有相邻城市
            string nextstate_name = current_state.nextstate[i].first;   //获取相邻城市的城市名
            Treenode * current_child = Create_node();                   //创建一个新的子结点
            current_child->theState = graph[nextstate_name];            //子结点的城市信息
            current_child->value = current->value + current_state.nextstate[i].second;  //子结点的代价估计值
            current_child->father = current;                            //子结点的父亲结点是当前正在拓展的结点
            current->child[current->child_num] = current_child;         //在当前正在拓展的结点中加入该子结点
            current->child_num++;                   //当前正在拓展的结点中孩子结点数加一

            cout <<"\tChild State : " <<nextstate_name <<"\tValue : " <<current_child->value;
            if(explored[nextstate_name] == 0){      //判断该相邻城市是否已经被拓展过
                Q.push(current_child);              //没被拓展过,加入待拓展的城市集合(树结点)
            }
        }
        cout <<endl<<endl;
    }
    cout <<"Failure" <<endl;        //找不到可以到目标城市的路径,搜索失败
    return;
}

int Iterative_Deepening_Depth_First_Search(string start,string goal,int D)  //迭代加深的深度优先搜索算法
{
    state start_state = graph[start];   //获取开始城市的城市信息
    Treenode *root = Create_node();     //创建搜索树一个根结点
    stack<Treenode *> Q;                //记录保存待拓展的城市集合
    root->theState = start_state;       //根结点的城市为开始城市
    Q.push(root);                       //将开始城市加入待拓展的城市集合(根结点)
    while(!Q.empty()){                  //还存在待拓展的城市
        Treenode * current = Q.top();   //取出当前需要拓展的城市
        Q.pop();
        if(current->depth > D)          //判断当前结点的深度是否超过限界
            continue;                   //超过限界,跳过该结点,不进行讨论
        state current_state = current->theState;    //获取当前城市的城市信息
        cout <<"State name : " <<current_state.name <<"\tValue : " <<current->value <<"\tDepth : " <<current->depth <<endl;
        explored[current_state.name] = 1;           //将当前城市设置为已经拓展过
        
        if(current_state.name == goal){             //判断当前城市是不是目标城市
            cout <<endl <<endl <<"Iterative_Deepening_Depth_First_Search Route : ";
            Route(current,start);                   //输出搜索的路径
            return 1;                               //搜索结束,返回 1 表示搜索成功
        }
        for(int i=0;i<current_state.neighbor_num;i++){                  //拓展当前城市的所有相邻城市
            string nextstate_name = current_state.nextstate[i].first;   //获取相邻城市的城市名
            Treenode * current_child = Create_node();                   //创建一个新的子结点
            current_child->theState = graph[nextstate_name];            //子结点的城市信息
            current_child->value = current->value + current_state.nextstate[i].second;  //子结点的代价估计值
            current_child->depth = current->depth + 1;                  //子结点的深度为父亲结点的深度加一
            current_child->father = current;                            //子结点的父亲结点是当前正在拓展的结点
            current->child[current->child_num] = current_child;         //在当前正在拓展的结点中加入该子结点
            current->child_num++;                   //当前正在拓展的结点中孩子结点数加一

            cout <<"\tChild State : " <<nextstate_name <<"\tValue : " <<current_child->value;
            if(explored[nextstate_name] == 0){      //判断该相邻城市是否已经被拓展过
                Q.push(current_child);              //没被拓展过,加入待拓展的城市集合(树结点)
            }
        }
        cout <<endl<<endl;
    }
    cout <<"Failure" <<endl;    //找不到可以到目标城市的路径,搜索失败
    return 0;                   //返回 0 表示搜索失败,需要继续加深
}

void Greedy_Best_First_Search(string start,string goal) //贪婪最佳优先搜索算法
{
    state start_state = graph[start];   //获取开始城市的城市信息
    Treenode *root = Create_node();     //创建搜索树一个根结点
    queue<Treenode *> Q;                //记录保存待拓展的城市集合
    root->theState = start_state;       //根结点的城市为开始城市
    root->value = start_state.h;        //根结点的启发函数估计值
    Q.push(root);                       //将开始城市加入待拓展的城市集合(根结点)
    while(!Q.empty()){                  //还存在待拓展的城市
        Treenode * current = Q.front();             //取出当前需要拓展的城市
        Q.pop();
        state current_state = current->theState;    //获取当前城市的城市信息
        cout <<"State name : " <<current_state.name <<"\tValue : " <<current->value <<endl;
        explored[current_state.name] = 1;           //将当前城市设置为已经拓展过
        
        if(current_state.name == goal){             //判断当前城市是不是目标城市
                cout <<endl <<endl <<"Greedy_Best_First_Search Route : ";
                Route(current,start);               //输出搜索的路径
                return;                             //搜索结束,返回
        }
        for(int i=0;i<current_state.neighbor_num;i++){                  //拓展当前城市的所有相邻城市
            string nextstate_name = current_state.nextstate[i].first;   //获取相邻城市的城市名
            Treenode * current_child = Create_node();                   //创建一个新的子结点
            current_child->theState = graph[nextstate_name];            //子结点的城市信息
            current_child->value = graph[nextstate_name].h;             //子结点的启发函数估计值
            current_child->father = current;                            //子结点的父亲结点是当前正在拓展的结点
            current->child[current->child_num] = current_child;         //在当前正在拓展的结点中加入该子结点
            current->child_num++;                   //当前正在拓展的结点中孩子结点数加一

            cout <<"\tChild State : " <<nextstate_name <<"\tValue : " <<current_child->value;
            if(explored[nextstate_name] == 0){      //判断该相邻城市是否已经被拓展过
                Q.push(current_child);              //没被拓展过,加入待拓展的城市集合(树结点)
            }
        }
        Q = sort(Q);            //进行优先队列的排序,实现优先队列的功能
        cout <<endl<<endl;
    }
    cout <<"Failure" <<endl;    //找不到可以到目标城市的路径,搜索失败
    return;
}

void A_Star_Search(string start,string goal)
{
    state start_state = graph[start];   //获取开始城市的城市信息
    Treenode *root = Create_node();     //创建搜索树一个根结点
    queue<Treenode *> Q;                //记录保存待拓展的城市集合
    root->theState = start_state;       //根结点的城市为开始城市
    root->value = start_state.h;        //根结点的启发函数估计值
    Q.push(root);                       //将开始城市加入待拓展的城市集合(根结点)
    while(!Q.empty()){                  //还存在待拓展的城市
        Treenode * current = Q.front();             //取出当前需要拓展的城市
        Q.pop();
        state current_state = current->theState;    //获取当前城市的城市信息
        cout <<"State name : " <<current_state.name <<"\tValue : " <<current->value <<endl;
        explored[current_state.name] = 1;           //将当前城市设置为已经拓展过
        if(current_state.name == goal){             //判断当前城市是不是目标城市
                cout <<endl <<endl <<"A_Star_Search Route : ";
                Route(current,start);               //输出搜索的路径
                return;                             //搜索结束,返回
        }
        
        for(int i=0;i<current_state.neighbor_num;i++){                  //拓展当前城市的所有相邻城市
            string nextstate_name = current_state.nextstate[i].first;   //获取相邻城市的城市名
            Treenode * current_child = Create_node();                   //创建一个新的子结点
            current_child->theState = graph[nextstate_name];            //子结点的城市信息
            //子结点的启发函数估计值
            current_child->value = current->value - current_state.h + current_state.nextstate[i].second + graph[nextstate_name].h;
            current_child->father = current;                            //子结点的父亲结点是当前正在拓展的结点
            current->child[current->child_num] = current_child;         //在当前正在拓展的结点中加入该子结点
            current->child_num++;                   //当前正在拓展的结点中孩子结点数加一

            cout <<"\tChild State : " <<nextstate_name <<"\tValue : " <<current_child->value;
            if(explored[nextstate_name] == 0){      //判断该相邻城市是否已经被拓展过
                Q.push(current_child);              //没被拓展过,加入待拓展的城市集合(树结点)
            }
        }
        Q = sort(Q);            //进行优先队列的排序,实现优先队列的功能
        cout <<endl<<endl;
    }
    cout <<"Failure" <<endl;    //找不到可以到目标城市的路径,搜索失败
    return;
}

int main()
{
    // 记录程序运行时间 
	clock_t start,end,over;
	start=clock();
	end=clock();
	over=end-start;

    ifstream file("Select_Search.txt");
    string select_Search;
    while(cout <<endl <<endl <<"Choose How to Search : " && file >>select_Search){
        File_Input("Romania.txt");
        // Show_Graph();

        start=clock();
        if(select_Search == "BFS"){
            cout <<endl <<"Breadth_First_Search" <<endl;
            Breadth_First_Search("Arad","Bucharest");
        }
        else if(select_Search == "UCS"){
            cout <<endl <<"Uniform_Cost_Search" <<endl;
            Uniform_Cost_Search("Arad","Bucharest");
        }
        else if(select_Search == "DFS"){
            cout <<endl <<"Depth_First_Search" <<endl;
            Depth_First_Search("Arad","Bucharest");
        }
        else if(select_Search == "IDDFS"){
            for(int i=0; ;i++){
                File_Input("Romania.txt");
                cout <<endl <<"Iterative_Deepening_Depth_First_Search : Limit : " <<i <<endl;
                int ans = Iterative_Deepening_Depth_First_Search("Arad","Bucharest",i);
                if(ans == 1)
                    break;
            }
        }
        else if(select_Search == "GBFS"){
            cout <<endl <<"Greedy_Best_First_Search" <<endl;
            Greedy_Best_First_Search("Arad","Bucharest");            
        }
        else if(select_Search == "ASS"){
            cout <<endl <<"A_Star_Search" <<endl;
            A_Star_Search("Arad","Bucharest");            
        }
        else{
            break;
        }
        end=clock(); 
 	    printf("\nRun Time : %6.3f ms\n",(double)(end-start-over)/CLOCKS_PER_SEC*1000);
    }
}

Romania.txt

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

Select_Search.txt

BFS
UCS
DFS
IDDFS
GBFS
ASS
END

记得点赞好评收藏分享嗷!

发布了36 篇原创文章 · 获赞 5 · 访问量 5230

猜你喜欢

转载自blog.csdn.net/qq_43413123/article/details/104266833