实验一:图书信息管理系统的设计与实现
(一)实验内容:
设计并实现一个图书信息管理系统。根据实验要求设计该系统的菜单和交互逻辑,并编码实现增删改查的各项功能。 该系统至少包含以下功能:
- 根据指定图书个数,逐个输入图书信息;
- 逐个显示图书表中所有图书的相关信息;
- 能根据指定的待入库的新图书的位置和信息,将新图书插入到图书表中指定的位置;
- 根据指定的待出库的旧图书的位置,将该图书从图书表中删除;
- 能统计表中图书个数;
- 实现图书信息表的图书去重;
- 实现最爱书籍查询,根据书名进行折半查找,要求使用非递归算法实现,成功返回此书籍的书号和价格;
- 图书信息表按指定条件进行批量修改;
- 利用快速排序按照图书价格降序排序;
- 实现最贵图书的查找;
(二)实验提示:
图书信息的定义:
typedef struct {
char no[8]; //8位书号
char name[20]; //书名
int price; //价格
}Book;
顺序表的定义:
typedef struct {
Book *elem; //指向数据元素的基地址
int length; //线性表的当前长度
}SqList;
链表的定义:
typedef struct LNode{
Book data; //数据域
struct LNode *next; //指针域
}LNode,*LinkList;
1.基于顺序存储/链式存储结构的图书信息表的创建和输出
定义一个包含图书信息(书号、书名、价格)的顺序表。读入相应的图书数据来完成图书信息表的创建,然后统计图书表中的图书个数,同时逐行输出每本图书的信息。
输入
输入 n+1 行,其中前 n 行是 n 本图书的信息(书号、书名、价格),每本图书信息占一行,书号、书名、价格用空格分隔,价格之后没有空格。最后第 n+1 行是输入结束标志:0 0 0(空格分隔的三个 0)。其中书号和书名为字符串类型,价格为浮点数类型。
输出
总计 n+1 行,第 1 行是所创建的图书表中的图书个数,后 n 行是 n 本图书的信息(书号、
书名、价格),每本图书信息占一行,书号、书名、价格用空格分隔。其中价格输出保留两
位小数。
输入样例:
9787302257646 程序设计基础 25.00
9787302164340 程序设计基础(第 2 版) 20.00
9787302219972 单片机技术及应用 32.00
9787302203513 单片机原理与应用技术 26.00
9787810827430 工业计算机控制技术——原理与应用 29.00
9787811234923 汇编语言程序设计教程 32.00
0 0 0
2.基于顺序存储/链式存储结构的图书信息表的修改
读入图书信息表,然后计算所有图书的平均价格,将所有低于平均价格的图书价格提高20%,所有高于或等于平均价格的图书价格提高10%,最后逐行输出价格修改后的图书信息。
输入
输入 n+1 行,其中前 n 行是 n 本图书的信息(书号、书名、价格),每本图书信息占一行,书号、书名、价格用空格分隔,价格之后没有空格。最后第 n+1 行是输入结束标志:0 0 0(空格分隔的三个 0)。其中书号和书名为字符串类型,价格为浮点数类型。
输出
总计 n+1 行,第 1 行是修改前所有图书的平均价格,后 n 行是价格修改后 n 本图书的信息(书号、书名、价格),每本图书信息占一行,书号、书名、价格用空格分隔。其中价格输出保留两位小数。
输入样例:
9787302257646 程序设计基础 25.00
9787302164340 程序设计基础(第 2 版) 20.00
9787302219972 数据挖掘与机器学习 32.00
9787302203513 模式识别与智能计算 26.00
9787810827430 工业计算机控制技术——原理与应用 29.00
9787811234923 操作系统教程 32.00
0 0 0
输出样例:
9787302257646 程序设计基础 30.00
9787302164340 程序设计基础(第 2 版) 24.00
9787302219972 数据挖掘与机器学习 35.20
9787302203513 模式识别与智能计算 28.60
9787810827430 工业计算机控制技术——原理与应用 31.90
9787811234923 操作系统教程 35.20
3.基于顺序存储/链式存储结构的图书信息表的最贵图书查找
读入相应的图书信息表,然后查找价格最高的图书,输出相应图书的信息。
输出
总计 m+1 行,其中,第 1 行是最贵的图书数目,(价格最高的图书可能有多本),后m行是最贵图书的信息,每本图书信息占一行,书号、书名、价格用空格分隔。其中价格输出保留两位小数。
输出样例:
2
9787302219972 数据挖掘与机器学习 35.20
9787811234923 操作系统教程 35.20
4.基于顺序存储/链式存储结构的图书信息表的最爱图书的查找
读入相应的图书信息表,然后根据指定的最爱图书的名字,输出相应图书的信息。
输入
输入1行,为每次待查找的最爱图书名字。
输出
若查找成功,输出k+1行,对于每次查找,第一行是最爱图书数目,同一书名的图书可能有多本,后K行是最爱图书的信息(书号、书名、价格),每本图书信息占一行,书号、书名、价格用空格分隔,其中价格输出保留两位小数。若查找失败:只输出以下提示:抱歉,没有你的最爱!
输出样例
2
9787302257646 程序设计基础 30.00
9787302164340 程序设计基础(第 2 版) 24.00
5.基于顺序存储/链式存储结构的图书信息表的新书入库
读入指定的待入库的新图书的位置和信息,将新图书插入到图书表中指定的位置上,最后输出新图书入库后所有图书的信息。
输入
总计n+1行,首先输入第1行,内容仅为一个整数,代表待入库的新图书的位置序号,然后输入n行,内容为新图书的信息,书号、书名、价格用空格分隔。
输出
若插入成功,输出新图书入库后所有图书的信息(书号、书名、价格),总计n+1行,每行是一本图书的信息,书号、书名、价格用空格分隔。其中价格输出保留两位小数。
若插入失败,只输出以下提示:抱歉,入库位置非法!
输入样例:
2
9787302265436 计算机导论实验指导 18.00
输出样例:
9787302257646 程序设计基础 30.00
9787302265436 计算机导论实验指导 18.00
9787302164340 程序设计基础(第 2 版) 24.00
9787302219972 数据挖掘与机器学习 35.20
9787302203513 模式识别与智能计算 28.60
9787810827430 工业计算机控制技术——原理与应用 31.90
9787811234923 操作系统教程 35.20
6.基于顺序存储/链式存储结构的图书信息表的旧书出库
读入指定的待出库的旧图书的书号,将该图书从图书表中删除,最后输出旧图书出库后所有图书的信息。
输入
输入待出库的旧图书的书号;
输出
若删除成功,输出旧图书出库后所有图书的信息(书号、书名、价格),每行是一本图书的信息,书号、书名、价格用空格分隔。其中价格输出保留两位小数。
若删除失败,只输出以下提示:出库失败,未找到该图书!
7.基于顺序存储/链式存储结构的图书信息表的图书去重
出版社出版的任何一本图书的书号(ISBN)都是唯一的,即图书表中不允许包含书号重复的图书。读入相应的图书信息表(事先加入书号重复的记录),然后进行图书的去重,即删除书号重复的图书(只留第一本),最后输出去重后所有图书的信息。
输出
总计输出m+1行(m<=n),其中,第一行是去重后的图书数目,后m行是去重后图书的信息(书号、书名、价格),每本图书信息占一行,书号、书名、价格用空格分隔,其中价格输出保留两位小数。
#include <bits/stdc++.h> //包含所有头文件
using namespace std;
//图书信息的定义
typedef struct{
char no[15]; //书号
char name[40]; //书名
double price; //价格
}Book;
//顺序表的定义
typedef struct{
Book *elem; //指向数据元素的基地址
int length; //线性表的当前长度
}SqList;
SqList lst;
void del(int pos){
for(int i=pos;i<lst.length;i++){
lst.elem[i]=lst.elem[i+1];
}
lst.length--;
}
//
void qsort(Book *a,int l,int r){
double mid=a[(l+r)/2].price;
int i=l,j=r;
do{
while(a[i].price>mid) i++;
while(a[j].price<mid) j--;
if(i<=j){
swap(a[i],a[j]);
i++;
j--;
}
}while(i<=j);
if(l<j) qsort(a,l,j);
if(i<r) qsort(a,i,r);
}
int main(){
lst.elem=new Book[100];
lst.length=0;
char opr;
do{
cout<<endl<<endl;
cout<<"输入图书信息请按1"<<endl;
cout<<"逐个显示图书表中所有图书请按2"<<endl;
cout<<"将新图书插入到图书表中指定的位置请按3"<<endl;
cout<<"删除图书请按4"<<endl;
cout<<"统计图书数量请按5"<<endl;
cout<<"去重请按6"<<endl;
cout<<"查找最爱图书请按7"<<endl;
cout<<"批量修改请按8"<<endl;
cout<<"将图书按照降序排列请按9"<<endl;
cout<<"查找最贵图书请按m"<<endl;
cout<<"退出请按x"<<endl<<endl;
cin>>opr;
if(opr=='1'){
cout<<"请输入图书信息"<<endl;
Book t;
do{
cin>>t.no>>t.name>>t.price;
if(t.price<1e-9)break;
lst.elem[++lst.length]=t;
}while(1);
continue;
}
if(opr=='2'){
for(int i=1;i<=lst.length;i++){
cout<<lst.elem[i].no<<" "<<lst.elem[i].name<<" "<<lst.elem[i].price<<endl;
}
continue;
}
if(opr=='3'){
cout<<"请输入图书信息和插入位置"<<endl;
int pos;
Book t;
cin>>t.no>>t.name>>t.price>>pos;
for(int i=++lst.length;i>pos;i--){
lst.elem[i]=lst.elem[i-1];
}
lst.elem[pos]=t;
continue;
}
if(opr=='4'){
cout<<"请输入要删除的图书位置"<<endl;
int pos;
cin>>pos;
del(pos);
continue;
}
if(opr=='5'){
cout<<"图书数量为:"<<lst.length<<endl;
continue;
}
if(opr=='6'){
for(int i=2;i<=lst.length;i++){
string str1=lst.elem[i].name;
for(int j=1;j<i;j++){
string str2=lst.elem[j].name;
if(str1==str2){
del(i--);
break;
}
}
}
continue;
}
if(opr=='7'){
cout<<"请输入书名"<<endl;
string str;
cin>>str;
for(int i=1;i<=lst.length;i++){
string str1=lst.elem[i].name;
if(str==str1){
cout<<lst.elem[i].no<<" "<<lst.elem[i].name<<" "<<lst.elem[i].price<<endl;
break;
}
}
continue;
}
if(opr=='8'){
float avg=0;
for(int i=1;i<=lst.length;i++){
avg+=lst.elem[i].price;
}
avg/=lst.length;
for(int i=1;i<=lst.length;i++){
lst.elem[i].price=lst.elem[i].price<avg?lst.elem[i].price*1.2:lst.elem[i].price*1.1;
}
for(int i=1;i<=lst.length;i++){
cout<<lst.elem[i].no<<" "<<lst.elem[i].name<<" "<<lst.elem[i].price<<endl;
}
continue;
}
if(opr=='9'){
qsort(lst.elem,1,lst.length);
for(int i=1;i<=lst.length;i++){
cout<<lst.elem[i].no<<" "<<lst.elem[i].name<<" "<<lst.elem[i].price<<endl;
}
continue;
}
if(opr=='m'){
float max=0;
for(int i=1;i<=lst.length;i++){
max=lst.elem[i].price>max?lst.elem[i].price:max;
}
for(int i=1;i<=lst.length;i++){
if(lst.elem[i].price==max){
cout<<lst.elem[i].no<<" "<<lst.elem[i].name<<" "<<lst.elem[i].price<<endl;
}
}
continue;
}
if(opr=='x') break;
}while(1);
return 0;
}
实验二:隐式图的搜索问题
实验内容:
编写九宫重排问题的启发式搜索(A*算法)求解程序。
在3х3组成的九宫棋盘上,放置数码为1~8的8个棋子,棋盘中留有一个空格,空格周围的棋子可以移动到空格中,从而改变棋盘的布局。根据给定初始布局和目标布局,编程给出一个最优的走法序列。输出每个状态的棋盘
测试数据:初始状态:123456780 目标状态:012345678
【输入格式】
输入包含三行,每行3各整数,分别为0-8这九个数字,以空格符号隔开,标识问题的初始状态。0表示空格,例如:
2 0 3
1 8 4
7 6 5
实验提示:
对于九宫重排问题的解决,首先要考虑是否有答案。每一个状态可认为是一个1×9的矩阵,问题即通过矩阵的变换,可以变换为目标状态对应的矩阵。由数学知识可知,可计算这两个有序数列的逆序值,如果两者都是偶数或奇数,则可通过变换到达,否则,这两个状态不可达。这样,就可以在具体解决问题之前判断出问题是否可解,从而可以避免不必要的搜索。
如果初始状态可以到达目标状态,那么采取什么样的方法呢?常用的状态空间搜索有深度优先和广度优先。广度和深度优先搜索有一个很大的缺陷就是他们都是在一个给定的状态空间中穷举。这在状态空间不大的情况下是很合适的算法,可是当状态空间十分大,且不预测的情况下就不可取了。他的效率实在太低,甚至不可完成。由于九宫重排问题状态空间共有9!个状态,如果选定了初始状态和目标状态,有9!/2个状态要搜索,考虑到时间和空间的限制,在这里要求采用A*算法求解。
A*算法是启发式搜索算法,启发式搜索就是在状态空间中对每一个搜索分支进行评估,得到最好的分支,再从这个分支进行搜索直到目标。这样可以省略大量无畏的搜索路径,提到了效率。在启发式搜索中,利用当前与问题有关的信息作为启发式信息指导搜索,这些信息能够有效省略大量无谓的搜索路径,大大提高了搜索效率。
启发式搜索算法定义了一个估价函数f(n),与问题相关的启发式信息都被计算为一定的 f(n) 的值,引入到搜索过程中。f(n) = g(n) +h(n)其中f(n) 是节点n的估价函数,g(n)是在状态空间中从初始节点到节点n的实际代价,h(n)是从节点n到目标节点最佳路径的估计代价。 在九宫重排问题中,显然g(n)就是从初始状态变换到当前状态所移动的步数,估计函数h(n)估计的是节点n到目标节点的距离,我们可以用欧几里德距离、曼哈顿距离或是两节的状态中数字的错位数来估计。
实验提示:
1、存储结构的定义
typedef struct node//八数码结构体
{
int nine[N][N];//数码状态
int f;//估价值
int direct;//空格移动方向
struct node *parent;//父节点
}pNode;
2、启发式搜索算法的描述:
(1)把初始节点S0 放入Open表中,f(S0)=g(S0)+h(S0);
(2)如果Open表为空,则问题无解,失败退出;
(3)把Open表的第一个节点取出放入Closed表,并记该节点为n;
(4)考察节点n是否为目标节点。若是,则找到了问题的解,成功退出;
(5)若节点n不可扩展,则转到第(2)步;
(6)扩展节点n,生成子节点ni(i=1,2,……),计算每一个子节点的估价值f(ni) (i=1,2,……),并为每一个子节点设置指向父节点的指针,然后将这些子节点放入Open表中;
(7)根据各节点的估价函数值,对Open表中的全部节点按从小到大的顺序重新进行排序;
(8)转到第(2)步。
启发式搜索算法的工作过程:
//Main.cpp
#include<iostream>
#include<vector>
#include<list>
using namespace std;
//点类表示棋盘里的一点
//x、y表示点在棋盘里的坐标,F、G、H表示该点和0点交换后的代价
struct point
{
int x, y;
int F, G, H;
point* parent;
point(int _x, int _y) :x(_x), y(_y), F(0), G(0), H(0), parent(NULL) {}
};
//A*算法类
class Astar
{
public:
Astar() {}
Astar(vector<vector<int>> _start, vector<vector<int>> _end) :begin(_start), end(_end) {}
list<point*> findPath();
private:
int inverseNum(vector<int> array); //求数组逆序数函数
bool haveSolution(); //判断此问题是否有解
list<point*> getPath(); //寻找移动路线
bool isCanReach(point* p, point* q); //判断两个点之间是否可以移动
vector<point*>getSurrounding(point* p); //得到一个点周围可达的点
point* getLeastPoint(); //从openList中获得F值最小的一个点
int calcG(); //计算G值,通过记录使用getLeastPoint()的次数
int calcF(point* start, point* end); //计算F值。
int calcH(point* start, point* end); //计算H值。利用与begin数组相同的数组,计算移动点后和end数组不同的数字个数
point* getZero(); //寻找此时的0点的位置
void move(point* p, point* q); //移动棋盘上的两点
point* isInList(list<point*> list, point* p); //判断openList中是否已存在此点
private:
int gCount = 0;
vector<vector <int>> begin;
vector<vector<int>> end;
list<point*> openList;
list<point*> closedList;
};
list<point*> Astar::findPath()
{
list<point*> path;
if (haveSolution())
path = getPath();
else
cout << "该问题无解" << endl;
openList.clear();
closedList.clear();
return path;
}
bool Astar::haveSolution()
{
vector<int> s;
vector<int> e;
for (int i = 0; i < this->begin.size(); i++)
for (int j = 0; j < this->begin[i].size(); j++)
{
s.push_back(begin[i][j]);
e.push_back(end[i][j]);
}
int sn = inverseNum(s);
int en = inverseNum(e);
if (sn / 2 == 0 && en / 2 == 0) //逆序数都是偶数
return true;
else if (sn % 2 == 1 && en % 2 == 1) //逆序数都是奇数
return true;
else
return false;
}
int Astar::inverseNum(vector<int> array)
{
int count = 0;
for (int i = 0; i < array.size(); i++)
{
if (array[i] == 0) //0点不计
continue;
for (int j = 0; j < i; j++)
{
if (array[j] > array[i])
count++;
}
}
return count;
}
list<point*> Astar::getPath()
{
openList.push_back(getZero());
while (!openList.empty())
{
auto curPoint = getLeastPoint();
openList.remove(curPoint);
move(getZero(), curPoint);
closedList.push_back(curPoint);
auto surroundPoints = getSurrounding(curPoint);
for (auto& target : surroundPoints)
{
if (!isInList(openList, target)) //没探索过的点添加进openList
{ //由于结果存储在closedList中,所以openList中重复可不计
target->parent = curPoint;
target->G = calcG();
target->H = calcH(getZero(), target);
target->F = calcF(getZero(), target);
openList.push_back(target);
}
if (calcH(getZero(), target) == 0) //移动的结果是所有点都和end数组一样,即H值为0
return closedList;
}
}
}
bool Astar::isCanReach(point* p, point* q)
{
//p、q的x、y越线无效
//p、q两点坐标相等无效
//p、q两点需要移动两步无效
if (p->x<0 || p->x>this->begin.size() - 1
|| p->y<0 || p->y>this->begin.size() - 1
|| q->x<0 || q->x>this->begin.size() - 1
|| q->y<0 || q->y>begin.size() - 1
|| (q->x == p->x && q->y == p->y)
|| abs(p->x - q->x) + abs(p->y - q->y) == 2)
return false;
else
return true;
}
vector<point*> Astar::getSurrounding(point* p)
{
vector<point*> surroundPoint;
for (int i = p->x - 1; i <= p->x + 1; i++)
for (int j = p->y - 1; j <= p->y + 1; j++)
if (isCanReach(p, new point(i, j)))
surroundPoint.push_back(new point(i, j));
return surroundPoint;
}
point* Astar::getLeastPoint()
{
gCount++;
if (!openList.empty()) {
auto resPoint = openList.front();
for (auto& point : openList)
if (point->F < resPoint->F && isCanReach(getZero(), point)) //要确保点是可达的
resPoint = point;
return resPoint;
}
return NULL;
}
point* Astar::getZero()
{
for (int i = 0; i < begin.size(); i++)
for (int j = 0; j < begin[1].size(); j++)
if (begin[i][j] == 0)
return new point(i, j);
}
int Astar::calcG()
{
return gCount;
}
int Astar::calcF(point* start, point* end)
{
return calcG() + calcH(start, end);
}
int Astar::calcH(point* start, point* endp)
{
vector<vector<int>> maze = begin;
int t = maze[start->x][start->y];
maze[start->x][start->y] = maze[endp->x][endp->y];
maze[endp->x][endp->y] = t;
int count = 0;
for (int i = 0; i < maze.size(); i++)
for (int j = 0; j < maze[1].size(); j++)
if (maze[i][j] != end[i][j])
count++;
return count;
}
void Astar::move(point* p, point* q)
{
int t = begin[p->x][p->y];
begin[p->x][p->y] = begin[q->x][q->y];
begin[q->x][q->y] = t;
}
point* Astar::isInList(list<point*>list, point* p)
{
for (auto q : list)
{
if (q->x == p->x && q->y == p->y)
return q;
}
return NULL;
}
int main()
{
//初始和目标3*3九宫格棋盘
vector<vector<int>> start = { {5,8,3},{1,0,4},{7,6,2} };
vector<vector<int>>end = { {1,2,3},{8,0,4},{7,6,5} };
Astar at(start, end);
list<point*> rst = at.findPath();
cout << "已找到结果,现以二维数组下标形式输出0位置移动的过程:" << endl;
for (auto target : rst)
{
cout << "(" << target->x << "," << target->y << ")";
cout << "- >";
}
cout << "目标点" << endl;
return 0;
}