前言
其实写这篇的时候我刚lab2做完了,当时写完lab1休息了几天又开始了lab2,相比较下来lab1简直是。。。怎么说呢,工作量少很多吧。不过还是有不少的感悟可以说一下的。
Magic Squares
总的来说幻方这个任务不太难,java有很多很方便的方法,相比较c语言,简直是省了一大笔劲。
难点:读取字符串然后解析成数字。
我们根据spec,要从文本中读入内容,java中读取文本你的方法有很多,我这里使用的是BufferedReader ,并且每次只读入一行内容,所以可以用个循环来一直读,每读一次,都要对字符串进行解析,将其转化为数字存储在数组中。我在其中也有加入一些判断,判断是否满足幻方,是否存在非数字输入。
while ((str = bufferredReader.readLine()) != null) {
int s = 0;
row += 1;
String[] array = str.split("\t");
for (int i = 0; i < array.length; i++) {
if (!array[i].matches("[0-9]+") || array[i].equals("0")) {
if (array[i].contains(" ")) {
System.out.println("有不是用“\\t”分隔的数字" + array[i]);
bufferredReader.close();
fileReader.close();
return false;
}
System.out.println("there is a illegal input: 有不是大于0的非法输入" + array[i]);
bufferredReader.close();
fileReader.close();
return false;
}
}
int[] iarray = new int[array.length];
for (String strs : array) {
if (strs != null)
iarray[s++] = Integer.parseInt(strs);
}
list.add(iarray);
if (row > 1) {
if (list.get(row - 1).length != list.get(row - 2).length) {
System.out.println("this is not a square!不是正方形");
bufferredReader.close();
fileReader.close();
return false;
}
}
}
这个将字符串转成数字的方法还是特别好用了,以后会经常用到。
Turtle Graphics
P2就比P1有意思多了,内容上比幻方问题好有趣,要求实现画图功能。
前几个函数其实都是中学的数学题,实现几个简单的计算就可以完成。根据方法的spec编写代码。
public static void drawSquare(Turtle turtle, int sideLength) {
for ( int i = 0 ; i < 4 ; i++ ) {
turtle.forward(sideLength);
turtle.turn(90);
}
}
public static double calculateRegularPolygonAngle(int sides) {
if ( sides > 2) {
double angle =(double) (sides-2)*180 / sides ;
return angle ;
}
else
throw new IllegalArgumentException("sides is illegal");
}
public static int calculatePolygonSidesFromAngle(double angle) {
if ( angle > 0 && angle < 180 ) {
int sides = (int)Math.round((360 / (180 - angle)));
return sides ;
}
else
throw new IllegalArgumentException("angle is illegal");
}
public static void drawRegularPolygon(Turtle turtle, int sides, int sideLength) {
double angle = calculateRegularPolygonAngle(sides);
for ( int i = 0 ; i < sides ; i++ ) {
turtle.forward(sideLength);
turtle.turn(180-angle);
}
}
public static double calculateBearingToPoint(double currentBearing, int currentX, int currentY,
int targetX, int targetY) {
double degree = Math.toDegrees(Math.atan2((targetY - currentY),(targetX - currentX)));
double angle = (90-degree)-currentBearing ;
if ( angle < 0 )
angle = angle + 360 ;
return angle ;
}
public static List<Double> calculateBearings(List<Integer> xCoords, List<Integer> yCoords) {
List<Double> turn_degree = new ArrayList<Double>();
for ( int i = 0 ; i < xCoords.size()-1 ; i++ ) {
if ( i == 0 ) {
turn_degree.add(calculateBearingToPoint(0,xCoords.get(i),yCoords.get(i),xCoords.get(i+1),yCoords.get(i+1)));
}
else
turn_degree.add(calculateBearingToPoint(turn_degree.get(i-1),xCoords.get(i),yCoords.get(i),xCoords.get(i+1),yCoords.get(i+1)));
}
return turn_degree ;
}
难点:凸包问题
凸包问题,我在做这个实验前也不知道什么是凸包问题,想了解的小伙伴们可以去问问度娘,大概就是找一个点集,这个点集里面的点的连线能够把整个图的所有点都包括在内。在优快云上有很多博客都描述了这个问题的解法,我记得我当时也看了好几种,其中最好理解的一种就是边界漫游法,很通俗易懂。
边界漫游法,也就是通过计算找到最外围的一个点,以这个点为起点,找和它夹角最大的点,这个点就是要找的凸包的点,若有相同的情况就判断这两点到这一点的距离,远的那个加入到凸包的点集中,再以这个点为新起点,重复上述操作。当回到一开始的起点的时候,也就结束了,找全了。这就是为什么前面要实现计算Bearing方法的原因了,都是为了求凸包做准备。代码是我自己写的,仅做参考:
public static Set<Point> convexHull(Set<Point> points) {
Set<Point> convexHullPoint = new HashSet<Point>();
Point startPoint = null ;
double currentBearing ;
double nextBearing ;
double Bearing ;
double nextLength ;
Point currentPoint ;
Point nextPoint = null ;
if ( points.isEmpty())
return points ;
if ( points.size() < 4 )
return points ;
for ( Point point : points ) {
if ( startPoint == null ) {
startPoint = point ;
continue ;
}
if ( startPoint.x() > point.x() )
startPoint = point ;
else if ( startPoint.x() == point.x()) {
if ( startPoint.y() > point.y() )
startPoint = point ;
}
}
convexHullPoint.add(startPoint);
currentPoint = startPoint;
Bearing = 0 ;
double Length ;
while (true) {
nextBearing = 360 ;
nextLength = Double.MAX_VALUE ;
for ( Point point : points ) {
if ( point.equals(currentPoint))
continue ;
currentBearing = calculateBearingToPoint(Bearing ,(int)currentPoint.x(),(int)currentPoint.y(),(int)point.x(),(int)point.y());
Length = Math.pow(point.x()-currentPoint.x(),2)+Math.pow(point.y()-currentPoint.y(),2);
if ( nextBearing == currentBearing && nextLength < Length) {
nextLength = Length ;
nextPoint = point ;
}
else if ( nextBearing > currentBearing ) {
nextLength = Length ;
nextPoint = point ;
nextBearing = currentBearing ;
}
}
if ( startPoint.equals(nextPoint) )
break ;
currentPoint = nextPoint ;
Bearing += nextBearing ;
convexHullPoint.add(nextPoint);
}
return convexHullPoint ;
}
FriendshipGraph
FriendshipGraph这个实验的整体难度还是比较低的,但是有一个地方我觉得我做的不太好,就是getdistance这个方法,我当时一直在想用广搜吧,嗯,那就用队列,嗯,但是没想到怎么计算广搜层数的问题,因为不知道该在循环的哪个地方加,然后我就去用dj算法了。直到我做到了实验二,还要实现这个玩意,我觉得还是得想一下的,最后我发现其实用Map特别好实现,之前想的其他方案都觉得太复杂太捞了,就没使。话不多说,看代码,主体循环应该就10行左右。不过这里我放的是实验二的代码,在实验一中将判断条件改改就行。
Set<Person> visit = new HashSet<>();
//类似一个数组一样的功能,存每个点到起点的距离。
Map<Person, Integer> distance = new HashMap<>();
//初始化这个Map,-1表示没有相邻。
for (Person src : this.vertices) {
distance.put(src, -1);
}
//利用队列,使用广度优先搜索,一层层的遍历整个图
Queue<Person> queue = new LinkedList<>();
//自身入队,visit表示已经入队了的点集
queue.add(source);
visit.add(source);
while (!queue.isEmpty()) {
Person src = queue.poll();
for (Person srt : this.personGraph.targets(src).keySet()) {
if ( !visit.contains(srt)) {
queue.add(srt);
visit.add(srt);
int i = distance.get(src);
//i表示src距离源点的距离,srt是src的邻接点,所以srt到源点的距离等于src的距离加上它两间的权值
distance.put(srt, i + personGraph.targets(src).get(srt));
}
}
}
总结
整体来讲,对于我这种现学现卖的学生来说,实验一更像是一种尝试,缓冲。我通过实验一也是了解了java的很多语法,但还有很多东西要学,要知道一本java核心技术基础知识有600多页。。。还只是卷一,又是一本深入理解计算机系统。。。正是有了这次实验的基础,我才敢在实验二中做更多的尝试,包括想着用更优的算法。特别是Person类,一个person有的属性太多了,又怎么会是只有姓名,朋友呢,还有很多属性可以去扩展,构建,和其他事物进行联系,不然怎么能叫ADT呢?