一. 幻方问题
幻方:一个n×n矩阵,其所有行、所有列和两个对角线中的n个数字和为一个常数
要求1:判断给出的矩阵是否为幻方
要求2:针对给出的使用罗伯法生成幻方的代码,画出程序流程图
要求1:
首先,我需要读入文件数据,并将其存入一个二维数组中。
读取方法:使用BufferedReader类逐行读取文件,并使用line.split("\t")
方法分割字符,存入一个String[ ]数组中,再通过Integer.valueOf(substring)
将每个字符转化为数字,存入二维数组。
BufferedReader reader = new BufferedReader(new FileReader(fileName));
String line;
while ((line = reader.readLine()) != null) {
String[] elements = line.split("\t");
存入数组后,遍历数组的每一行每一列每一个对角线,判断是否相等,若不相等,返回false,若均相等,返回true
// 判断是否为 magic square
int n = matrix.length;
int sum = 0;
// 计算第一行的和
for (int j = 0; j < n; j++) {
sum += matrix[0][j];
}
// 验证每行、每列以及对角线的和是否相等
for (int i = 0; i < n; i++) {
int rowSum = 0;
int colSum = 0;
for (int j = 0; j < n; j++) {
rowSum += matrix[i][j];
colSum += matrix[j][i];
}
if (rowSum != sum || colSum != sum) {
return false;
}
}
// 验证主对角线的和
int diagSum1 = 0;
for (int i = 0; i < n; i++) {
diagSum1 += matrix[i][i];
}
if (diagSum1 != sum) return false;
// 验证副对角线的和
int diagSum2 = 0;
for (int i = 0; i < n; i++) {
diagSum2 += matrix[i][n - 1 - i];
}
return diagSum2 == sum;
错误处理:在我的程序中,将输入的数据以 \t
分隔,如果文件中的数据以空格分隔,会将带有空格字符的数据读入String[ ]数组中,因此,只需在每一次分隔后判断字符串中是否包含空格字符,即可做出未以\t
分隔的错误处理。
for (String element : elements) {
if (element.contains(" ")) {
System.out.print("文件" + fileName + "错误:数字之间并非使用'\\t'分割\t");
return false;
}
错误处理:在判断完字符串是否以 \t
分割后,通过正则表达式判断输入数据是否为正整数
String regex = "\\d+";
if (!Pattern.matches(regex, element)) {
System.out.print("文件" + fileName + "错误:矩阵中的数字必须为正整数\t");
return false;
}
错误处理:在第一次向矩阵中写入数据中,会记录第一行的长度length,在后续读每一行时,都会比较length与element.length
是否相等,且在矩阵读完后,还会判断length与row是否相等
// 判断是否为方阵
if (!(length == elements.length)) {
System.out.print("文件" + fileName + "错误:不是一个n×n矩阵\t");
return false;}
if (!(length == row)) {
System.out.print("文件" + fileName + "错误:不是一个n×n矩阵\t");
return false;
}
要求2:
程序流程图:
二. Turtle Graphics
在这个任务中,我需要用老师给出的Java海龟图形代码,来完成TurtleSoup.java类中未完成的一系列方法,在每完成一个方法后,需要用Junit调试,并通过git完成add、commit、push等操作。 这里,我选择一些难度较高的问题进行总结
Problem 6 :Calculating Bearings
calculateBearingToPoint:
设计思路:首先计算出目标方向的方位角,再用该角度减去初始方位角得到角度差,若相减为负数,则加上360°
首先,计算当前位置到目标位置的相对坐标偏移量,可表示为一个向量。
int deltaX = targetX - currentX;
int deltaY = targetY - currentY;
使用反正切函数计算向量相对正x轴方向的方位角,并将弧度转化为角度
double relativeBearing = Math.atan2(deltaY, deltaX);
double degreesBearing = Math.toDegrees(relativeBearing);
随后,将方位角转化成以北为基准,顺时针为正方向的角度
degreesBearing = 90.0 - degreesBearing;
if(degreesBearing < 0) degreesBearing += 360;
最后,计算角度差
double angleDifference = degreesBearing - currentBearing;
if (angleDifference < 0) {
angleDifference += 360;
}
calculateBearings:
设计思路:遍历两个List,使用calculateBearingToPoint
方法来计算相邻两点的方位角差,存入一个List数组中,并用currentBearing
加上这个方位角差作为新的currentBearing
,若大于360°,则减一个360°
代码示例:
double currentBearing = 0.0;
List<Double> bearingsList = new ArrayList<>();
for (int i = 0; i < xCoords.size() - 1; i++) {
Integer currentX = xCoords.get(i);
Integer currentY = yCoords.get(i);
Integer targetX = xCoords.get(i+1);
Integer targetY = yCoords.get(i+1);
bearingsList.add(calculateBearingToPoint(currentBearing,currentX,currentY,targetX, targetY));
currentBearing += calculateBearingToPoint(currentBearing,currentX,currentY,targetX, targetY);
if(currentBearing >= 360.0) currentBearing -= 360.0;
}
Problem 7: Convex Hulls
该任务中,我被给出一组坐标,要求计算其凸包
**设计思路:**使用礼品包装算法,取集合中左下角的点——纵坐标最小,若与其他点纵坐标相等,则取横坐标最小。该点必是凸包中的一个点,从该点开始,按逆时针的方向,逐个找凸包上的其他点,若两点均可选且在同一直线上,则取距离参考点最远的点。
**解决方案:**取一参考点,上一个参考点到它的向量方向角度即为该参考点的方位角,遍历集合,使用calculateBearingToPoint
方法,找出与参考点方位角差最大的点即为凸点,将该凸点设为新的参考点,循环进行,直到新参考点为起始点,终止。对于初始点没有上一个参考点的情况,我们令其方向为0.0,找不超过90°的最大方位角。
代码展示:
首先,选择凸包起始点,findStartPoint
函数实现找到左下角点
Point startPoint = findStartPoint(points);
convexHull.add(startPoint);
以起始点为参考点,令初始方向为0.0
Point currentPoint = startPoint;
double currentBearing = 0.0;
遍历集合,找方位角差最大的点为新的参考点,并考虑初始点和两点同一直线的情况
for (Point candidate : points) {
if (!candidate.equals(currentPoint)) {
double bearing = calculateBearingToPoint(currentBearing,(int)currentPoint.x(),
(int)currentPoint.y(), (int)candidate.x(),(int)candidate.y());
if (bearing > maxBearing) {
// 当参考点为起始点时,需排除角度差大于90的情况
if(currentPoint == startPoint && bearing > 90.0)
continue;
maxBearing = bearing;
nextPoint = candidate;
} else if (bearing == maxBearing) {
// 方位角相等时,选距离currentPoint距离最远的点
if(distance(currentPoint,nextPoint) < distance(currentPoint,candidate))
nextPoint = candidate;
}
}
}
遍历结束,添加旧参考点至凸包,添加新参考点,并确定新方向
// 添加变化方位角最大的点到凸包
convexHull.add(nextPoint);
currentPoint = nextPoint;
// 以新方向为基准找凸点
currentBearing += maxBearing;
if(currentBearing >= 360.0) currentBearing -= 360.0;
三. Social Network问题
该任务要求我实现并测试一个社交关系图,并可以计算图中两个人之间的距离。还需要实现一个辅助的Person类。我还应该将社交网络构建为一个无向图,其中每个人都连接到零个或更多的人,但是底层图实现应该是有向的。
设计/实现FriendshipGraph类
使用Map存储图的结构,键为顶点,值为顶点的邻接表
Map<Person, List<Person>> graph;
添加Person时需判断图中是否已存在
public void addVertex(Person person) {
if (!graph.containsKey(person)) {
graph.put(person, new ArrayList<>());
}
}
添加边时需要判断图中是否存在输入的两个顶点
public void addEdge(Person person1, Person person2) {
if (!graph.containsKey(person1) || !graph.containsKey(person2)) {
throw new IllegalArgumentException("Person not in graph");
}else {
graph.get(person1).add(person2);
}
}
计算两点间距离时使用广度优先算法,找起始点的邻接表,并存入队列中,循环取队头,重复操作,直到找到目标点
public int getDistance(Person person1, Person person2) {
// 用Set存储已访问过的顶点,避免重复访问
Set<Person> visited = new HashSet<>();
// 用队列存储待访问的顶点和已走过距离组成的键值对
Queue<Pair<Person, Integer>> queue = new LinkedList<>();
queue.add(new Pair<>(person1, 0));
while (!queue.isEmpty()) {
// 取出队列中的第一个元素
Pair<Person, Integer> current = queue.poll();
// 当前顶点
Person currentPerson = current.getKey();
int dist = current.getValue();
visited.add(currentPerson);
if (currentPerson == person2) {
return dist;
}
// 将当前顶点的邻接表中的顶点加入队列
for (Person friend : graph.getOrDefault(currentPerson, Collections.emptyList())) {
if (!visited.contains(friend)) {
queue.add(new Pair<>(friend, dist + 1));
}
}
}
// 如果两个人之间没有路径,则返回-1
return -1;
}
存入队列的数据结构为自定义的Pair内部静态类,以键值对的形式构建,键为顶点,值为已走的距离
public Pair(K key, V value) {
this.key = key;
this.value = value;
}