程序设计思维与实践第二周作业实验小结
作业一 迷宫问题
任意输入仅由0,1组成的5*5二维数组作为迷宫地图(0表示可以走,1表示不能走)。在确保迷宫左上角与右下角皆可以走且两位置间必有通路的情况下找到两位置间最短路线。
该题要求找到两位置间的最短路线,基于广度优先搜索的特点,我们优先考虑它。从起始位置出发,记录与该位置相邻且尚未到达的位置。依次对被记录的位置进行相同操作直至找到目标位置(该题条件较为宽松,类似题目中可能会出现找不到目标位置的情况,此时两位置间没有通路)。此时我们就找到了两位置间的最短路线。怎么将路线记录下来呢?我的做法是在进行广度优先搜索时,当我们记录位置x相邻且未到达的位置的时候,我们同时将x作为这些位置的前一位置记录下来。这样当我们搜索到目标位置时,我们可以找到该通路中目标位置的前一位置,前一位置的前一位置…直至找到起始位置。边找边记录,我们就能确定最短路线中每一个位置所在。
该实验原理做法均比较简单,但是越简单的实验越要细心应对。在实验的过程中由于未考虑到变量在使用时数值的变化,一直WA。是个很惨痛的教训。
#include <iostream>
#include <stdio.h>
#include <queue>
using namespace std;
struct point
{
int x,y;
};
point pre[5][5];
point path[25];
int maze[5][5];
int di[4][2]= {{-1,0},{1,0},{0,-1},{0,1}};
int main()
{
for(int i=0;i<5;i++)
{for(int j=0;j<5;j++)
{cin>>maze[i][j];}}
queue<point> Q;
point q;
q.x=0;
q.y=0;
Q.push(q);
maze[q.x][q.y]=1;
bool flag=false;
while(!Q.empty()&&!flag)
{
for(int i=0;i<4;i++)
{
q.x=Q.front().x+di[i][0];
q.y=Q.front().y+di[i][1];
if(q.x>=0&&q.x<5&&q.y>=0&&q.y<5&&maze[q.x][q.y]!=1)
{
Q.push(q);
pre[q.x][q.y]=Q.front();
if(q.x==4&&q.y==4)
{
flag=true;
break;
}
maze[q.x][q.y]=1;
}
}
Q.pop();
}
int step=0;
while(q.x!=0||q.y!=0)
{
path[step].x=q.x;
path[step].y=q.y;
int x=q.x;
q.x=pre[x][q.y].x;
q.y=pre[x][q.y].y;
step++;
}
cout<<"(0, 0)";
for(int i=step-1;i>=0;i--)
cout<<endl<<"("<<path[i].x<<", "<<path[i].y<<")";
}
作业二 倒水问题
给定两个特定容量的杯子。在未给出刻度的情况下,要求经多次倾倒操作(每次操作必有一个杯子为满或为空)使其中一个杯子中含有特定体积的水。给出操作过程。
倾倒操作共六种:A倒满,A倒空,B倒满,B倒空,A倒B与B倒A。我们在操作的过程中,无论怎样倾倒,在两个杯子容量确定的情况下,我们最多经历(m+1*(n+1)种状态。(m,n)为两个杯子的容量。在操作一定,状态一定的情况下,我们可以将其理解为上一迷宫问题的变形(操作类比找邻接位置,状态类比通路,实验要求类比目标位置)。确定了这一思想,我们就确定了用广度优先搜索解决这一问题的思路。首先确定每一种操作的实现。然后以两个杯子均为空为起始状态,记录该状态下进行操作后且未被记录的所有状态。依次对被记录的状态进行相同操作直至找到目标状态(即其中一个杯子中有实验要求体积的水)。同样,在进行广度优先搜索时,我们要记录每个状态的前一状态。这样在找到目标状态后我们同样可以通过倒推的方式找到每一步操作过程。
有些题目可能风马牛不相及,但多加思考就可能发现其中隐含的关联与算法设计的相似性。思考和发散性思维的重要性,是这道题给我最宝贵的财富。
#include<iostream>
#include<map>
#include<queue>
using namespace std;
int A, B, C;
struct Status {
int x, y;
Status() {}
Status(int X, int Y) {
x = X;
y = Y;
}
bool operator<(const Status &b) const {
return x == b.x ? y < b.y : x < b.x;
}
bool operator==(const Status &b) const {
return x == b.x && y == b.y;
}
Status pourAB() {
Status c;
c.x = max(x + y - B, 0);
c.y = min(B, x + y);
return c;
}
Status pourBA() {
Status c;
c.y = max(x + y - A, 0);
c.x = min(A, x + y);
return c;
}
Status FillA() {
return Status(A, y);
}
Status FillB() {
return Status(x, B);
}
Status EmptyA() {
return Status(0, y);
}
Status EmptyB() {
return Status(x, 0);
}
};
map<Status, bool> mp;
map<Status, Status> from;
queue<Status> Q;
void check(Status x, Status y) {
if (mp[x] == 0) {
mp[x] = 1;
from[x] = y;
Q.push(x);
}
}
void print(Status t) {
if (from.find(t) != from.end()) {
print(from[t]);
if(from[t].pourAB()==t) cout<<"pour A B"<<endl;
else if(from[t].pourBA()==t) cout<<"pour B A"<<endl;
else if(from[t].FillA()==t) cout<<"fill A"<<endl;
else if(from[t].FillB()==t) cout<<"fill B"<<endl;
else if(from[t].EmptyA()==t) cout<<"empty A"<<endl;
else if(from[t].EmptyB()==t) cout<<"empty B"<<endl;
}
}
void bfs(int a, int b) {
Status t(a, b);
Q.push(t);
mp[t] = 1;
while (!Q.empty()) {
t = Q.front();
Q.pop();
if (t.x == C || t.y == C) {
print(t);
cout<<"success"<<endl;
return;
}
check(t.pourAB(), t);
check(t.pourBA(), t);
check(t.FillA(), t);
check(t.FillB(), t);
check(t.EmptyA(), t);
check(t.EmptyB(), t);
}
}
int main() {
while (cin >> A >> B >> C) {
mp.clear();
from.clear();
while (!Q.empty())
Q.pop();
bfs(0, 0);
}
return 0;
}
实验一 鉴定烷烃基类别
给定烷烃基的6个原子和五条化学键,鉴定烷烃基的种类。
这是很简单的一道题,稍微有点麻烦的地方是原子没有编号方法,我们无法通过判断第i个原子所连化学键数直接确定其类别,不过也不是什么大问题。由于6个原子的烷烃基一共只有5种情况。我们可以根据每种情况的特点确定其类型。例如,我通过判断不同烷烃基中每个原子连接化学键个数的差异将其区分开。其中个数相同的两种情况也可以通过比较与化学键最多的原子相连接的原子的差异将其区分开。这样,当我们将五种情况区分开的时候,我们就能轻易找出烷烃基的类型。
本题实验过程较为顺利,出现的小问题在于在记录每个原子所连接化学键个数时,忘记原子i的化学键个数存储在第i-1号位置导致数组越界,今后要更加细心才是。
#include <iostream>
using namespace std;
int main(){
int m;
cin>>m;
for(int i=0;i<m;i++){
int link[5][2];
int count[6]={0};
for(int i=0;i<5;i++){
for(int j=0;j<2;j++){
cin>>link[i][j];
count[link[i][j]-1]++;
}
}
int count1=0;
for(int i=0;i<6;i++){
if(count[i]==2){
count1++;
}
}
switch(count1){
case 0:{
cout<<"2,3-dimethylbutane"<<endl;
break;
}
case 1:{
cout<<"2,2-dimethylbutane"<<endl;
break;
}
case 2:{
int max=0;
int count2=0;
for(int i=0;i<6;i++){
if(count[i]>max){
max=count[i];
}
}
for(int i=0;i<5;i++){
if(count[link[i][0]-1]==max&&count[link[i][1]-1]==2){
count2++;
}
if(count[link[i][1]-1]==max&&count[link[i][0]-1]==2){
count2++;
}
}
if(count2==1){
cout<<"2-methylpentane"<<endl;
}
else if(count2==2){
cout<<"3-methylpentane"<<endl;
}
break;
}
case 4:{
cout<<"n-hexane"<<endl;
break;
}
}
}
}
实验二 评测系统的基本实现
给定题数与单位罚时。输入待排序的所有同学的姓名与题目完成情况(正数表示完成时间,正数加(正数)表示完成时间加错误次数,负数表示未完成)。依次按照完成题数、时间分与名字的字典序进行排序,最后输出排序情况。
在我看来,这道题可以分成两个部分解决。第一部分解决数据处理,第二部分解决排序。在得到每一位同学的姓名之后,我们要依次对每道题目的数据进行处理。按照题意,负数与0可以不加考虑,由此我们就完成了对数据的第一步筛选。在对数据的进一步处理中,我用的方法简单粗暴:依次取出数据中的一位进行处理(在这一过程中出现本实验一个漏洞:片面判断错误次数不超过十,导致答案错误)。在处理完某同学全部成绩数据的同时计算得到完成题数与时间分。之后按照题目要求重载操作符并进行排序(强调sort()函数的使用,可以节省排序函数的实现时间,但要注意左闭右开),最后输出结果(注意格式)。
在本实验的实现过程中,主要出现这么几点问题:1.结构体数组开辟太小,导致程序运行时经常越界;2.片面认为错误次数仅为个位数,导致时间分计算错误;3.未考虑到字符型数据与整型数据在数据值上的差异导致时间分计算错误;4.排序的实现,未能注意到sort()排序范围左闭右开;5.输出格式的规范。
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string>
using namespace std;
struct student{
char name[10];
int score;
int num;
bool operator<(const student p)
{
if(num>p.num) return true;
else if(num==p.num)
{
if(score<p.score) return true;
else if(score==p.score)
{
if(name>p.name) return true;
}
}
return false;
}
};
int main(){
int m,n;
cin>>n>>m;
student s[10000];
int i=0;
while(cin>>s[i].name){
s[i].num=0;
s[i].score=0;
for(int j=0;j<n;j++){
string t;
cin>>t;
int score1=0;
int score2=0;
if(t[0]!='0'&&t[0]!='-'){
s[i].num++;
for(int k=0;k<t.length();k++){
if(t[k]=='('){
k++;
while(t[k]!=')'){
score2=score2*10+(t[k]-48);
k++;}
break;
}
else if(t[k]!='('&&t[k]!='\0'){
score1=score1*10+(t[k]-48);
}
}
s[i].score=s[i].score+score1+score2*m;
}}
i++;
}
sort(s,s+i);
for(int j=0;j<i;j++){
printf("%-10s %2d %4d\n",s[j].name,s[j].num,s[j].score);
}
}
实验三 瑞神打牌
牌局由四个人组成,分别位于东南西北四个方位。指定一位发牌员,按照顺时针的发牌顺序从其下一位开始发牌。定义牌的大小规则为(梅花)<(方片)<(黑桃)<(红桃),花色相同则2<3<4<5<6<7<8<9<T<J<Q<K<A。排序并输出每个人手里的牌。
在我眼中,这道题可以分为两部分。一部分为分牌,一部分为排序。首先我们不必在乎牌的归属权问题。我们先简单的将其分为四堆,第一堆为第一个拿到牌的人,以此类推。然后我们通过发牌员的不同分析牌堆的不同归属情况,例如N发牌则S拥有2号牌堆,E发牌则S拥有1号牌堆以此类推。我认为在该题中,将分组问题分解为分牌和分配,降低了问题的复杂性。分组解决了,我们来解决排序。由于花色和牌面的大小顺序已知,且每张牌都是独一无二的。我们不妨给每张牌赋予其独一无二的价值作为排序的准则。例如,梅花2的价值为013+0=0,红桃A的价值为313+12=51。当我们计算出每张牌的价值后,我们就很容易对每个牌堆进行排序,之后只需要注意输出格式即可。
本题问题出在输入上,一个很简单的问题(cin>>没有写入while循环)却让我卡了很久,实属不该。
#include<iostream>
#include<stdio.h>
#include<string>
#include<algorithm>
using namespace std;
struct Poker{
char cla;
char n;
int num;
};
bool cmp(const Poker &p1,const Poker &p2){
if(p1.num<p2.num) return true;
return false;
}
int main(){
char begin;
Poker p[4][13];
int a=0,b=0;
while(cin>>begin&&begin!='#'){
for(int i=0;i<13;i++){
for(int j=0;j<4;j++){
cin>>p[j][i].cla>>p[j][i].n;
switch(p[j][i].cla){
case 'C':{ a=0;break;}
case 'D':{ a=1;break;}
case 'S':{ a=2;break;}
case 'H':{ a=3;break;}
}
if(p[j][i].n>='2'&&p[j][i].n<='9'){
b=p[j][i].n-'2';}
else{
switch(p[j][i].n){
case 'T':{ b=8;break;}
case 'J':{ b=9;break;}
case 'Q':{ b=10;break;}
case 'K':{ b=11;break;}
case 'A':{ b=12;break;}
}
}
p[j][i].num=a*13+b;
}
}
for(int i=0;i<4;i++){
sort(p[i],p[i]+13,cmp);
}
int t=0;
string s[4]={"South player:","West player:","North player:","East player:"};
switch(begin){
case 'E':{ t=0;break;}
case 'N':{ t=1;break;}
case 'W':{ t=2;break;}
case 'S':{ t=3;break;}
}
for(int i=0;i<4;i++){
cout<<s[i]<<endl;
int q=(i+t)%4;
cout<<"+---+---+---+---+---+---+---+---+---+---+---+---+---+"<<endl;
for(int j=0;j<13;j++){
cout<<"|"<<p[q][j].n<<" "<<p[q][j].n;
}
cout<<"|"<<endl;
for(int j=0;j<13;j++){
cout<<"| "<<p[q][j].cla<<" ";
}
cout<<"|"<<endl;
for(int j=0;j<13;j++){
cout<<"|"<<p[q][j].n<<" "<<p[q][j].n;
}
cout<<"|"<<endl;
cout<<"+---+---+---+---+---+---+---+---+---+---+---+---+---+"<<endl;
}
cout<<endl;
}
}
上一篇: javascript的三种循环写法
下一篇: Eclipse上导出码云上项目的实例详解