实验报告只是为了提供给学弟学妹们参考,所以很多代码都没有完整给出,希望学弟学妹们只用来参考,请勿直接抄袭!!!
有问题请联系QQ:1187987704
1实验目标概述
本次实验通过求解三个问题,训练基本 Java 编程技能,能够利用 Java OO 开
发基本的功能模块,能够阅读理解已有代码框架并根据功能需求补全代码,能够
为所开发的代码编写基本的测试程序并完成测试,初步保证所开发代码的正确性。
另一方面,利用 Git 作为代码配置管理的工具,学会 Git 的基本使用方法。
⚫ 基本的 Java OO 编程
⚫ 基于 Eclipse IDE 进行 Java 编程
⚫ 基于 JUnit 的测试
⚫ 基于 Git 的代码配置管理
2实验环境配置
安装git,安装eclipse,注册github账号,关联仓库,注册piazza,配置过程中跟着教师的演示没有较大的困难
https://github.com/ComputerScienceHIT/Lab1-1180300120.git
3实验过程
请仔细对照实验手册,针对四个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但无需把你的源代码全部粘贴过来!)。
为了条理清晰,可根据需要在各节增加三级标题。
3.1Magic Squares
此任务就是建立一个判断矩阵是否为幻方的方法,但是要注意检测不合格的数据,例如:浮点数、负数、矩阵不为方阵等。
3.1.1isLegalMagicSquare()
首先我们明确幻方是一个满足以下条件的方阵:每一行、每一列、正反对角线的和都相等;
根据这一个判断方法,我们来进行编写这个函数,首先要处理的是将文本读入,由于读入是按行读入的字符串,所以我们需要将字符串转化为我们想要的一组数字,但是要首先判断是否有负数和浮点数或者没有用空格分开的数,分别利用split(“-”)、split(“.”)、split(“ ”),如果分割开的数量大于1,就说明此行内含有不合法的输入,所以如果有这种情况我们需要报错,然后直接返回false,如果没有不合法输入,我们在进行以下判断,首先行列数我们可以在文件读入时直接计算得到,需要判断行列数是否相等,如果不相等报错返回false,如果相等继续判断,我们第一行为参考,计算出他的和,再计算每一行,每一列,正反对角线的和是否与刚才计算的参考值相等,如果不等返回false,在函数最后如果前面都没有返回,则说明幻方成立,所以返回true。
3.1.2generateMagicSquare()
由于这是已给的函数,所以这里直接对函数代码进行解析:
public static boolean generateMagicSquare(int n) throws FileNotFoundException {
if(n < 0) { //输入负数,下方定义数组容量时会出错
System.out.println("false,n输入为负数!");
return false;
}
if(n % 2 == 0) { //以下是对奇数使用的填写方法,如果n为偶数会在i%n==0时row++后填写magic[][]时出现越界情况
System.out.println("false,n输入为偶数!");
return false;
}
int magic[][] = new int[n][n]; //设置数组容量
int row = 0, col = n / 2, i, j, square = n * n; //从第一行的最中间位置开始填写,一共需要填写n*n个数
for (i = 1; i <= square; i++) {
magic[row][col] = i;
if (i % n == 0) //每填写n个数,其右上方的位置已被填写,所以此时需要向下移动一个位置,即行数加一,列数不变
row++;
else {
if (row == 0) //填写到第一行的时候,继续向右上方填写就需要到最后一行
row = n - 1;
else
row--; //如果没到第一行,那么继续行数向上移动
if (col == (n - 1)) //如果列填写到最后一列,那么再填写就应该从列的第一行
col = 0;
else //如果列没填写到最后一列,那么列就向右移动
col++;
}
}
File outputname = new File(".\\src\\P1\\txt\\6.txt"); //生成文件名
if(!outputname.exists()) { //如果此文件不存在
PrintWriter output = new PrintWriter(outputname); //准备对文件写
for (i = 0; i < n; i++) { //将上面已经生成好的幻方输入到文件中
for (j = 0; j < n; j++)
output.print(magic[i][j] + "\t");
output.println();
}
output.close(); //关闭文件
}
return true;
}
3.2Turtle Graphics
这个实验是需要我们了解turtle内的一些功能函数,利用这些完成旋转,前进,换色等功能然后实现需要我们完成的功能。
3.2.1Problem 1: Clone and import
打开在实验说明内的链接,然后点击右上方有“…”标记,选择clone,克隆到本地后移进到本地仓库即可。
3.2.2Problem 3: Turtle graphics and drawSquare
画一个正方形,我们很容易计算出旋转角度为每次90度,共4次,所以我们可以使用一个for循环,循环体一共运行4次,每次先forward边长后在turn90度,这样一个正方形就画好了。
3.2.3Problem 5: Drawing polygons
这里有两个函数,一个是根据边数计算内角,一个是根据内角计算边数。
计算内角:所有正多边形外角和是360度,所以我们用360除以边数(注意是浮点数除法,360需要乘以1.0),就是外角度数,然后用180减外角度数就是内角度数,返回即可;
计算边数:利用180减内角度数即为外角度数,用360除以外角度数即为边数,但是注意到边数是正整数,所以考虑到用(int)转化,但是要注意到这样转化为向下取整,所以例如3.9999这样的数我们应该知道他的边数是4,但是会错误的得到3,所以为了解决这个问题,我们可以使用round函数,进行四舍五入,这样返回得到的边数就是准确的了。
3.2.4Problem 6: Calculating Bearings
计算这个角度前,首先明确,这里说的角度是顺时针角度,而初始角度是相对于y轴正方向来说的。
第一个函数:在这里需要用到反三角函数,对于这个问题利用arctan最为合适,我们经过简单的数学分析,就可以很容易的得到我们想要的角度,但是注意不要忘记将得到的角度减去初始的角度。
第二个函数:这个题意是英文描述,开始有些不清除到底让我干什么,只是知道是给出一系列的点,计算旋转角度,后来仔细阅读了解了他的意思,就是开始默认初始角度为0,然后从第一个点走向第二个点后,朝向不变,然后以此朝向作为下一次旋转的初始角度,再计算第二个点到第三个点的旋转角度,依次类推。
很明显我们只需要弄清每次的初始角度就可以了,后面直接调用我们之前完成的函数即可。首先每次都是顺时针旋转,所以每次旋转后的朝向就是初始角度加上我们的旋转角度,一直累加就可以,但是要注意当角度大于360度时,我们要不断的让其减去360度,直到角度在0~360度之间,这样这个函数就完成了。
3.2.5Problem 7: Convex Hulls
凸包问题之前从未接触过,所以从头学习相对之前的函数有点困难,我采用的是graham scan法,首先要确定第一个点,但是要保证这个点一定在凸包上,这个点就是最左下方的点,左优先然后再比较下,我们得到第一个点后,就可以利用逆时针的旋转来依次确定接下来的点,做法是假设现在的最后确定的点是A,那么对于其他所有未被确定在凸包上的点与A相对于x轴正方向的夹角就是判定标准,选出这个角度最小的点即为A的下一个点,但是这里需要分情况讨论,也就是判断点是在A的右上,左上,左下,右下,与A横坐标相等,与A纵坐标相等这些情况,还要注意的是如果出现这种情况ABC三点共线,恰好这个角度就是当前的最小角度,那么我们应该选取距离A较远的那个点。选好A的下一个点后,我们需要判定这个点是否是起点(最左下方的点),如果是直接结束,如果不是将其加入凸包,最后返回这些点即可。
3.2.6Problem 8: Personal art
我设计的渐变色的图形是五环图形,如下:
代码如下:
public static void drawPersonalArt(Turtle turtle) {
try {
turtle.color(PenColor.BLACK);
int x = 6, y = 60, add = 0;
double z = 0.1;
for(int j = 1; j <= 5; j ++) {
switch(j) { //经计算每次行动次数需要相对3600有一定的偏移,才能恰好构成五环图形
case 1:
add = 0;
break;
case 2:
add = -90;
break;
case 3:
add = 30;
break;
case 4:
add = -60;
break;
case 5:
add = 0;
break;
}
for(int i = 1; i <= 3600 + add; i ++) { //每过几次就改变一下颜色,以达到渐变色的效果
if(i % y >= 0 && i % y <= x ) {
turtle.color(PenColor.BLACK);
}else if(i % y > x && i % y <= 2 * x) {
turtle.color(PenColor.BLUE);
}else if(i % y > 2 * x && i % y <= 3 * x) {
turtle.color(PenColor.CYAN);
}else if(i % y > 3 * x && i % y <= 4 * x) {
turtle.color(PenColor.GRAY);
}else if(i % y > 4 * x && i % y <= 5 * x) {
turtle.color(PenColor.GREEN);
}else if(i % y > 5 * x && i % y <= 6 * x) {
turtle.color(PenColor.MAGENTA);
}else if(i % y > 6 * x && i % y <= 7 * x) {
turtle.color(PenColor.ORANGE);
}else if(i % y > 7 * x && i % y <= 8 * x) {
turtle.color(PenColor.PINK);
}else if(i % y > 8 * x && i % y <= 9 * x) {
turtle.color(PenColor.RED);
}else if(i % y > 9 * x && i % y <= 10 * x) {
turtle.color(PenColor.YELLOW);
}
turtle.forward(1);
if(j % 2 == 1) { //第奇数个环和偶数个环旋转方向不同,而且旋转角度需要有细微的改变,才能使环向内旋转
turtle.turn(1 + z * i * 1.0 / 360);
}else {
turtle.turn(359 - z * i * 1.0 / 360);
}
}
}
}catch(Exception e) {
throw new RuntimeException("implement me!");
}
}
首先我们利用一个较小的旋转角度即可画出类似圆形的图案,我采用的是1度,要保证向内螺旋,就需要将旋转角度不断增大,也就是根据循环变量给出一个合理的偏移角度,例如:turtle.turn(1 + z * i * 1.0 / 360);我每个环循环基数是3600度,但是由于需要准确的到达每个环与相邻换之间的结点处,所以需要有一定的偏移量,即用3600加上一个或减去一个数,经过测试我们测得最内的一圈大概是150次完成一个圆周,所以我们就可以得到偏移量,还要注意的就是相邻的环的顺时针和逆时针是交替的,这样才能让相邻两个环分开,而不是叠在一起,最后就是画笔颜色的改变,我采用走6步换一个颜色,这样五环图形就形成了。
3.2.7Submitting
在eclipse中直接就可以提交,首先右键,点击team,选择share,然后根据步骤与github仓库链接,然后将全部内容加到缓冲区,然后将缓冲区中的内容commit and push即可。
3.3Social Network
建立一些点,这些点看做是人,点之间连边,有边代表这两点有联系,这样就构成了一个关系图,然后完成获取两点之间最近距离。
3.3.1设计/实现FriendshipGraph类
首先需要见一个Node类,存放有关点的相关信息,如:这个点代表的Person,下一个点,与这个点之间有边的点等。下一个点,我们可以看做纵向链表,与这个点之间有边的点可以看成横向的链表,如下图:
然后就可以写加点的函数了,加点的函数首先要判断是否重名,这个根据一个名字的hashset,如果此名字已经在里面了,就报错,如果没重名,就新建一个点,用来存这个人,将其连接到表头后,如果其实第一个点就充当表头。接下来编写加边函数,这个传入两个人,我们可以用person.node找到对应的点,这个是在person类中定义的,然后横向插入到链表中,这个方法就很显然了,这里不做解释了。最重要的是getdistance这个函数,由于是最近的路,所以我们采用bfs算法,对每一个顶点初始一个distance为0,并且设上访问标记visit,distance和visit都是在Node类中定义的,使用前初始化即可,将传入的两个人一个设为起点,一个设为终点,将起点加入队列,依次完成下列操作,队列头出队列,将其相邻的点入队列,然后这些点的distance都设为刚才出队列的点的distance+1,并设为已访问,直到终点加入队列,或者队列为空结束循环,返回终点的distance即可。
3.3.2设计/实现Person类
Person类比较简单,这里直接给代码:
class Person {
public String Name;
public FriendshipGraph.Node node = null;
public Person(String PersonName) {
Name = PersonName;
}
}
3.3.3设计/实现客户端代码main()
客户端的main(),可以采用几个选项:1.加人,2.加边,3.求距离,4.退出,将这些放入一个永真循环中,直到输入4退出循环。这里只写出大概的框架,不给出全部代码。
public static void main(String[] args){
//下面大部分都可以通过调用FriendshipGraph中的已有函数实现
int choice = 0;
Scanner x = new Scanner(System.in);
while(choice != 4){
System.out.print(“请输入你想选择的功能:(1.加人,2.加边,3.求距离,4.退出)”);
choice = x.nextInt();
if(choice == 1){
相应的建立新节点和新的person的代码;
}elseif(choice == 2){
相应的通过person求出node然后再加边的代码;
}elseif(choice == 3){
相应的通过person求最短距离的代码;
}
}
}
3.3.4设计/实现测试用例
我有3个测试用例,第一个是重名的测试(注意在@test后面加上(expected = RuntimeException.class)),第二个是简单图的测试(使用的就是题目里给的那个图),第三个是较为复杂的图的测试(如下图),然后选择几对点求之间的最短距离即可,利用assertEquals判断。