HIT 软件构造 lab1 文章末尾有全部源代码

2020年春季学期
计算机学院《软件构造》课程

Lab 1实验报告

目录

1 实验目标概述 1
2 实验环境配置 1
3 实验过程 1
3.1 Magic Squares 1
3.1.1 isLegalMagicSquare() 1
3.1.2 generateMagicSquare() 1
3.2 Turtle Graphics 1
3.2.1 Problem 1: Clone and import 2
3.2.2 Problem 3: Turtle graphics and drawSquare 2
3.2.3 Problem 5: Drawing polygons 2
3.2.4 Problem 6: Calculating Bearings 2
3.2.5 Problem 7: Convex Hulls 2
3.2.6 Problem 8: Personal art 2
3.2.7 Submitting 2
3.3 Social Network 2
3.3.1 设计/实现FriendshipGraph类 2
3.3.2 设计/实现Person类 2
3.3.3 设计/实现客户端代码main() 2
3.3.4 设计/实现测试用例 3
4 实验进度记录 3
5 实验过程中遇到的困难与解决途径 3
6 实验过程中收获的经验、教训、感想 3
6.1 实验过程中收获的经验和教训 3
6.2 针对以下方面的感受 3

1 实验目标概述
本次实验通过求解三个问题,训练基本 Java 编程技能,能够利用 Java OO 开 发基本的功能模块,能够阅读理解已有代码框架并根据功能需求补全代码,能够 为所开发的代码编写基本的测试程序并完成测试,初步保证所开发代码的正确性。 另一方面,利用 Git 作为代码配置管理的工具,学会 Git 的基本使用方法。
基本的 Java OO 编程
基于Eclipse IDE 进行 Java 编程
基于 JUnit 的测试
基于 Git 的代码配置管理
2 实验环境配置
1.安装了Eclipse和JDK、Git,其中Eclipse安装的版本是Eclipse IDE for Java Developers
2.学会了一些简单的Git指令,如初始化一个本地仓库(git init),将lab1 push到本地仓库,以及查看版本介绍等
3.在上课6-6的过程中,在Eclipse IDE中安装配置了Junit, 学会了关于Junit的使用与书写方式,在项目2中使用了Junit进行测试,在项目3中书写并使用Junit进行测试

3 实验过程
请仔细对照实验手册,针对四个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但无需把你的源代码全部粘贴过来!)。
为了条理清晰,可根据需要在各节增加三级标题。
3.1 Magic Squares
该任务有两个目标:

  1. 实现从txt文件中读取数据并保存,随后判断其是否满足幻方的定义
  2. 创建一个阶数为奇数的幻方,并用上面的判断方法再次判断其是否满足幻方的定义,并且在题目给出的代码中进行增加,使其创建出的幻方保存到文件中,并且当程序抛出异常时,能够提示用户出错而不是暴力地终止程序
    3.1.1 isLegalMagicSquare()
    该函数我将其分为三个步骤:
    1. 文件读入
    2. 将数据写入二维数组并进行输入内容判断
    3. 判断是否满足幻方行、列、对角之和相等的条件
      3.1.1.1 文件读入
      在该函数中,最让我感到困难的部分就是从文件中读入并保存为二维数组,由于在C语言中,C语言没有Java语言所提供的很多可直接使用的数据结构和算法,并且二者关于文件读入的方式也不相同,在查阅了网上的资料后,得出想要实现上述算法,需要使用BufferedReader或者FileInputStream两种方法其中一种实现。由于网上使用BufferedReader的方法更多,所以我在本次实验中也采用了这种方法,也通过这个读入方法学习了Java中List等数据结构的使用,也因此写了博客记录,博客地址如下:
      https://blog.csdn.net/weixin_43348617/article/details/104710633
      3.1.1.2 将数据写入二维数组并进行输入内容判断
      在实验要求中,有几项要求是关于处理输入文件的各种特殊情况,例如txt文件中的数据不能构成一个矩阵,矩阵中有数字为非正整数,数字之间并非使用‘\t’分割等,所以下面一项一项来处理这些异常内容
      1 不是矩阵
      不是矩阵有两种情况,第一种是文件中内容为空,第二种是文件中的二维数组行列数不相等。这两种情况均可以用try-catch来捕捉异常,捕捉异常后返回false即可,并且数字之间并非使用‘\t’分割也能同时解决
      2 矩阵中有内容为非正整数或未用‘\t ’分隔
      关于非正整数的判断,在课堂上老师曾讲过正则表达式的方法,可以用str[i].matches(“[0-9]”)的方法来判断,是一个十分快速简单高效的方法,但是由于在上课前我就已经完成这部分的代码,所以也没有修改的必要,我使用的方法是valueOf的方法
      通过读取文件一整行使用循环来判断其中是否有数值为非正整数
      3.1.1.3 判断是否满足幻方行、列、对角之和相等的条件
      这部分相比于前两部分来说简单很多,只需要判断幻方行之和、列之和以及对角之和是否相等就行,属于该函数最简单的部分,不必过度赘述
      不过在百度百科的代码部分,找到了比我原本写的对角线相加更加简洁快速的方式,所以最后改成了与百度百科代码类似的方法
      其中sum0是参照,我设定为第一行的值相加
/*
		 * 判断对角之和是否与sum0相等
		 */
		int diagSum1 = 0;
		int diagSum2 = 0;
		int i, j;
		for (i = 0, j = 0; i < n; i++, j++) {
			diagSum1 += square[i][j];
			diagSum2 += square[i][n - j - 1];
		}
		if (diagSum1 != sum0) {
			return false;
		}
		if (diagSum2 != sum0) {
			return false;
		} else {
			return true;
		}

3.1.2 generateMagicSquare()
在查阅相关资料后,我发现这种创造奇数阶幻方的方法叫做罗伯特法(连续摆数法),其法则为把“1”放在中间一列最上边的方格中,从它开始,按对角线方向(比如说按从左下到右上的方向)顺次把由小到大的各数放入各方格中,如果碰到顶,则折向底,如果到达右侧,则转向左侧,如果进行中轮到的方格中已有数或到达右上角,则退至前一格的下方。
同时它也有助记口诀:
1居上行正中央,依次斜填切莫忘。
上出框界往下写,右出框时左边放。
重复便在下格填,角上出格一个样。
流程图如下

下为该函数的注释

public static boolean generateMagicSquare(int n) {
		File file = new File("src/P1/txt/6.txt");
		if ((n % 2 == 0) || n <= 0) { // 判断参数n是否不符合输入要求
			System.out.println(false); // 不符合要求提示用户输入错误
			return false; // 不符合要求返回false
		}
		int magic[][] = new int[n][n]; // 创建矩阵
		int row = 0, col = n / 2, i, j, square = n * n;
		for (i = 1; i <= square; i++) {
			magic[row][col] = i; // 每次赋值一个矩阵元素并计算下一个位置
			if (i % n == 0) // 计算下一个位置
				row++; // 如果放置完一个右下角元素,开始下一个
			else {
				if (row == 0) // 元素位置来到第一行,则返回最后一行
					row = n - 1;
				else
					row--; // 否则行数-1
				if (col == (n - 1)) // 元素位置来到最后一列,则返回第一列
					col = 0;
				else
					col++; // 否则列数+1
			}
		}

		// 文件输出,将生成的矩阵输出到6.txt中
		try {
			BufferedWriter out = new BufferedWriter(new FileWriter(file)); // 使用FileOutputStream输出
			for (i = 0; i < n; i++) {
				for (j = 0; j < n; j++) {
					String line = String.valueOf(magic[i][j]);
					out.write(line);
					out.write('\t');
				}
				out.newLine(); // 每次输出完一行在文件中换行
			}
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return true;
	}

然后是关于异常产生的分析:

  1. java.lang.ArrayIndexOutOfBoundsException

    该异常产生的原因是数组越界,原因是在生成函数中,i<=square,计算机面对非整数时常用的方法是向下取整,输入奇数时col = n / 2的值会向下取整,而square = n*n,所以此时i即使=square,数组也不会越界,而如果输入为偶数时,当i = square时就会发生越界 ,原因是在下标为n的数组中放入了大于n个的数据
  2. java.lang.NegativeArraySizeException

    该异常产生的原因是数组下标为负值,输入的n为负值循环时数组下标为负,自然会出错

3.2 Turtle Graphics
熟悉turtle Gragpics的各种函数接口,调用已有的函数,实现java语言的绘图功能,并用java给出的.Math库里的函数实现一些简单的计算功能
3.2.1 Problem 1: Clone and import
3.2.1.1 从github获取代码
从网页Clone or download,选择download选项下载
之后在eclipse中选择File->import->File System->Browse将文件import进P2文件夹中
3.2.1.2 在本地创建仓库
使用eclipse的Team功能自动创建一个本地仓库,具体为File->Team->Share Project
3.2.1.3 使用git管理本地开发
同时也使用eclipse里的Team->commit->Staged Changes->Commit Message->Commit(推到本地仓库)
之后就可以使用git log来查看版本更新信息等

3.2.2 Problem 3: Turtle graphics and drawSquare
通过阅读题目要求,可以知道Problem3主要是让我们熟悉java的绘图方式,也很贴心地给出了forward(steps)和turn(degrees)两个很实用的工具,在这里具体说一说两个函数的作用

  1. forward(steps)函数
    该函数根据当前坐标和面向角度,计算走steps距离后的坐标,根据起始点生成线段类lineSeg,再根据朝向及lineSeg构造Action添加到ActionList中等待执行

  2. turn(degrees)函数
    更改当前朝向,向ActionList中添加turn的Action

    说完两个函数,drawSquare函数也就是画一个正方形,十分简单,调用两个函数即可

public static void drawSquare(Turtle turtle, int sideLength) {
		// throw new RuntimeException("implement me!");
		for (int i = 0; i < 4; i++) {
			turtle.forward(sideLength);
			turtle.turn(90);
		}
	}

3.2.3 Problem 5: Drawing polygons
3.2.3.1 calculateRegularPolygonAngle(int sides)函数
该函数十分简单,就是根据正多边形的边数计算该正多边形的内角角度值

public static double calculateRegularPolygonAngle(int sides) {
		// throw new RuntimeException("implement me!");
		return ((double) (sides - 2) * 180.0 / sides);
	}

3.2.3.2 calculatePolygonSidesFromAngle(double angle)函数
该函数也很简单,是根据正多边形的内角角度值来计算正多边形的边数,但是我在写的时候,TurtleSoupTest中calculatePolygonSidesFromAngleTest(double angle)第二个测试始终过不了.后来我将这个函数的返回值输出,发现输出值为6而不是预想的7,原因是在double与int的强制转换类型中,double类型的6.997在转换为int类型后,计算机向下取整变成了6,解决方式就是使用Math.round(double)函数,该函数可以将浮点数四舍五入,最后再用int类型强制转换时,就不会出现上述问题.

public static int calculatePolygonSidesFromAngle(double angle) {
		// throw new RuntimeException("implement me!");
		return ((int) Math.round((360.0 / (180.0 - angle))));
	}

3.2.3.3 drawRegularPolygon(Turtle turtle, int sides, int sideLength)函数
该函数也很简单,但需要注意的是转向角度并非正多边形的内角,而是180-内角

public static void drawRegularPolygon(Turtle turtle, int sides, int sideLength) {
		// throw new RuntimeException("implement me!");
		double angle = 180 - calculateRegularPolygonAngle(sides);
		for (int i = 0; i < sides; i++) {
			turtle.forward(sideLength);
			turtle.turn(angle);
		}
	}

3.2.4 Problem 6: Calculating Bearings
3.2.4.1 calculateBearingToPoint(double currentBearing, int currentX, int currentY, int targetX, int targetY)函数
该函数的作用是计算((currentX, currentY), currentBearing)构成的向量与((currentX, currentY), (targetX, targetY))构成的向量之间的夹角
思路是先计算((currentX, currentY), (targetX, targetY))构成的向量与y轴的tan值,使用的函数为Math.atan2(double,double), 再与currentBearing相减,所得值为两向量间的夹角
需要注意的是角度不能为负值,每次计算之后都需要判断角度是否为负,若为负则需要+360使其变正

public static double calculateBearingToPoint(double currentBearing, int currentX, int currentY, int targetX,
			int targetY) {
		// throw new RuntimeException("implement me!");
		int x = targetX - currentX;
		int y = targetY - currentY;
		if (x == 0 && y == 0) {
			return 0.0;
		}
		double degrees = Math.atan2(y, x) / Math.PI * 180.0;
		if (degrees < 0) {
			degrees += 360.0;
		}
		currentBearing = 90.0 - currentBearing;
		if (currentBearing < 0) {
			currentBearing += 360.0;
		}
		double angle = currentBearing - degrees;
		if (angle < 0) {
			return angle + 360.0;
		} else {
			return angle;
		}
	}

3.2.4.2 public static List calculateBearings(List xCoords, List yCoords)函数
该函数需要调用上一个函数,所以作用基本上一样.只不过是调用了List类型,对列表中每两个相邻节点调用calculateBearingToPoint函数计算并保存
在函数中循环时,由于i从0开始,而每n组向量只能得出n-1个角度,此时循环结束,所以循环结束的条件应i=n-2

public static List<Double> calculateBearings(List<Integer> xCoords, List<Integer> yCoords) {
		// throw new RuntimeException("implement me!");
		int length = xCoords.size();
		List<Double> degrees = new ArrayList<>();
		for (int i = 0; i < length; i++) {
			if (i == 0) {
				degrees.add(calculateBearingToPoint(0.0, xCoords.get(i), yCoords.get(i), xCoords.get(i + 1),
						yCoords.get(i + 1)));
			} else {
				degrees.add(calculateBearingToPoint(degrees.get(i - 1), xCoords.get(i), yCoords.get(i),
						xCoords.get(i + 1), yCoords.get(i + 1)));
			}
			if (i == length - 2) {
				break;
			}
		}
		return degrees;
	}

3.2.5 Problem 7: Convex Hulls
我在写这个问题之前,正好算法与设计的老师讲了关于凸包问题的几种解决算法,但是其他算法与本次内容无关,所以无需赘述,只讲该此实验的内容:凸包问题的边界漫游法
边界漫游发的基本思路:
1.先遍历所有的点,找到最左下角的点
2.以找到的点为基点,y轴正向为目前偏移角,开始依次找顺势针转角最小的点,记录这个点并将他加入到凸包集合中,以这次的偏向角累加上之前的角度度作为下一次的目前偏向角。
3.循环直到再次遇到最左下角为止退出
由于这部分我是参考了别人的代码,所以就不在这里贴出自己的代码了

3.2.6 Problem 8: Personal art
感觉自己想象力缺乏,画不出什么好看的

画了个类似于虫洞的东西?
需要补充说明的是,在图中所使用的随机颜色的方法如下所示,使用了Random函数

public static void drawPersonalArt(Turtle turtle, int sideLength) {
		// throw new RuntimeException("implement me!");
		for (int i = 0; i < 50; i++) {
			int rand = new Random().nextInt(PenColor.values().length);
			turtle.color(PenColor.values()[rand]);
			drawRegularPolygon(turtle, i, 30);
			turtle.turn(10);

		}
	}

3.2.7 Submitting
使用eclipse里的Team->commit->Staged Changes->Commit Message->Commit and Push即可,需要填写仓库的url和github的账号密码
3.3 Social Network
熟悉更多java中的数据结构类型,同时图作为计算机中最重要的数据结构之一,该项目需要我们掌握图的建立与遍历,以及求最短路径的方法(如上学期数据结构课中所学的Dijkstra算法和Floyed算法),只有掌握这些数据结构才算真正入门一门语言
3.3.1 设计/实现FriendshipGraph类
因为使用的是java语言,所以在写图时我想采用不同于C语言的做法,不想单纯地使用邻接表或者邻接矩阵来储存图,于是在网上寻找的时候发现有使用了hashMap的方法,于是便学习了他的方法
3.3.1.1 主要的数据结构

	public final static int maxNum = 1000;
	public ArrayList<Person> people = new ArrayList<Person>();
	public int[][] edge = new int[maxNum][maxNum];
	public HashMap<Person, Integer> hm = new HashMap<Person, Integer>();
	boolean visited[] = new boolean[maxNum];

3.3.1.2 addVertex(Person a)函数
函数使用了hashMap,判断重名的方法也很简单,就是在读取数据时与已在name里的名字比较,相同则报错并结束程序

3.3.1.3 addEdge(Person a, Personb)函数
加边也很简单,需要注意的是该问题是无向图,所以加边时需要双向加边
3.3.1.4 getDistace(Person a, Personb)函数
该函数我选择了Dijkstra算法, Dijkstra算法也算是一个十分有名的算法了,基本的思想我再贴一下:

  1. 通过Dijkstra计算图G中的最短路径时,需要指定起点s(即从顶点s开始计算)。
  2. 此外,引进两个集合S和U。S的作用是记录已求出最短路径的顶点(以及相应的最短路径长度),而U则是记录还未求出最短路径的顶点(以及该顶点到起点s的距离)。
  3. 初始时,S中只有起点s;U中是除s之外的顶点,并且U中顶点的路径是”起点s到该顶点的路径”。然后,从U中找出路径最短的顶点,并将其加入到S中;接着,更新U中的顶点和顶点对应的路径。 然后,再从U中找出路径最短的顶点,并将其加入到S中;接着,更新U中的顶点和顶点对应的路径。重复该操作,直到遍历完所有顶点。
    3.3.2 设计/实现Person类
    Person类中的内容只需要name,所以将name添加到Person类里即可
    3.3.3 设计/实现客户端代码main()
    与题目所给的main相差不多,但是为了方便之后addEdge的测试,先将edge二维数组的值全部设为不可更改的静态整形数据maxNum,之后测试时如果edge值与maxNum相同,则证明二者无边,其余与题目要求完全一致

3.3.4 设计/实现测试用例
设计test可以说是整个实验中最新、以前完全没接触过的一个东西了.在听完老师6-6的讲解之后感觉这个东西十分有效,不像以前写C语言程序的时候需要重新创建一个.c文件,将函数抽出来单独测试其正确性
在本次实验中,最主要的三个函数在3.3.1中有详细介绍,所以测试也就是针对这三个函数(尤其是最后一个算距离的函数)
在测试中,我还多添加了一个Person加以测试其正确性

3.3.4.1 addVertexTest()函数
测试点是否加入,使用的是assertTrue和.contains两个函数

	@Test
	public void addVertexTest() {
		FriendshipGraph graph = new FriendshipGraph();
		Person rachel = new Person("Rachel");
		Person ross = new Person("Ross");
		Person ben = new Person("Ben");
		Person kramer = new Person("Kramer");
		Person zxd = new Person("zxd");

		graph.addVertex(rachel);
		graph.addVertex(ross);
		graph.addVertex(ben);
		graph.addVertex(kramer);
		graph.addVertex(zxd);

		assertTrue(graph.people.contains(rachel));
		assertTrue(graph.people.contains(ross));
		assertTrue(graph.people.contains(ben));
		assertTrue(graph.people.contains(kramer));
		assertTrue(graph.people.contains(zxd));
	}

3.3.4.2 addEdgeTest()函数
之前说过,在初始化时将edge初始化为不可修改的静态数据数据manNum,在测试时只需要判断edge的值是否为1就可以知道是否有边
在测试中我新加入的zxd与ben有联系

测试使用的是assertEquals函数

	@Test
	public void addEdgeTest() {
		final int maxNum = 1000;
		FriendshipGraph graph = new FriendshipGraph();
		for (int i = 0; i < graph.edge.length; i++) {
			for (int j = 0; j < graph.edge.length; j++) {
				graph.edge[i][j] = maxNum;
			}
		}

		Person rachel = new Person("Rachel");
		Person ross = new Person("Ross");
		Person ben = new Person("Ben");
		Person kramer = new Person("Kramer");
		Person zxd = new Person("zxd");

		graph.addVertex(rachel);
		graph.addVertex(ross);
		graph.addVertex(ben);
		graph.addVertex(kramer);
		graph.addVertex(zxd);

		graph.addEdge(rachel, ross);
		graph.addEdge(ross, rachel);
		graph.addEdge(ross, ben);
		graph.addEdge(ben, ross);
		graph.addEdge(zxd, ben);
		graph.addEdge(ben, zxd);

		assertEquals(1, graph.edge[graph.hm.get(rachel)][graph.hm.get(ross)]);
		assertEquals(1, graph.edge[graph.hm.get(ross)][graph.hm.get(rachel)]);
		assertEquals(1, graph.edge[graph.hm.get(ross)][graph.hm.get(ben)]);
		assertEquals(1, graph.edge[graph.hm.get(ben)][graph.hm.get(ross)]);
		assertEquals(1, graph.edge[graph.hm.get(zxd)][graph.hm.get(ben)]);
		assertEquals(1, graph.edge[graph.hm.get(ben)][graph.hm.get(zxd)]);
		assertEquals(maxNum, graph.edge[graph.hm.get(kramer)][graph.hm.get(zxd)]);
		assertEquals(maxNum, graph.edge[graph.hm.get(kramer)][graph.hm.get(ross)]);
	}

3.3.4.3 getDistaceTest()函数
在测试中我新加入的zxd与ben有联系
在这里插入图片描述
所以从理论上来说图应该是这样的

测试使用的是assertEquals函数

@Test
	public void getDistanceTest() {
		final int maxNum = 1000;
		FriendshipGraph graph = new FriendshipGraph();
		for (int i = 0; i < graph.edge.length; i++) {
			for (int j = 0; j < graph.edge.length; j++) {
				graph.edge[i][j] = maxNum;
			}
		}

		Person rachel = new Person("Rachel");
		Person ross = new Person("Ross");
		Person ben = new Person("Ben");
		Person kramer = new Person("Kramer");
		Person zxd = new Person("zxd");

		graph.addVertex(rachel);
		graph.addVertex(ross);
		graph.addVertex(ben);
		graph.addVertex(kramer);
		graph.addVertex(zxd);

		graph.addEdge(rachel, ross);
		graph.addEdge(ross, rachel);
		graph.addEdge(ross, ben);
		graph.addEdge(ben, ross);
		graph.addEdge(zxd, ben);
		graph.addEdge(ben, zxd);

		assertEquals(-1, graph.getDistance(zxd, kramer));
		assertEquals(0, graph.getDistance(zxd, zxd));
		assertEquals(1, graph.getDistance(zxd, ben));
		assertEquals(2, graph.getDistance(zxd, ross));
		assertEquals(3, graph.getDistance(zxd, rachel));
	}

测试距离也就比main中多了一个3,其余的与main函数实际上相差不大
4 实验进度记录
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期 时间段 任务 实际完成情况
2020-03-05 10:00-11:30 编写问题1的isLegalMagicSquare函数中的读入文件部分 遇到困难,未完成
2020-03-05 14:00-16:40 编写问题1的isLegalMagicSquare函数中的读入文件部分 预期完成任务
2020-03-05 19:00-20:00 编写问题1的isLegalMagicSquare函数的其余部分 预期完成任务
2020-03-05 20:30-21:30 给问题1中的generateMagicSquare函数添加注释,画出流程图,并添加部分代码增加其鲁棒性,最终添加代码使其生成的幻方输出保存到6.txt里 预期完成任务
2020-03-08 10:30-12:00 编写问题2的drawSquare函数,
calculateRegularPolygonAngle函数,
calculatePolygonSidesFromAngle函数,
drawRegularPolygon函数 预期完成任务
2020-03-09 19:50-21:30 编写问题2的calculateBearingToPoint函数,
calculateBearings函数,
drawPersonnalArt函数 预期完成任务
2020-03-10 19:50-21:30 编写问题2的convexHull函数 延迟1个半小时完成
2020-03-11 14:20-16:40 编写问题3的addVertex函数,
addEdge函数,getDistance函数,
Person类函数,getName函数 延迟半个小时完成
2020-03-11 10:50-17:00 编写完成实验报告,修补完善代码中的一些小缺陷 预期完成任务
5 实验过程中遇到的困难与解决途径
遇到的难点 解决途径
不知道怎么读入文件并将文件保存至二维数组中
百度各种方法,询问了同学,在很多方法中自己总结出一套比较适合问题1的读入方法,并在博客中记录了这个方法,博客地址在前文中已经贴过
不知道问题2需要我干什么 在询问同学和仔细阅读题目要求后,明白了题目要求,码起代码来十分快速
凸包问题难以解决 算法设计课上老师在讲分治算法的时候提到了凸包问题,介绍了诸如蛮力算法,枚举算法,边界漫游法和Graham-Scan(Q)算法,都是很好的能解决凸包问题的算法
6 实验过程中收获的经验、教训、感想
6.1 实验过程中收获的经验和教训
本来以为自己在假期里自学了java,对java有一定的了解,老师也说java”不用太学”,所以麻痹大意了.其实这次试验之所以在3月5号才开始写代码,原因就是我仅仅掌握到数组,字符串和函数这部分,没有明白java作为面向对象编程的语言其核心所在,漏掉了一些java自带的数据结构的学习,导致本次实验刚开始时无从下手,只能从头开始学习java语言.但不得不说java语言相比于C语言来说不知道高到哪里去了.上学期学的数据结构中如此复杂的数据结构,java都能轻松简单地完成,全是java库里自带的,不需要像C一样自己构建数据结构还要写对其的操作函数.在本次实验中也学习到了很多java语言的知识
6.2 针对以下方面的感受
(1) Java编程语言是否对你的口味?
很不错,比起C来说能用的东西多得多
(2) 关于Eclipse IDE
之前就接触过,Eclipse作为IDE来说改错能力很强,比原来C用的DevC++,CodeBlocks等要好用得多,但感觉比不上VS2019
(3) 关于Git和GitHub
Git之前从未接触过,感觉这种能在本地构建仓库更新版本很cool
Github之前就听说过名气,但是也仅止于创建了一个账号.所以这门课让我们接触Github时十分有必要的,是我们迈向工程师的一大步
(4) 关于CMU和MIT的作业
作业难度可以接受,但是全英文看起来比较困难,而机翻又比较僵硬,所以有的时候会出现明明问题很简单,但是描述太拗口导致没能准确明白问题的要求
(5) 关于本实验的工作量、难度、deadline
个人认为工作量还是挺大的
难度的话我作为Java新手,个人感觉难度还是挺高的
Deadline:个人觉得ddl给的蛮合理的,虽然我从第二周周三才开始写代码,但是第三周周四的时候已经完成了报告,但是原因可能是周四一整天都没课,如果到了以后周四有课了可能效率会降低
(6) 关于初接触“软件构造”课程
虽然是第一次接触软件构造这门课,但是在大一年度项目的时候,我和组员的项目就是一个软件,当时虽然有了一点点关于软件的了解,但是很外行.希望这门课能让我成为一名合格的软件工程师

.
.
.

写在源代码之前
我相信每一位HIT的同学在学习这门课程的时候多少会有些迷茫,尤其是像我这种本身就学的不是很好的人来说,假期里学习java也不是很认真,一上来就要写这些程序会感到很迷茫
所以我提供源代码的原因的就是为了让大家度过这段迷茫期,但绝不是为大家提供一个廉价的方式获得代码来完成实验,我希望大家能够认真地看前面的我的实验报告或者其他人的实验报告,再根据我提供的源代码快速准确地完成任务,并且能够熟悉java语言的运用,快速地上手实验,不至于到了lab2甚至lab3还不怎么会使用Java语言完成实验

//P1,MagicSquare

package P1;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Scanner;

public class MagicSquare {
	public static void main(String[] args) {
		MagicSquare ms = new MagicSquare();
		for (int i = 1; i < 6; i++) { // 输出最后结果
			boolean check = ms.isLegalMagicSquare(i + ".txt");
			System.out.println("test for " + i + ".txt:" + check);
		}
		
		// ms.generateMagicSquare(in.nextInt());
		while (true) {
			System.out.println("Please enter a parameter:");
			Scanner in = new Scanner(System.in);
			boolean check1 = generateMagicSquare(in.nextInt());
			if (check1 == true) {
				boolean check2 = ms.isLegalMagicSquare("6.txt");
				System.out.println("test 6.txt:" + check2);
				in.close();
				break;
			} else {
				break;
			}
		}

	}

	public boolean isLegalMagicSquare(String fileName) {
		/*
		 * 文件读入
		 */
		String filePath = "src/P1/txt/" + fileName;
		FileReader in = null;
		try {
			in = new FileReader(filePath);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}

		/*
		 * 使用BufferedReader读入并保存为二维数组
		 */
		BufferedReader br = new BufferedReader(in);
		ArrayList<String> list = new ArrayList<String>();
		String s = null;

		while (true) {
			try {
				s = br.readLine();
			} catch (IOException e) {
				e.printStackTrace();
			}
			list.add(s);
			if (s == null)
				break;
		}

		/*
		 * 创建矩阵
		 */
		int colLength; // 列数
		int rowLength; // 行数
		rowLength = list.size() - 1;
		String num0 = list.get(0);
		String[] line0 = num0.split("\t");
		colLength = line0.length;
		int[][] square = new int[rowLength][colLength]; // 幻方

		/*
		 * 将数据写入二维数组
		 */
		try {
			for (int i = 0; i < rowLength; i++) {
				String numx = list.get(i);
				String[] linex = numx.split("\t");
				for (int j = 0; j < colLength; j++) {
					if (Integer.valueOf(linex[j]) <= 0) { // 判断是否有输入为非正整数或未用'\t'分隔
						System.out.println("Non positive integer or input format error!");
						return false;
					} else
						square[i][j] = Integer.valueOf(linex[j]);
				}
			}
		} catch (Exception e) { // 判断是否为矩阵
			System.out.println("Not a matrix!");
			return false;
		}

		/*
		 * 读入完毕 判断是否是幻方
		 */
		int n = rowLength; // 由于幻方为矩阵,用n简化代码
		int sum0 = 0; // 第一行的和,以其作为参照物
		for (int i = 0; i < n; i++) {
			sum0 += square[0][i];
		}

		/*
		 * 判断每一行之和是否与sum0相等
		 */
		int colSum = 0;
		for (int i = 0; i < n; i++) {

			for (int j = 0; j < n; j++) {
				colSum += square[i][j];
			}
			if (colSum != sum0) {
				return false;
			}
			colSum = 0;
		}

		/*
		 * 判断每一列之和是否与sum0相等
		 */
		int rowSum = 0;
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				rowSum += square[j][i];
			}
			if (rowSum != sum0) {
				return false;
			}
			rowSum = 0;
		}

		/*
		 * 判断对角之和是否与sum0相等
		 */
		int diagSum1 = 0;
		int diagSum2 = 0;
		int i, j;
		for (i = 0, j = 0; i < n; i++, j++) {
			diagSum1 += square[i][j];
			diagSum2 += square[i][n - j - 1];
		}
		if (diagSum1 != sum0) {
			return false;
		}
		if (diagSum2 != sum0) {
			return false;
		} else {
			return true;
		}
	}

	public static boolean generateMagicSquare(int n) {
		File file = new File("src/P1/txt/6.txt");
		if ((n % 2 == 0) || n <= 0) { // 判断参数n是否不符合输入要求
			System.out.println(false); // 不符合要求提示用户输入错误
			return false; // 不符合要求返回false
		}
		int magic[][] = new int[n][n]; // 创建矩阵
		int row = 0, col = n / 2, i, j, square = n * n;
		for (i = 1; i <= square; i++) {
			magic[row][col] = i; // 每次赋值一个矩阵元素并计算下一个位置
			if (i % n == 0) // 计算下一个位置
				row++; // 如果放置完一个右下角元素,开始下一个
			else {
				if (row == 0) // 元素位置来到第一行,则返回最后一行
					row = n - 1;
				else
					row--; // 否则行数-1
				if (col == (n - 1)) // 元素位置来到最后一列,则返回第一列
					col = 0;
				else
					col++; // 否则列数+1
			}
		}

		// 文件输出,将生成的矩阵输出到6.txt中
		try {
			BufferedWriter out = new BufferedWriter(new FileWriter(file)); // 使用FileOutputStream输出
			for (i = 0; i < n; i++) {
				for (j = 0; j < n; j++) {
					String line = String.valueOf(magic[i][j]);
					out.write(line);
					out.write('\t');
				}
				out.newLine(); // 每次输出完一行在文件中换行
			}
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return true;
	}

}

//P2,只需要关注TurtleSoup这个class即可

package P2.turtle;

import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.ArrayList;
import java.util.HashSet;

public class TurtleSoup {

	/**
	 * Draw a square.
	 * 
	 * @param turtle     the turtle context
	 * @param sideLength length of each side
	 */
	public static void drawSquare(Turtle turtle, int sideLength) {
		// throw new RuntimeException("implement me!");
		for (int i = 0; i < 4; i++) {
			turtle.forward(sideLength);
			turtle.turn(90);
		}
	}

	/**
	 * Determine inside angles of a regular polygon.
	 * 
	 * There is a simple formula for calculating the inside angles of a polygon; you
	 * should derive it and use it here.
	 * 
	 * @param sides number of sides, where sides must be > 2
	 * @return angle in degrees, where 0 <= angle < 360
	 */
	public static double calculateRegularPolygonAngle(int sides) {
		// throw new RuntimeException("implement me!");
		return ((double) (sides - 2) * 180.0 / sides);
	}

	/**
	 * Determine number of sides given the size of interior angles of a regular
	 * polygon.
	 * 
	 * There is a simple formula for this; you should derive it and use it here.
	 * Make sure you *properly round* the answer before you return it (see
	 * java.lang.Math). HINT: it is easier if you think about the exterior angles.
	 * 
	 * @param angle size of interior angles in degrees, where 0 < angle < 180
	 * @return the integer number of sides
	 */
	public static int calculatePolygonSidesFromAngle(double angle) {
		// throw new RuntimeException("implement me!");
		return ((int) Math.round((360.0 / (180.0 - angle))));
	}

	/**
	 * Given the number of sides, draw a regular polygon.
	 * 
	 * (0,0) is the lower-left corner of the polygon; use only right-hand turns to
	 * draw.
	 * 
	 * @param turtle     the turtle context
	 * @param sides      number of sides of the polygon to draw
	 * @param sideLength length of each side
	 */
	public static void drawRegularPolygon(Turtle turtle, int sides, int sideLength) {
		// throw new RuntimeException("implement me!");
		double angle = 180 - calculateRegularPolygonAngle(sides);
		for (int i = 0; i < sides; i++) {
			turtle.forward(sideLength);
			turtle.turn(angle);
		}
	}

	/**
	 * Given the current direction, current location, and a target location,
	 * calculate the Bearing towards the target point.
	 * 
	 * The return value is the angle input to turn() that would point the turtle in
	 * the direction of the target point (targetX,targetY), given that the turtle is
	 * already at the point (currentX,currentY) and is facing at angle
	 * currentBearing. The angle must be expressed in degrees, where 0 <= angle <
	 * 360.
	 *
	 * HINT: look at http://en.wikipedia.org/wiki/Atan2 and Java's math libraries
	 * 
	 * @param currentBearing current direction as clockwise from north
	 * @param currentX       current location x-coordinate
	 * @param currentY       current location y-coordinate
	 * @param targetX        target point x-coordinate
	 * @param targetY        target point y-coordinate
	 * @return adjustment to Bearing (right turn amount) to get to target point,
	 *         must be 0 <= angle < 360
	 */

	public static double calculateBearingToPoint(double currentBearing, int currentX, int currentY, int targetX,
			int targetY) {
		// throw new RuntimeException("implement me!");
		int x = targetX - currentX;
		int y = targetY - currentY;
		if (x == 0 && y == 0) {
			return 0.0;
		}
		double degrees = Math.atan2(y, x) / Math.PI * 180.0;
		if (degrees < 0) {
			degrees += 360.0;
		}
		currentBearing = 90.0 - currentBearing;
		if (currentBearing < 0) {
			currentBearing += 360.0;
		}
		double angle = currentBearing - degrees;
		if (angle < 0) {
			return angle + 360.0;
		} else {
			return angle;
		}
	}

	/**
	 * Given a sequence of points, calculate the Bearing adjustments needed to get
	 * from each point to the next.
	 * 
	 * Assumes that the turtle starts at the first point given, facing up (i.e. 0
	 * degrees). For each subsequent point, assumes that the turtle is still facing
	 * in the direction it was facing when it moved to the previous point. You
	 * should use calculateBearingToPoint() to implement this function.
	 * 
	 * @param xCoords list of x-coordinates (must be same length as yCoords)
	 * @param yCoords list of y-coordinates (must be same length as xCoords)
	 * @return list of Bearing adjustments between points, of size 0 if (# of
	 *         points) == 0, otherwise of size (# of points) - 1
	 */
	public static List<Double> calculateBearings(List<Integer> xCoords, List<Integer> yCoords) {
		// throw new RuntimeException("implement me!");
		int length = xCoords.size();
		List<Double> degrees = new ArrayList<>();
		for (int i = 0; i < length; i++) {
			if (i == 0) {
				degrees.add(calculateBearingToPoint(0.0, xCoords.get(i), yCoords.get(i), xCoords.get(i + 1),
						yCoords.get(i + 1)));
			} else {
				degrees.add(calculateBearingToPoint(degrees.get(i - 1), xCoords.get(i), yCoords.get(i),
						xCoords.get(i + 1), yCoords.get(i + 1)));
			}
			if (i == length - 2) {
				break;
			}
		}
		return degrees;
	}

	/**
	 * Given a set of points, compute the convex hull, the smallest convex set that
	 * contains all the points in a set of input points. The gift-wrapping algorithm
	 * is one simple approach to this problem, and there are other algorithms too.
	 * 
	 * @param points a set of points with xCoords and yCoords. It might be empty,
	 *               contain only 1 point, two points or more.
	 * @return minimal subset of the input points that form the vertices of the
	 *         perimeter of the convex hull
	 */
	public static Set<Point> convexHull(Set<Point> points) {
		// throw new RuntimeException("implement me!");
		Set<Point> shellPoint = new HashSet<Point>();
		Point minPoint = null;
		double preBearing, nowBearing, nextBearing;
		double nextLength;
		Point nowPoint, nextPoint = null;
		if (!points.isEmpty()) {
			if (points.size() <= 3) {
				return points;
			}
			for (Point point : points) {
				if (minPoint == null) {
					minPoint = point;
					continue;
				}
				if (minPoint.x() > point.x()) {
					minPoint = point;
				} else {
					if (point.y() < minPoint.y()) {
						minPoint = point;
					}
				}
			}
			shellPoint.add(minPoint);
			nowPoint = minPoint;
			preBearing = 0;
			for (;;) {
				nextBearing = 360.0;
				nextLength = Double.MAX_VALUE;
				for (Point point : points) {
					if (point.equals(nowPoint)) {
						continue;
					}
					nowBearing = calculateBearingToPoint(preBearing, (int) nowPoint.x(), (int) nowPoint.y(),
							(int) point.x(), (int) point.y());
					if (nextBearing == nowBearing) {
						if (nextLength < (Math.pow(point.x() - nowPoint.x(), 2)
								+ Math.pow(point.y() - nowPoint.y(), 2))) {
							nextLength = Math.pow(point.x() - nowPoint.x(), 2) + Math.pow(point.y() - nowPoint.y(), 2);
							nextPoint = point;
						}
					} else if (nextBearing > nowBearing) {
						nextLength = Math.pow(point.x() - nowPoint.x(), 2) + Math.pow(point.y() - nowPoint.y(), 2);
						nextBearing = nowBearing;
						nextPoint = point;
					}
				}
				if (minPoint.equals(nextPoint)) {
					break;
				}
				nowPoint = nextPoint;
				preBearing += nextBearing;
				shellPoint.add(nextPoint);
			}

		}
		return shellPoint;
	}

	/**
	 * Draw your personal, custom art.
	 * 
	 * Many interesting images can be drawn using the simple implementation of a
	 * turtle. For this function, draw something interesting; the complexity can be
	 * as little or as much as you want.
	 * 
	 * @param turtle the turtle context
	 */
	public static void drawPersonalArt(Turtle turtle, int sideLength) {
		// throw new RuntimeException("implement me!");
		for (int i = 0; i < 50; i++) {
			int rand = new Random().nextInt(PenColor.values().length);
			turtle.color(PenColor.values()[rand]);
			drawRegularPolygon(turtle, i, 30);
			turtle.turn(10);

		}
	}

	/**
	 * Main method.
	 * 
	 * This is the method that runs when you run "java TurtleSoup".
	 * 
	 * @param args unused
	 */
	public static void main(String args[]) {
		DrawableTurtle turtle = new DrawableTurtle();

		drawPersonalArt(turtle, 100);

		// draw the window
		turtle.draw();
	}

}

// P3,以下代码为FriendshipGraph这个类

package P3;

import java.util.ArrayList;
import java.util.HashMap;

public class FriendshipGraph {
	public final static int maxNum = 1000;
	public ArrayList<Person> people = new ArrayList<Person>();
	public int[][] edge = new int[maxNum][maxNum];
	public HashMap<Person, Integer> hm = new HashMap<Person, Integer>();
	boolean visited[] = new boolean[maxNum];

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		FriendshipGraph graph = new FriendshipGraph();
		for (int i = 0; i < graph.edge.length; i++) {
			for (int j = 0; j < graph.edge.length; j++) {
				graph.edge[i][j] = maxNum;
			}
		}
		Person rachel = new Person("Rachel");
		Person ross = new Person("Ross");
		Person ben = new Person("Ben");
		Person kramer = new Person("Kramer");
		graph.addVertex(rachel);
		graph.addVertex(ross);
		graph.addVertex(ben);
		graph.addVertex(kramer);
		graph.addEdge(rachel, ross);
		graph.addEdge(ross, rachel);
		graph.addEdge(ross, ben);
		graph.addEdge(ben, ross);
		System.out.println(graph.getDistance(rachel, ross));
		// should print 1
		System.out.println(graph.getDistance(rachel, ben));
		// should print 2
		System.out.println(graph.getDistance(rachel, rachel));
		// should print 0
		System.out.println(graph.getDistance(rachel, kramer));
		// should print -1
	}

	public void addVertex(Person a) {
		for (Person p : people) {
			if (p.getName() == a.getName()) {	//判断是否重名
				System.out.println("Same name!");
				System.exit(0);	//重名退出
			}
		}
		people.add(a);
		hm.put(a, people.indexOf(a));
	}

	public void addEdge(Person a, Person b) {
		int x, y;
		x = hm.get(a);
		y = hm.get(b);
		edge[x][y] = 1;
	}

	public int getDistance(Person a, Person b) {
		if (a.equals(b)) {
			return 0;
		}
		int sum, temp, w;
		int x = hm.get(a);
		int y = people.size();
		int[] cost = new int[maxNum];
		for (int i = 0; i < y; i++) {
			cost[i] = edge[x][i];
			visited[i] = false;
		}
		visited[hm.get(a)] = true;
		for (int i = 0; i < y - 1; i++) {
			w = 0;
			temp = 100;
			for (int j = 0; j < y; j++) {
				if (!visited[j] && cost[j] < temp) {
					temp = cost[j];
					w = j;
				}
			}
			visited[w] = true;
			for (int k = 0; k < y; k++) {
				if (visited[k] != true) {
					sum = cost[w] + edge[w][k];
					if (sum < cost[k]) {
						cost[k] = sum;
					}
				}
			}
		}
		if (cost[hm.get(b)] == maxNum) {
			return -1;
		} else {
			return cost[hm.get(b)];
		}
	}

	
}

// P3,以下代码为Person这个类

package P3;

public class Person {
	private String name;

	public Person(String name) {
		this.name = name;
	}
	
	public String getName() {
		return this.name;
	}
}

//P3的test

package P3;

import static org.junit.Assert.*;
import org.junit.Test;

import P3.FriendshipGraph;
import P3.Person;

public class FriendshipGraphTest {
	public final static int maxNum = 1000;

	@Test
	public void addVertexTest() {
		FriendshipGraph graph = new FriendshipGraph();
		Person rachel = new Person("Rachel");
		Person ross = new Person("Ross");
		Person ben = new Person("Ben");
		Person kramer = new Person("Kramer");
		Person zxd = new Person("zxd");

		graph.addVertex(rachel);
		graph.addVertex(ross);
		graph.addVertex(ben);
		graph.addVertex(kramer);
		graph.addVertex(zxd);

		assertTrue(graph.people.contains(rachel));
		assertTrue(graph.people.contains(ross));
		assertTrue(graph.people.contains(ben));
		assertTrue(graph.people.contains(kramer));
		assertTrue(graph.people.contains(zxd));
	}

	@Test
	public void addEdgeTest() {
		final int maxNum = 1000;
		FriendshipGraph graph = new FriendshipGraph();
		for (int i = 0; i < graph.edge.length; i++) {
			for (int j = 0; j < graph.edge.length; j++) {
				graph.edge[i][j] = maxNum;
			}
		}

		Person rachel = new Person("Rachel");
		Person ross = new Person("Ross");
		Person ben = new Person("Ben");
		Person kramer = new Person("Kramer");
		Person zxd = new Person("zxd");

		graph.addVertex(rachel);
		graph.addVertex(ross);
		graph.addVertex(ben);
		graph.addVertex(kramer);
		graph.addVertex(zxd);

		graph.addEdge(rachel, ross);
		graph.addEdge(ross, rachel);
		graph.addEdge(ross, ben);
		graph.addEdge(ben, ross);
		graph.addEdge(zxd, ben);
		graph.addEdge(ben, zxd);

		assertEquals(1, graph.edge[graph.hm.get(rachel)][graph.hm.get(ross)]);
		assertEquals(1, graph.edge[graph.hm.get(ross)][graph.hm.get(rachel)]);
		assertEquals(1, graph.edge[graph.hm.get(ross)][graph.hm.get(ben)]);
		assertEquals(1, graph.edge[graph.hm.get(ben)][graph.hm.get(ross)]);
		assertEquals(1, graph.edge[graph.hm.get(zxd)][graph.hm.get(ben)]);
		assertEquals(1, graph.edge[graph.hm.get(ben)][graph.hm.get(zxd)]);
		assertEquals(maxNum, graph.edge[graph.hm.get(kramer)][graph.hm.get(zxd)]);
		assertEquals(maxNum, graph.edge[graph.hm.get(kramer)][graph.hm.get(ross)]);
	}

	@Test
	public void getDistanceTest() {
		final int maxNum = 1000;
		FriendshipGraph graph = new FriendshipGraph();
		for (int i = 0; i < graph.edge.length; i++) {
			for (int j = 0; j < graph.edge.length; j++) {
				graph.edge[i][j] = maxNum;
			}
		}

		Person rachel = new Person("Rachel");
		Person ross = new Person("Ross");
		Person ben = new Person("Ben");
		Person kramer = new Person("Kramer");
		Person zxd = new Person("zxd");

		graph.addVertex(rachel);
		graph.addVertex(ross);
		graph.addVertex(ben);
		graph.addVertex(kramer);
		graph.addVertex(zxd);

		graph.addEdge(rachel, ross);
		graph.addEdge(ross, rachel);
		graph.addEdge(ross, ben);
		graph.addEdge(ben, ross);
		graph.addEdge(zxd, ben);
		graph.addEdge(ben, zxd);

		assertEquals(-1, graph.getDistance(zxd, kramer));
		assertEquals(0, graph.getDistance(zxd, zxd));
		assertEquals(1, graph.getDistance(zxd, ben));
		assertEquals(2, graph.getDistance(zxd, ross));
		assertEquals(3, graph.getDistance(zxd, rachel));
	}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值