对于学了一学期的数据结构来说,用图论中的一些算法实现校园导航系统应该是课设中比较简单的选择了,但是这个选项比较烂大街,所以班里可能有很多个做校园导航系统的,大家尽量在其中加一些自己的东西会比较好,避免和别人的相似性过高,
话不多说直接先展示一下整体的效果。
点击返回后会回到主页
之后进入第二个功能,也就是寻找最短路的功能
首先是输入所在地与目的地,由于我使用的实现图形化界面的库是easyX,这个库的功能比较简单,实现输入只能由这样一个比较丑陋的框去输入,我目前也不知道有没有更加美观的实现输入的方法,有的话可以评论区多交流。
接下来是第三个功能,是用TSP算法实现的一个导游功能,会将所有被创建在邻接矩阵中的地图上的点都走过一遍,且是最短路。
第四个功能就明显是凑数的简单功能,实现一个评论区。
ok程序的大致效果展示完了,下面来展示一下代码。
我是在vs2022中运行的程序,说实话有许多报错都需要借助其他帖子进行debug,在我的印象中至少有三四处需要更改一下你自己的vs2022设置,所以如果直接照搬代码出现报错,不妨将错误内容整个贴在csdn搜索框里,细心翻找一定有能够解决问题的帖子帮助你。
首先得下载easyx库,直接去官网搜就行,我下载之后的图标是这样的
上代码:
主要就两个文件,,如果需要背景图片,记得要将图片文件和程序放在一个文件夹里。
首先是主程序 realmain:
#define _CRT_SECURE_NO_WARNINGS
#undef UNICODE
#undef _UNICODE
//#include "menu.h"
#include "graph.h"
#include <string>
#include <iostream>
#include <cstring>
#include <fstream>
#include <graphics.h> // 引用图形库头文件
#include <conio.h>
#include<stdio.h>
#include<easyx.h>
using namespace std;
inline void PerrorExit(const string& msg = "") {
if (!msg.empty()) cout << msg << ": ";
cout << strerror(errno) << endl;
}
bool GetPlace1(string& line) {
//getline(cin, line)
line = {};
char s[30];
TCHAR InputWindow1[] = _T("请输入起点");
InputBox(s, 30, InputWindow1);
for (int i = 0; i < strlen(s); i++)
{
line += s[i];
}
cout << line << endl;
if (!place_map.count(line)) {
HWND hndtipsM = GetHWnd();
int isok = MessageBox(hndtipsM, "暂无此地", "提示", MB_OK);
cout << "buycunzai" << endl;
return false;
}
return true;
}
bool GetPlace2(string& line) {
//getline(cin, line)
line = {};
char s[30];
TCHAR InputWindow1[] = _T("请输入终点");
InputBox(s, 30, InputWindow1);
for (int i = 0; i < strlen(s); i++)
{
line += s[i];
}
cout << line << endl;
if (!place_map.count(line)) {
HWND hndtipsM = GetHWnd();
//用一个字符数组来总结句子
int isok = MessageBox(hndtipsM, "暂无此地", "提示", MB_OK);
cout << "buycunzai" << endl;
return false;
}
return true;
}
void button(int x, int y, int w, int h, TCHAR* text) // 定义按钮绘制函数
{
setbkmode(TRANSPARENT); // 设置背景透明
setfillcolor(RGB(255, 105, 180)); //设置按钮颜色
fillroundrect(x, y, x + w, y + h, 10, 10); // 填充圆角矩形
TCHAR s1[] = "黑体"; // 设置字体样式为黑体
settextstyle(19, 0, s1); // 设置字体大小为 30
int tx = x + (w - textwidth(text)) / 2; // 计算字符串输出的 x 坐标
int ty = y + (h - textheight(text)) / 2; // 计算字符串输出的 y 坐标
outtextxy(tx, ty, text);
}
void firstpage();
void page()
{
//initgraph(846, 506);
cleardevice();
IMAGE img2;
loadimage(&img2, "./map2.bmp");
putimage(0, 0, &img2); // 显示另一张图片
MOUSEMSG msg1;
TCHAR s5[50] = "返回";
button(650, 450, 150, 30, s5);
while (true)
{
msg1 = GetMouseMsg();//获取鼠标操作
if (msg1.x >= 650 && msg1.x <= 650 + 150 && msg1.y >= 450 && msg1.y <= 450 + 30)
{
if (msg1.uMsg == WM_LBUTTONDOWN) {
cleardevice();
firstpage();
}
}
}
}
void secondpage()
{
cleardevice();
IMAGE img4;
loadimage(&img4, "./black.bmp");
putimage(0, 0, &img4); // 显示另一张图片
MOUSEMSG msg1;
TCHAR s5[50] = "返回";
button(650, 450, 100, 30, s5);
string line;
if (!GetPlace1(line)) firstpage(); //获得起点
int start = place_map[line];
if (!GetPlace2(line)) firstpage(); //获得终点
int end = place_map[line];
// 最短距离
cout << "您所需的最短距离为:" << min_dis[start][end] << " 米哦!" << endl;
//cout << numble11 << "a"<<endl;
// 最短路径
// cout << "最短路径是这样走:";
cout << places[start] << " --> ";
for (auto p : paths[start][end]) cout << places[p] << " --> ";
cout << places[end] << endl;
string result;
result += places[start];
for (auto p : paths[start][end])
{
result += "--->";
result += places[p];
}
result += "-->";
result += places[end];
char* charArray;
charArray = new char[result.size() + 1]; //创建字符数组
strcpy(charArray, result.c_str()); //将string类型转换为char*类型
settextcolor(BLACK);
settextstyle(24, 12, _T("宋体"));
outtextxy(20, 15, _T("您应该这样走:"));
outtextxy(44, 40, charArray);
int w02= min_dis[start][end];
char str[100];
sprintf(str, "%d", w02);
outtextxy(20, 80, _T("所需要的最短路径长为:"));
outtextxy(280, 80, str); //注意*的使用
outtextxy(330, 80, _T("米"));
while (true)
{
msg1 = GetMouseMsg();//获取鼠标操作
if (msg1.x >= 650 && msg1.x <= 650 + 100 && msg1.y >= 450 && msg1.y <= 450 + 30)
{
if (msg1.uMsg == WM_LBUTTONDOWN) {
cleardevice();
firstpage();
}
}
}
}
void tsppage()
{
cleardevice();
IMAGE img4;
loadimage(&img4, "./black.bmp");
putimage(0, 0, &img4); // 显示另一张图片
MOUSEMSG msg1;
TCHAR s5[50] = "返回";
button(650, 450, 100, 30, s5);
string line;
settextcolor(BLACK);
if (!GetPlace1(line)) firstpage();
int start = place_map[line];
string resultTSP = TSP(start);
char* charArray;
charArray = new char[resultTSP.size() + 1];
strcpy(charArray, resultTSP.c_str());
outtextxy(20, 15, _T("想要以最短的距离游览北化请您这样走:"));
int maxlen = 80;
int x = 44;
int y = 40;
int len = strlen(charArray);
for (int i = 0; i < len; i += maxlen)
{
char lineText[81];
strncpy(lineText, charArray + i, maxlen);
lineText[maxlen] = '\0'; // 手动添加字符串结束符
cout << lineText;
outtextxy(x, y, lineText);
y += 30;
}
while (true)
{
msg1 = GetMouseMsg();//获取鼠标操作
if (msg1.x >= 650 && msg1.x <= 650 + 100 && msg1.y >= 450 && msg1.y <= 450 + 30)
{
if (msg1.uMsg == WM_LBUTTONDOWN) {
cleardevice();
firstpage();
}
}
}
}
void commentpage()
{
cleardevice();
IMAGE img4;
loadimage(&img4, "./black.bmp");
putimage(0, 0, &img4); // 显示另一张图片
MOUSEMSG msg1;
TCHAR s5[50] = "返回";
button(650, 450, 100, 30, s5);
char s[10];
TCHAR InputWindow1[] = _T("请留下您宝贵的评论");
InputBox(s, 100, InputWindow1);
string line;
for (int i = 0; i < strlen(s); i++)
{
line += s[i];
}
cout << line << endl;
if (line.empty()) firstpage();
ofstream os{ "comment.txt",ios_base::app };
os << line << endl;
cout << endl;
while (true)
{
msg1 = GetMouseMsg();//获取鼠标操作
if (msg1.x >= 650 && msg1.x <= 650 + 100 && msg1.y >= 450 && msg1.y <= 450 + 30)
{
if (msg1.uMsg == WM_LBUTTONDOWN) {
cleardevice();
firstpage();
}
}
}
}
void lastpage()
{
cleardevice();
IMAGE img4;
loadimage(&img4, "./black.bmp");
putimage(0, 0, &img4); // 显示另一张图片
MOUSEMSG msg1;
TCHAR s5[50] = "返回";
button(650, 450, 100, 30, s5);
string line;
ifstream is{ "comment.txt" };
int x = 44, y = 40;
while (getline(is, line))
{
char* charArray;
charArray = new char[line.size() + 1]; //创建字符数组
strcpy(charArray, line.c_str()); //将string类型转换为char*类型
outtextxy(x,y, charArray);
y += 30;
}
cout << endl;
while (true)
{
msg1 = GetMouseMsg();//获取鼠标操作
if (msg1.x >= 650 && msg1.x <= 650 + 100 && msg1.y >= 450 && msg1.y <= 450 + 30)
{
if (msg1.uMsg == WM_LBUTTONDOWN) {
cleardevice();
firstpage();
}
}
}
}
void firstpage() //首页
{
initgraph(846, 506); // 创建绘图窗口
IMAGE img1;
loadimage(&img1, "./buct1.bmp");
putimage(0, 0, &img1);
setbkmode(TRANSPARENT);
settextcolor(WHITE);
settextstyle(35, 25, _T("宋体"));
outtextxy(170, 15, _T("校园导航系统 启动!"));
TCHAR s[50] = "显示校园平面图"; // 定义第一个按钮上显示的文本
TCHAR s1[50] = "两地点间最短路径"; // 定义第二个按钮上显示的文本
TCHAR s2[50] = "导游模式"; // 定义第三个按钮上显示的文本
TCHAR s3[50] = "留下评论"; // 定义第四个按钮上显示的文本
TCHAR s4[50] = "查看评论";
button(120, 225, 150, 30, s); // 调用 button 函数绘制第一个按钮
button(120, 275, 150, 30, s1);
button(120, 325, 150, 30, s2);
button(120, 375, 150, 30, s3);
button(120, 425, 150, 30, s4);
ExMessage msg; // 定义消息类型
string line;
while (true) { // 进入循环等待消息
if (peekmessage(&msg, EM_MOUSE)) {
switch (msg.message) // 根据消息类型进行相应的处理
{
case WM_LBUTTONDOWN: // 当鼠标左键被按下时
if (msg.x >= 120 && msg.x <= 120 + 150 && msg.y >= 225 && msg.y <= 225 + 30) // 判断是否点击了第一个按钮
{
page();
}
if (msg.x >= 120 && msg.x <= 120 + 150 && msg.y >= 275 && msg.y <= 275 + 30) // 判断是否点击了第二个按钮
{
secondpage();
}
if (msg.x >= 120 && msg.x <= 120 + 150 && msg.y >= 325 && msg.y <= 325 + 30) // 判断是否点击了第三个按钮
{
tsppage();
}
if (msg.x >= 120 && msg.x <= 120 + 150 && msg.y >= 375 && msg.y <= 375 + 30)
{
commentpage();
}
if (msg.x >= 120 && msg.x <= 120 + 150 && msg.y >= 425 && msg.y <= 425 + 30)
{
lastpage();
}
break;
default:
break;
}
}
}
}
int main()
{
InitGraph();
firstpage();
return 0;
}
主程序中并没有算法,更多的是实现图形界面,与用户交互的东西,建议先花一些时间学习一下easyx库的使用,由于这个库中实际上没有页面跳转的功能,所以只能将每个页面都写作一个函数,看似页面跳转实际上是清空整个页面再重新把新的页面打印出来。
比如每次实现功能后点击返回回到首页就是由下面的代码实现的
之后是头文件 graph
实现最短路径是平平无奇的 Dijkstra,很多人都会选择这个算法。显得有些大众化,没什么新意,所以我说是平平无奇,不是说这个伟大的算法平平无奇。
实现导航功能的是TSP算法可以去学习一下
#ifndef NAVIGATION_GRAPH_H
#define NAVIGATION_GRAPH_H
#include <climits>
#include <unordered_map>
#include <string>
#include <map>
#include <stack>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
constexpr int PN = 16;
constexpr int INF = INT_MAX;
constexpr int Max = 16;
const char* places[PN] =
{
"留学生公寓",
"玉兰餐厅",
"工程训练中心",
"紫竹苑宿舍楼",
"大学生活动中心",
"第二教学楼(C教和D教)",
"紫竹餐厅",
"校史博物馆",
"图书馆",
"校医院",
"樱花苑1号楼附近",
"樱花苑7号楼附近",
"实验楼",
"操场",
"第一教学楼(A教和B教)",
"体育馆",
};
// 邻接矩阵
int graph[PN][PN] =
{
{INF, 443, 722, 637, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF},
{443, INF, 421, 166, 38 , 215, 313, INF, INF, INF, INF, INF, INF, INF, INF, INF},
{722, 421, INF, INF, INF, 336, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF},
{637, 166, INF, INF, 126, INF, 214, INF, INF, INF, INF, INF, INF, INF, INF, INF},
{INF, 38 , INF, 126, INF, 222, 275, 176, INF, INF, INF, INF, INF, INF, INF, INF},
{INF, 215, 336, INF, 222, INF, INF, INF, 213, INF, INF, INF, INF, INF, INF, INF},
{INF, 313, INF, 214, 275, INF, INF, 154, INF, 163, INF, 194, INF, INF, INF, INF},
{INF, INF, INF, INF, 176, INF, 154, INF, 431, INF, INF, 314, INF, INF, INF, INF},
{INF, INF, INF, INF, INF, 213, INF, 431, INF, INF, INF, 555, 139, INF, INF, INF},
{INF, INF, INF, INF, INF, INF, 163, INF, INF, INF, 98 , 154, INF, INF, INF, INF},
{INF, INF, INF, INF, INF, INF, INF, INF, INF, 98 , INF, 150, INF, 58 , INF, INF},
{INF, INF, INF, INF, INF, INF, 194, 314, 555, 154, 150, INF, 499, INF, 408, INF},
{INF, INF, INF, INF, INF, INF, INF, INF, 139, INF, INF, 499, INF, INF, 364, INF},
{INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, 58 , INF, INF, INF, 538, 508},
{INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, 408, 364, 538, INF, 248},
{INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, 508, 248, INF},
};
void Dijkstra1(int s, int e)
{
int n = Max;
int vis[Max] = { 0 }; // 标记节点是否已被访问
int d[Max];
int pre[Max] = { 0 }; // 记录路径的前驱节点
// 初始化距离数组
for (int i = 0; i < n; i++)
d[i] = INF;
d[s] = 0;
// 找到距离源节点最近的下一个节点,并更新距离数组
for (int i = 0; i < n; i++)
{
int u = -1;
int Min = INF;
for (int j = 0; j < n; j++)
{
if (vis[j] == 0 && d[j] < Min)
{
u = j;
Min = d[j];
}
}
// 若找不到下一个节点,则退出循环
if (u == -1) break;
vis[u] = 1;
// 更新与当前节点相邻节点的最短距离
for (int k = 0; k < n; k++)
{
if (vis[k] == 0 && graph[u][k] != INF)
{
if (d[u] + graph[u][k] < d[k])
{
d[k] = d[u] + graph[u][k];
pre[k] = u; // 更新前驱节点
}
}
}
}
// 输出最短距离和路径
cout << "起点:" << places[s] << endl;
cout << "终点:" << places[e] << endl;
cout << "最短距离:" << d[e] << endl;
cout << "路径:";
int path[Max];
int p = e;
int count = 0;
while (p != s)
{
path[count++] = p;
p = pre[p];
}
path[count++] = s;
for (int i = count - 1; i >= 0; i--)
{
cout << places[path[i]];
if (i != 0) cout << " --> ";
}
cout << endl;
}
// 转换为完全图后的邻接矩阵
int cgraph[PN][PN] = { 0 };
// 名称到id的映射,图操作时使用id而非名称
unordered_map<string, int> place_map;
// 两点之间最短距离
int min_dis[PN][PN] = { 0 };
// 两点之间最短路径
vector<int> paths[PN][PN];
void Dijkstra(int start)
{
// 初始化
map<int, int> S, T, Pre;
for (int i = 0; i < PN; ++i)
{
T[i] = graph[start][i];
if (graph[start][i] != INF) Pre[i] = start;
}
T.erase(start);
for (int i = 1; i < PN; ++i)
{
// 从T中取出距离起点最近的点
int min = INF, min_index = -1;
for (const auto& pr : T)
{
auto k = pr.first;
auto v = pr.second;
if (v < min)
{
min = v;
min_index = k;
}
}
if (min_index < 0) break; // 无可达点
// 将最近点移动至结果集
S.insert({ min_index, min });
T.erase(min_index);
// 使用新加入点,更新待处理集的距离
for (const auto& pr : T) {
auto k = pr.first;
auto v = pr.second;
if (graph[min_index][k] == INF) continue;
int nd = S[min_index] + graph[min_index][k];
if (nd < v || v == INF) {
Pre[k] = min_index;
T[k] = nd;
}
}
}
// 已经获取到结果集、路径集
for (const auto& pr : S)
{
auto k = pr.first;
auto v = pr.second;
// v:从start到k的最短距离
// 更新最短距离、完全图
min_dis[start][k] = v;
if (cgraph[start][k] == INF) cgraph[start][k] = v;
// 填入路径
vector<int> path;
int cur = k;
while (Pre[cur] != start)
{
path.push_back(Pre[cur]);
cur = Pre[cur];
}
reverse(path.begin(), path.end());
paths[start][k] = path;
}
}
string TSP(int start)
{
static int dp[1 << PN][PN] = { 0 };
static int pre[1 << PN][PN];
for (int s = 1; s < (1 << PN); ++s)
{
for (int t = 0; t < PN; ++t)
{
dp[s][t] = 0;
pre[s][t] = -1;
}
}
for (int s = 1; s < (1 << PN); ++s)
{
for (int i = 0; i < PN; ++i)
{
if ((s & (1 << i)) == 0) continue;
if ((s & (1 << start)) == 1 && i != start) continue;
int pre_s = (s ^ (1 << i));
if (pre_s == 0)
{
dp[s][i] = cgraph[start][i];
pre[s][i] = start;
continue;
}
int min = INF, min_index = -1;
for (int j = 0; j < PN; ++j)
{
if ((pre_s & (1 << j)) == 0) continue;
if (dp[pre_s][j] + cgraph[j][i] < min)
{
min = dp[pre_s][j] + cgraph[j][i];
min_index = j;
}
}
dp[s][i] = min;
pre[s][i] = min_index;
}
}
// 获得结果集,路径集
int result = dp[(1 << PN) - 1][start];
vector<int> path;
int s = (1 << PN) - 1;
int t = start;
while (s != 0) {
int pre_t = pre[s][t];
path.push_back(pre_t);
s = (s ^ (1 << t));
t = pre_t;
}
reverse(path.begin(), path.end());
cout << endl;
cout << "最短距离是:" << result << endl;
cout << endl;
cout << "请这样走:";
path.push_back(start);
string aa;
for (int i = 0; i < path.size() - 1; ++i)
{
// cout << places[path[i]] << " --> ";
aa+= places[path[i]];
aa += "--> ";
auto next = path[i + 1];
for (auto p : paths[path[i]][next])
{
aa += places[p];
aa += "--> ";
}
}
aa += places[start];
//cout << aa << endl;
cout << endl;
//cout << places[start] << endl;
return aa;
}
// 初始化校园地图
void InitGraph()
{
for (int i = 0; i < PN; ++i) place_map[places[i]] = i;
// 初始化邻接矩阵
for (int i = 0; i < PN; ++i)
{
for (int j = 0; j < PN; ++j)
{
if (graph[i][j] != INF) graph[j][i] = graph[i][j];
}
graph[i][i] = 0;
}
// 用Dijkstra计算两点之间的最短距离、路径
for (int i = 0; i < PN; ++i)
{
for (int j = 0; j < PN; ++j)
{
min_dis[i][j] = INF;
cgraph[i][j] = INF;
if (graph[i][j] != INF) cgraph[i][j] = graph[i][j];
}
}
for (int i = 0; i < PN; ++i) Dijkstra(i);
// 检查是否存在两点,它们之间不可达,是则报错
for (int i = 0; i < PN; ++i)
{
for (int j = 0; j < PN; ++j)
{
if (cgraph[i][j] == INF)
{
cout << places[i] << " 到 " << places[j] << " 不可达" << endl;
exit(1);
}
}
}
}
#endif //NAVIGATION_GRAPH_H
ok本文的主要内容就结束了,还是建议大家将自己的想法加入到课设里,实现更多的功能,或者将图形界面 做的更加美观些,easyx的优点是上手很快,但是如果时间充足的话还是选其他的库将图形界面做的好一些,像我这样显得有一些千篇一律了。