哈工大2020春软件构造Lab1实验报告

实验报告只是为了提供给学弟学妹们参考,所以很多代码都没有完整给出,希望学弟学妹们只用来参考,请勿直接抄袭!!!
有问题请联系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判断。

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值