一、运行环境
请安装vs与配置opengl环境,注意是glut,这上门课应该会同时上着计算机图形学,一般不变动的话,老师已经让配置了glut的环境。如果没有配置此环境,请百度或古狗。
二、代码
由于此前电脑坏了被迫刷机,又没有提前保存程序,目前剩下的只有代码,请自行创建项目,创建对应的cpp插入代码运行。
主函数与操作模块:Search.cpp
// SearchAI.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include"pch.h"
#include<windows.h>
#include"graph.h"
#include<stdlib.h>
#include<gl/glut.h>
Graph g;//定义
int k = 0;//控制选择三种算法
//初始化
void Initial()
{
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);//设置窗口背景颜色
glMatrixMode(GL_PROJECTION);
gluOrtho2D(0.0, 800, 0.0, 600);
}
//
void mouseClick(int button,int state,int x,int y)
{
if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
k = 1;//a*
}
if (button == GLUT_MIDDLE_BUTTON && state == GLUT_DOWN)
{
k = 2;//代价一致的宽度优先
}
if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
{
k = 3;//贪婪算法
}
}
void Display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(0.0f, 0.0f, 0.0f);
g.DrawGraph();//绘制图像
glColor3f(1.0f, 0.0f, 0.0f);
if (k == 1)
{
g.A_Star(0, 1);
}
if (k == 2)
{
g.Uniform_Cost_Search(0,1);
}
if (k == 3)
{
g.greedy(0, 1);
}
glFlush();
}
int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(800, 600);
glutInitWindowPosition(600, 100);
//创建窗口,参数为窗口标题
glutCreateWindow("鼠标左键a*--中键一致代价算法--右键贪婪算法");
glutMouseFunc(mouseClick);
glutDisplayFunc(Display);
Initial();
glutMainLoop();
return 0;
}
以上便是主函数与操作运行模块,可以自行修改。
在这里我想说的是,当时由于时间匆忙,几乎是一个下午赶出来的代码,因此有如下问题:
1.注释少,可读性差.
2.为了让程序慢慢运行,实现直观的可视化,使用了睡眠函数,说实话有点哈皮.
3.代码写的稀烂.
不过好在非常的直观,可直接窥视了算法的运行流程。
在这里我要说一点,qt实际上是最tm方便好用的,当时只是因为我没用过qt和锻炼一下opengl能力才用的glut。
最后的重要代码如下:
graph.cpp
#pragma once
#include<windows.h>
#include<gl/glut.h>
#include<iostream>
#include<fstream>
#include<string>
#include<queue>
#include<vector>
#include <stdlib.h>
#include<ctime>
using namespace std;
//评估函数
int h[20] = { 366,0,160,242,161,176,77,151,226,244,241,234,380,98,193,253,329,80,199,374 };
#define _CRT_NONSTDC_NO_DEPRECATE
#define _CRT_SECURE_NO_WARNINGS
//字符 ascll码
#define MAX_CHAR 128
//打印字符
void drawString(string str) {
static int isFirstCall = 1;
static GLuint lists;
if (isFirstCall) { // 如果是第一次调用,执行初始化
// 为每一个ASCII字符产生一个显示列表
isFirstCall = 0;
// 申请MAX_CHAR个连续的显示列表编号
lists = glGenLists(MAX_CHAR);
// 把每个字符的绘制命令都装到对应的显示列表中
wglUseFontBitmaps(wglGetCurrentDC(), 0, MAX_CHAR, lists);
}
// 调用每个字符对应的显示列表,绘制每个字符
int size = str.size();
for (int i = 0; i < size; i++)
{
glCallList(lists + str[i]);
}
}
//int转string类型
string IntToStr(int nVal)
{
char intNumber[10];
_itoa_s(nVal, intNumber, 10);
string strValue(intNumber);
return strValue;
}
int situation;//函数进行状况,控制节点的比较运算符重载,进而操控选择三种算法
//节点
class node
{
public:
int num;//记录号
int F, G, H;//f=g+h,g是节点到始节点的开销 f=g+h
int parent;//记录父亲节点的编号
string str;
node(){
num = 0;
F = 0;
G = 0;
H = 0;
parent = 0;
}
node(int p, int n){//构造函数,e为边长,p是父亲节点的下标,n是此节点下标
num = n;
parent = p;
H = h[n];
}
//运算符重载,在优先队列中,会根据此比较进行排列,由此弹出队列时可以取到权值最小的节点
bool operator < (const node& a) const
{
if (0 == situation)//a*
{
return F > a.F;
}
if (1 == situation)//代价一致的宽度优先
{
return G > a.G;
}
if (2 == situation)//代价一致的宽度优先
{
return H > a.H;
}
}
};
//坐标
class Point
{
public:
float x, y;//x,y坐标
Point()
{
x = 0; y = 0;
}
void setPoint(float mx, float my)
{
x = mx; y = my;
}
};
//绘制矩形
void DrawRec(Point p)
{
glBegin(GL_LINE_LOOP);
glVertex2f(p.x - 2, p.y - 2);
glVertex2f(p.x + 2, p.y - 2);
glVertex2f(p.x + 2, p.y + 2);
glVertex2f(p.x - 2, p.y + 2);
glEnd();
}
//绘制直线
void DrawLine(Point p1, Point p2)
{
glBegin(GL_LINES);
glVertex2f(p1.x, p1.y);
glVertex2f(p2.x, p2.y);
glEnd();
}
//图类,保存城市名称和边
class Graph
{
private:
string city[20];//保存城市名称
int edge[20][20];//邻接矩阵
public:
Graph();
Point p[20];//城市坐标
void InitPoint();
void InitGraph();//读入数据
void Uniform_Cost_Search(int first, int end);//代价一致搜索
void greedy(int first, int end);//贪婪算法
void A_Star(int first, int end);//a*
void DrawGraph();
};
Graph::Graph()
{
InitGraph();
InitPoint();
}
//读取文件
void Graph::InitGraph()
{
ifstream ifs1("Romanian1.txt");
//读入名字
for (int i = 0; i < 20; i++)
{
ifs1 >> city[i];
}
ifs1.close();
//读入矩阵
ifstream ifs2("Romanian2.txt");
for (int i = 0; i < 20; i++)
{
for (int j = 0; j < 20; j++)
{
ifs2 >> edge[i][j];
}
}
ifs2.close();
}
//设置坐标
void Graph::InitPoint()
{
p[0].setPoint(5, 450);
p[1].setPoint(420, 280);
p[2].setPoint(258,240);
p[3].setPoint(105, 250);
p[4].setPoint(560, 190);
p[5].setPoint(340,410);
p[6].setPoint(400, 170);
p[7].setPoint(530,326);
p[8].setPoint(470, 490);
p[9].setPoint(120,350);
p[10].setPoint(130, 300);
p[11].setPoint(326, 540);
p[12].setPoint(90, 550);
p[13].setPoint(360,320);
p[14].setPoint(235,385);
p[15].setPoint(200,420);//sibiu
p[16].setPoint(15, 380);
p[17].setPoint(460, 320);
p[18].setPoint(500, 420);
p[19].setPoint(50, 500);
}
//绘制地图
void Graph::DrawGraph()
{
for (int i = 0; i < 20; i++)
{
for (int j = 0; j < 20; j++)
{
DrawRec(p[i]);
if ((0 != edge[i][j]) && (1000 != edge[i][j]))
{
DrawLine(p[i], p[j]);
//打印边的数值
float x = (p[i].x + p[j].x) / 2; float y = (p[i].y + p[j].y) / 2;//数字坐标
int a = edge[i][j];
string s = IntToStr(a);
glRasterPos2f(x,y);
drawString(s);
}
}
//打印字符
glRasterPos2f(p[i].x, p[i].y);
drawString(city[i]);
}
glFlush();
}
//代价一致搜索
void Graph::Uniform_Cost_Search(int first, int end)
{
vector<node> v;
cout << "正在进行代价一致的宽度优先搜索" << endl;
clock_t startTime, endTime;
startTime = clock();//计时开始
situation = 1;
int searchNum = 0;//搜索的节点
int generate = 1;//生成的节点
int path[20] = { 0 };//用于记录已经遍历过的城市,0是未遍历,1是已经遍历
if (first == end)
return;
//开始搜寻
priority_queue <node> Q;//优先队列
node temp(-1, 0);
temp.G = 0; temp.parent = -1; temp.num = 0;
temp.F = temp.G + temp.H;
temp.str = city[first];
Q.push(temp);
while (!Q.empty())
{
temp = Q.top();//弹出栈顶元素,栈顶元素就是F最小的节点
v.push_back(temp);
Q.pop();
if (temp.parent != -1)
{
Sleep(1000);
glColor3f(1.0f, 0.0f, 0.0f);
DrawLine(p[temp.num], p[temp.parent]);
glFlush();
}
searchNum++;
path[temp.num] = 1;//标记访问
if (temp.num == end)
{
endTime = clock();//计时结束
//打印路径
int i = v.size() - 1;
int k = v[i].num;
int j = v[i].parent;//表示确定是下一个节点
//从终点开始返回,打印边
for (; i > 0;)
{
Sleep(500);
i--;
if (v[i].num == j)
{
glColor3f(0.0f, 1.0f, 0.0f);
DrawLine(p[v[i].num], p[k]);
glFlush();
k = v[i].num;
j = v[i].parent;
}
}
//输出路程
cout << "代价一致的宽度优先搜索,结果如下" << endl << temp.str << endl << " 路径长度为:" << temp.G << endl;
cout << "搜索节点数:" << searchNum << endl;
cout << "生成节点数:" << generate << endl;
cout << "运行时间: " << (double)(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;
return;
}
for (int i = 0; i < 20; i++)
{
//找到相邻节点,边长不是1000则相邻,不是0就是为了排除自身
if (edge[temp.num][i] != 1000 && edge[temp.num][i] != 0)
{
if (path[i] != 1)
{
node te(temp.num, i);//创建节点
++generate;
te.G = edge[temp.num][i] + temp.G;
te.str = temp.str + "->" + city[i];
te.F = te.G + te.H;
if (te.parent != -1)
{
Sleep(300);
glColor3f(0.0f, 0.0f, 1.0f);
DrawLine(p[te.num], p[te.parent]);
Sleep(300);
glFlush();
}
Q.push(te);
}
}
}
}
}
void Graph::greedy(int first, int end)//贪婪算法
{
vector<node> v;
cout << "正在进行贪婪搜索" << endl;
clock_t startTime, endTime;
startTime = clock();//计时开始
situation = 2;
int searchNum = 0;//遍历的节点数
int generate = 1;//记录生成的节点数目
int path[20] = { 0 };//用于记录已经遍历过的城市,0是未遍历,1是已经遍历
if (first == end)
return;
//开始搜寻
priority_queue <node> Q;//优先队列
node temp(-1, 0);
temp.G = 0; temp.parent = -1; temp.num = 0;
temp.F = temp.G + temp.H;
temp.str = city[first];
Q.push(temp);
while (!Q.empty())
{
temp = Q.top();//弹出栈顶元素,栈顶元素就是F最小的节点
v.push_back(temp);
Q.pop();
if (temp.parent != -1)
{
Sleep(1000);
glColor3f(1.0f, 0.0f, 0.0f);
DrawLine(p[temp.num], p[temp.parent]);
glFlush();
}
searchNum++;
path[temp.num] = 1;//标记访问
if (temp.num == end)
{
endTime = clock();//计时结束
//打印路径
int i = v.size() - 1;
int k = v[i].num;
int j = v[i].parent;//表示确定是下一个节点
for (; i > 0;)
{
Sleep(500);
i--;
if (v[i].num == j)
{
glColor3f(0.0f, 1.0f, 0.0f);
DrawLine(p[v[i].num], p[k]);
glFlush();
k = v[i].num;
j = v[i].parent;
}
}
//输出路程
cout << "贪婪搜索,结果如下" << endl << temp.str << endl << " 路径长度为:" << temp.G << endl;
cout << "搜索节点数:" << searchNum << endl;
cout << "生成节点数:" << generate << endl;
cout << "运行时间: " << (double)(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;
return;
}
for (int i = 0; i < 20; i++)
{
//找到相邻节点,边长不是1000则相邻,不是0就是为了排除自身
if (edge[temp.num][i] != 1000 && edge[temp.num][i] != 0)
{
if (path[i] != 1)
{
node te(temp.num, i);//创建节点
++generate;
te.G = edge[temp.num][i] + temp.G;
te.str = temp.str + "->" + city[i];
te.F = te.G + te.H;
if (te.parent != -1)
{
Sleep(300);
glColor3f(0.0f, 0.0f, 1.0f);
DrawLine(p[te.num], p[te.parent]);
Sleep(600);
glFlush();
}
Q.push(te);
}
}
}
}
}
//a*寻路算法,first是起点下标,end是终点下标
void Graph::A_Star(int first, int end)
{
vector<node> v;
cout << "正在进行a*搜索" << endl;
situation = 0;
int generate = 1;
int searchNum = 0;
clock_t startTime, endTime;
startTime = clock();//计时开始
int path[20] = { 0 };//用于记录已经遍历过的城市,0是未遍历,1是已经遍历
if (first == end)
return;
//开始搜寻
priority_queue <node> Q;//优先队列
node temp(-1, first);
temp.G = 0; temp.parent = -1; temp.num = first;
temp.F = temp.G + temp.H;
temp.str = city[first];
Q.push(temp);
while (!Q.empty())
{
temp = Q.top();//弹出栈顶元素,栈顶元素就是F最小的节点
v.push_back(temp);
Q.pop();
if (temp.parent != -1)
{
Sleep(500);
glColor3f(1.0f, 0.0f, 0.0f);
DrawLine(p[temp.num], p[temp.parent]);
glFlush();
}
searchNum++;
path[temp.num] = 1;//标记访问
if (temp.num == end)
{
endTime = clock();//计时结束
//打印路径
int i = v.size() - 1;
int k = v[i].num;
int j = v[i].parent;//表示确定是下一个节点
for (; i > 0;)
{
Sleep(1000);
i--;
if (v[i].num == j)
{
glColor3f(0.0f, 1.0f, 0.0f);
DrawLine(p[v[i].num], p[k]);
glFlush();
k = v[i].num;
j = v[i].parent;
}
}
//输出路程
cout << "a*算法搜索,结果如下"<<endl << temp.str << endl<<" 路径长度为:" << temp.G << endl;
cout << "搜索节点数:" << searchNum << endl;
cout << "生成节点数:" << generate << endl;
cout << "运行时间: " << (double)(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;
return;
}
for (int i = 0; i < 20; i++)
{
//找到相邻节点,边长不是1000则相邻,不是0就是为了排除自身
if ((edge[temp.num][i] != 1000) && (edge[temp.num][i] != 0))
{
if (path[i] != 1)//若没有标记
{
node te(temp.num, i);//创建节点
++generate;
te.G = edge[temp.num][i] + temp.G;
te.str = temp.str + "->" + city[i];
te.F = te.G + te.H;
if (te.parent != -1)
{
Sleep(300);
glColor3f(0.0f, 0.0f, 1.0f);
DrawLine(p[te.num], p[te.parent]);
Sleep(600);
glFlush();
}
Q.push(te);
}
}
}
}
}
以上就是所有的代码了。实际上可以观察到,三种算法代码基本一致,只是评估函数的选择不同,
那为什么要写成这样呢?
因为这是完完全全赶出来的代码,当时混了好久,最后网上找不到,只好硬着头皮花了3个小时赶了出来。
希望用到此代码的人,可以自行看懂(写的稀烂,我现在已经看不懂了),然后若有时间的话,可以自己修修改改。