目录
完成Magic Squares、Turtle Graphics和Social Network三个任务。
一、Magic Squares
实现两个方法,isLegalMagicSquare用于判断是否是幻方,generateMagicSquare用于生成行列数为奇数的幻方。
1.isLegalMagicSquare
从文件中读取数据判断是否为Magic Square,并检查输入是否合法。
这个方法的声明
public static boolean isLegalMagicSquare(String fileName)
实现过程分为三步:文件读入、合法性检查和判断是否为幻方。
文件读入:定义一个ArrayList并按行读取字符串保存在这个ArrayList中。
ArrayList<String> list = new ArrayList<String>();
String path = "src/P1/txt/" + fileName;
try
{
BufferedReader br = new BufferedReader(new FileReader(path));
String line = "";
while ((line = br.readLine()) != null)
{
list.add(line);
}
br.close();
}
catch (IOException e)
{
e.printStackTrace();
return false;
}
合法性检查:检查行列数是否相等、是否用\t分割和是否所有元素都是正整数。
int row;//行
int column;//列
row = list.size();
String f0 = list.get(0);
String[] l0 = f0.split("\t");
column = l0.length;
if(row!=column)
{
System.out.print("rows and columns are not equal ");
return false;
}
int[][] matrix = new int[row][column];
try
{
for(int i=0;i<row;i++)
{
String fn = list.get(i);
String[] ln = fn.split("\t");
for(int j =0;j<column;j++)
{
if(Integer.valueOf(ln[j])<=0)
{
System.out.print("Contains non-positive integers ");
return false;
}
if(ln.length!=column)
{
System.out.print("Not a \\t split matrix ");
return false;
}
matrix[i][j] = Integer.valueOf(ln[j]);
}
}
}
catch(Exception e)
{
System.out.print("Not a \\t split matrix");
return false;
}
判断是否为幻方:计算二维数组的各行各列和两个对角线的和是否相等。
int sumr[] = new int[row];
int sumc[] = new int[column];
int sumdia[] = new int[2];
for(int i=0;i<row;i++)
{
sumdia[0]+=matrix[i][i];
sumdia[1]+=matrix[i][row-1-i];
for(int j=0;j<row;j++)
{
sumr[i]+=matrix[i][j];
sumc[i]+=matrix[j][i];
}
}
int x=sumr[0];
if(sumr[0]!=x||sumdia[0]!=x||sumdia[1]!=x)
{
return false;
}
for(int i=1;i<row-1;i++)
{
if((sumr[i]!=x)||(sumc[i]!=x)||(sumdia[0]!=x)||(sumdia[1]!=x))
{
return false;
}
}
return true;
2.generateMagicSquare
输入一个奇数n生成一个n*n的Magic Square并写入到文件中,并检查输入是否合法。
异常分析:
如果输入的 n 为偶数,函数运行之后在控制台产生以下输出:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 12
at MagicSquare.generateMagicSquare(MagicSquare.java:17)
at MagicSquare.main(MagicSquare.java:121)
原因:数组越界
如果输入的 n 为负数,函数运行之后在控制台产生以下输出:
Exception in thread "main" java.lang.NegativeArraySizeException
at MagicSquare.generateMagicSquare(MagicSquare.java:11)
at MagicSquare.main(MagicSquare.java:121)
原因:数组长度为负
原理分析:采用了罗伯法生成幻方
数字 1 放在首行最中间的格子中。向右上角斜行,依次填入数字。如果右上方向出了上边界,就以出框后的虚拟方格位置为基准,将数字竖直降落至底行对应的格子中。同上,向右出了边界,就以出框后的虚拟方格位置为基准,将数字平移至最左列对应的格子中。如果数字{N} 右上的格子已被其它数字占领,就将{N+1} 填写在{N}下面的格子中。如果朝右上角出界,和“重复”的情况做同样处理。
实现思路:
这个方法的声明
public static boolean generateMagicSquare(int n)
实现过程分为三步:合法性检查、生成幻方和写入文件。
合法性检查
if(n%2==0)
{
System.out.println("n为偶数");
return false;
}
else if(n<0)
{
System.out.println("n为负数");
return 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)//每n个为一组
row++;//进行下一组的赋值
else
{
if (row == 0)//第0行赋值后进行最后一行的赋值
row = n - 1;
else
row--;//按行递减赋值
if (col == (n - 1))//最后一列赋值后进行第0列的赋值
col = 0;
else
col++;//按列递增赋值
}
}
写入文件
FileWriter out = null;
try {
out = new FileWriter("src/P1/txt/6.txt");//二维数组按行存入到文件中
for (int u = 0; u < magic.length; u++)
{
for (int v = 0; v < magic.length; v++)
{
String content = String.valueOf(magic[u][v]) + "";//将整数转换为字符串
out.write(content + "\t");
}
out.write("\r\n");
}
out.close();
}
catch (IOException e)
{
e.printStackTrace();
}
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
System.out.print(magic[i][j] + "\t");
System.out.println();
}
return true;
二、Turtle Graphics
1.Turtle graphics and drawSquare
调用forward沿当前方向移动sideLenghth像素,再调用turn旋转90度。重复四次,则可以画出一个边长为sideLength的正方形。
public static void drawSquare(Turtle turtle, int sideLength) {
turtle.forward(sideLength);
turtle.turn(90);
turtle.forward(sideLength);
turtle.turn(90);
turtle.forward(sideLength);
turtle.turn(90);
turtle.forward(sideLength);
turtle.turn(90);
}
2.Drawing polygons
画出一个正多边形。
根据多边形的边角关系完成calculateRegularPolygonAngle方法和calculatePolygonSidesFromAngle方法。
public static double calculateRegularPolygonAngle(int sides) {
return (double)((sides-2)*180)/sides;
}
public static int calculatePolygonSidesFromAngle(double angle) {
return (int) Math.round(360/(180-angle));
}
输入需要满足边数大于等于3、边长大于等于0,否则报错退出。由于每次默认是向北方向的,所以第一次要转的角度需要判断内角是锐角还是钝角,如果是锐角的话,第一次向右旋转(90-内角)°,如果是钝角的话,第一次向右旋转(450-内角)°,然后每次旋转(180-内角)°。每次旋转后前进固定长度,总共前进次数为边数,则可以得到一个正多边形。
public static void drawRegularPolygon(Turtle turtle, int sides, int sideLength) {
if (sides>=3)
{
if(sideLength>=0)
{
double Dside = calculateRegularPolygonAngle(sides);
if(Dside>90)
{
turtle.turn(450- Dside);
turtle.forward(sideLength);
}
else
{
turtle.turn(90 - Dside);
turtle.forward(sideLength);
}
for (int i = 1; i<sides; i++)
{
turtle.turn(180- Dside);
turtle.forward(sideLength);
}
}
else
{
System.out.println("sideLength should be greater than or equal to 0");
System.exit(0);
}
}
3.Calculating Bearings
calculateBearingToPoint函数:
当两点纵坐标相等时,判断横坐标的大小关系来计算出需要旋转的角度;
纵坐标不相等时,调用arctan计算倾斜角,调用toDegrees将弧度制转为角度制,再计算需要旋转的角度。如果算出的结果不在[0,360)范围内,则通过加减360使其在范围内。
public static double calculateBearingToPoint(double currentBearing, int currentX, int currentY,int targetX, int targetY) {
double r;
if(currentY==targetY)
{
if(currentX>targetX)
{
r=270-currentBearing;
}
else if(currentX<targetX)
{
r=90-currentBearing;
}
else
{
r=0;
}
}
else if(targetX==currentX&&targetY<currentY)
{
return 180;
}
else
{
double k=((double)(targetX-currentX))/((double)(targetY-currentY));
r=Math.toDegrees(Math.atan(k))-currentBearing;
}
while(r>=360)
{
r-=360;
}
while(r<0)
{
r+=360;
}
return r;
}
calculateBearings函数
使用temp记录当前角度,调用calculateBearingToPoint函数计算每次需要旋转的角度保存在列表Angle中。
public static List<Double> calculateBearings(List<Integer> xCoords, List<Integer> yCoords) {
List<Double> Angle = new ArrayList<Double>();
int n=xCoords.size();
double temp=0;
for(int i=0;i<n-1;i++)
{
Angle.add(calculateBearingToPoint(temp,xCoords.get(i),yCoords.get(i),xCoords.get(i+1),yCoords.get(i+1)));
temp=Angle.get(i);
}
return Angle;
}
4.Convex Hulls
算法思想:集合大小小于等于3时凸包就是这个集合。大于3时,首先找出左下角的点,并记录为初始点和极点。找到极角最小的点加入凸包集合(极角相同的点选择距离极点最远的点),并将该点更新为极点,并且在原集合中删除该点。重复寻找极角最小的点的操作,直到极角最小的点为初始点时,返回凸包集合。
实现:
循环寻找极角最小的点时,要使用do-while循环,避免开始时初始点和当前点相同直接跳出循环。
public static Set<Point> convexHull(Set<Point> points) {
if(points.size()<=3)
{
return points;
}
Set<Point> convexHull = new HashSet<Point>();
List<Point> temp = new ArrayList<Point>();
for (Point point: points)
{
temp.add(point);
}
Point pole=temp.get(0);//记录极点
Point first=temp.get(0);//记录初始点
Point now=temp.get(1);//记录当前极角最小的点
for(int i=1;i<temp.size();i++)//找到左下角的点
{
now=temp.get(i);
if((pole.x()>now.x())||((pole.x()==now.x())&&(pole.y()>now.y())))
{
pole=now;
}
}
first=pole;
convexHull.add(pole);
double degree;
do
{
now=temp.get(0);
degree=calculateBearingToPoint(0,(int)pole.x(),(int) pole.y(),(int) temp.get(0).x(),(int) temp.get(0).y());
int n=temp.size();
if(n>1)
{
for(int i=1;i<n;i++)
{
if((degree>calculateBearingToPoint(0,(int)pole.x(), (int)pole.y(), (int)temp.get(i).x(), (int)temp.get(i).y()))&&!((now==first)&&(pole==first)))
{
now=temp.get(i);
degree=calculateBearingToPoint(0,(int)pole.x(), (int)pole.y(), (int)temp.get(i).x(), (int)temp.get(i).y());
}
else if((degree==calculateBearingToPoint(0,(int)pole.x(), (int)pole.y(), (int)temp.get(i).x(), (int)temp.get(i).y()))&&!((now==first)&&(pole==first)))
{
if(Math.abs(pole.x()-now.x())<Math.abs(pole.x()-temp.get(i).x())||Math.abs(pole.y()-now.y())<Math.abs(pole.y()-temp.get(i).y()))
{
now=temp.get(i);
degree=calculateBearingToPoint(0,(int)pole.x(), (int)pole.y(), (int)temp.get(i).x(), (int)temp.get(i).y());
}
}
}
}
convexHull.add(now);
pole=now;
temp.remove(now);
}while(now!=first);
return convexHull;
}
5.Personal art
利用循环,循环中使用旋转、前进和改变颜色绘制而成。
public static void drawPersonalArt(Turtle turtle) {
for(int j=0;j<180;j++)
{
for(int i=0;i<5;i++)
{
switch (i%5)
{
case 0:
turtle.color(PenColor.GREEN);
case 1:
turtle.color(PenColor.YELLOW);
break;
case 2:
turtle.color(PenColor.BLUE);
break;
case 3:
turtle.color(PenColor.RED);
case 4:
turtle.color(PenColor.GREEN);
break;
}
turtle.forward(250);
turtle.turn(144);
}
turtle.turn(100);
if(j%9==0)
{
turtle.turn(77);
}
}
}
三、Social Network
通过一个图模拟社交网络,顶点代表人,边代表两个人之间有社交关系。实际是考察对图的各种操作,添加顶点、添加边和计算最短距离。
1.设计/实现FriendshipGraph类
List<Person> vertexList = new ArrayList<Person>();//使用List保存顶点
addEdge函数用于在两个顶点之间加入边,并在有不存在的顶点、为同一个顶点或边已经存在时,报错退出。
public void addVertex(Person man)//加入顶点
{
if (vertexList.contains(man))//判断顶点是否重复
{
System.out.println("Duplicate names");
System.exit(0);
}
else
{
vertexList.add(man);
}
}
addVertex函数用于图中加入顶点,并判断顶点名是否重复,有重复时报错退出。
public void addEdge(Person x, Person y)//加入边
{
if (vertexList.contains(x)&& vertexList.contains(y))//判断是否有不存在的顶点
{
if (x!=y)//判断是否为同一个顶点
{
if(x.exist(y)==false)//判断边是否已经存在
{
x.addFriend(y);
}
else
{
System.out.println("There's already an edge between these two vertices");
System.exit(0);
}
}
else
{
System.out.println("Repeated vertices");
System.exit(0);
}
}
else
{
System.out.println("Vertices don't exist");
System.exit(0);
}
}
getDistance函数把图转化为邻接矩阵后,根据该矩阵使用Dijkstra算法计算并返回两个顶点之间的距离。
public int getDistance(Person x, Person y)//Dijkstra算法计算两个顶点的距离
{
if (vertexList.contains(x)&&vertexList.contains(y))
{
int size = vertexList.size();//计算顶点数量
int[][] Adjacency = new int[size][size];//保存邻接矩阵
List<Integer> Distance = new ArrayList<Integer>();//存储该顶点到其他顶点的最短距离
boolean[] flag = new boolean[size];//用来记录是否被访问过
int w = 0;
int sum = 0;
for (int i = 0; i < size; i++)
{
Person temp = vertexList.get(i);
for (int j = 0; j < size; j++)
{
if (i == j)
{
Adjacency[i][j] = 0;
}
else if (temp.exist(vertexList.get(j)))
{
Adjacency[i][j] = 1;
}
else
{
Adjacency[i][j] = 1000000;//用1000000来代表不连通
}
}
}
//将所有点默认初始化为没有访问过
int n = vertexList.indexOf(x);//获得x在存储顶点的vertexList中的下标
for (int i = 0; i < size; i++)
{
flag[i] = false;
Distance.add(Adjacency[n][i]);
}
// 利用Dijkstra算法,算出一个点到所有点的单源最短路径
for (int i = 0; i < size; i++)
{
w = min(flag,Distance);
flag[w] = true;//w是当前List中距离该点距离最短的点,并且是没有被访问过的
for (int j = 0; j < size; j++)
{
if (flag[j] == false && j != n)
{
sum = Distance.get(w) + Adjacency[w][j];
if (sum < Distance.get(j))//如果经过其他点到达另一个顶点的距离更小,那么最短距离便是经由其他点到达的距离
{
Distance.set(j, sum);
}
}
}
sum = 0;
}
if (Distance.get(vertexList.indexOf(y)) >=1000000)
{
return -1;
}
else
{
return Distance.get(vertexList.indexOf(y));
}
}
else
{
System.out.println("Vertices don't exist");
System.exit(0);
return -2;
}
}
public int min(boolean[] flag,List<Integer> x) // 算出当前List中距离该点距离最短的点,并且是没有被访问过的
{
int temp = 2000;
int m = 0;
int size = x.size();
for (int i = 0; i < size; i++)
if (flag[i] == false&&x.get(i)!=0&&x.get(i)<temp)
{
temp = x.get(i);
m = i;
}
return m;
}
2设计/实现Person类
由于person中的名字和邻接关系都应该是私有的,所以要在其内部声明方法。这样的private在面向对象的编程中是十分重要的。保证用户可以访问的权限。
public class Person//Person是基本的对象类型
{
private String name;
private List<Person> edgeList=new ArrayList<>();//保存和该顶点相连接的所有顶点
public String getname(Person friend)//获取该Person的名字
{
return this.name;
}
public boolean exist(Person friend)//判断是否存在边
{
if(edgeList.contains(friend))
{
return true;
}
else
{
return false;
}
}
public void addFriend(Person bfriend)//加入边
{
edgeList.add(bfriend);
}
public Person(String friend)
{
this.name=friend;
}
}
3.设计/实现客户端代码main()
public static void main(String[] args)
{
FriendshipGraph graph = new FriendshipGraph();
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
}
4.设计/实现测试用例
创建一个五个顶点五条边的有向图。测试每个方法的功能是否符合yu'q
class FriendshipGraphTest
{
@Test
void friendshipGraphtest()
{
FriendshipGraph graph = new FriendshipGraph();
Person Curry = new Person("Curry");
Person Green = new Person("Green");
Person Thompson = new Person("Thompson");
Person Wiggins = new Person("Wiggins");
Person Looney = new Person("Looney");
graph.addVertex(Curry);
graph.addVertex(Green);
graph.addVertex(Thompson);
graph.addVertex(Wiggins);
graph.addVertex(Looney);
graph.addEdge(Curry, Green);
graph.addEdge(Wiggins, Curry);
graph.addEdge(Wiggins, Looney);
graph.addEdge(Thompson, Looney);
graph.addEdge(Green,Thompson);
assertEquals(3,graph.getDistance(Curry, Looney));
assertEquals(-1,graph.getDistance(Green,Wiggins));
assertEquals(3,graph.getDistance(Wiggins, Thompson));
}
}