
1. 问题背景与手动计算的局限性
在地理信息系统或路径规划等应用中,经常需要计算一系列城市两两之间的距离。假设我们有一个cities类,其中包含城市id、x坐标和y坐标,并提供一个distance方法来计算当前城市到指定坐标的距离。
初始的城市数据结构可能如下所示:
public class Cities {
int cityID;
float City_X_Location;
float City_Y_Location;
public void Initialization(int id, float x, float y) {
this.cityID = id;
this.City_X_Location = x;
this.City_Y_Location = y;
}
public void Distance(float x, float y) {
// 使用当前城市的坐标与传入的 (x, y) 计算距离
int dist = (int) Math.sqrt(Math.pow(this.City_X_Location - x, 2) + Math.pow(this.City_Y_Location - y, 2));
System.out.println(dist);
}
// 为了后续的equals方法,可能需要一个getter
public int getCityID() {
return cityID;
}
// 更好的实践是提供getter for X, Y coordinates
public float getCity_X_Location() {
return City_X_Location;
}
public float getCity_Y_Location() {
return City_Y_Location;
}
// 重写equals方法以实现基于cityID的比较
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Cities cities = (Cities) obj;
return cityID == cities.cityID;
}
@Override
public int hashCode() {
return java.util.Objects.hash(cityID);
}
}
public class Lab2 {
public static Cities[] City = new Cities[4]; // 假设有4个城市
public static void main(String[] args) {
for (int i = 0; i < City.length; i++)
City[i] = new Cities();
City[0].Initialization(0, 1, 1);
City[1].Initialization(1, 5, 5);
City[2].Initialization(2, 10, 3);
City[3].Initialization(3, 2, 7);
// 手动计算示例:从City[0]到其他城市的距离
// City[0].Distance(City[1].City_X_Location, City[1].City_Y_Location); // 3
// City[0].Distance(City[2].City_X_Location, City[2].City_Y_Location); // 8
// City[0].Distance(City[3].City_X_Location, City[3].City_Y_Location); // 6
// ...以此类推,对于所有城市对进行手动编码,效率低下且易错
}
}手动为每个城市对编写Distance调用是繁琐且不切实际的,尤其当城市数量增加时。我们需要一种自动化的方式来遍历所有城市并计算它们之间的距离。
2. 错误的循环尝试与分析
初学者可能会尝试使用单个for循环,并通过在循环体内递增索引来访问不同的城市,如下所示:
// 错误的尝试
for (int i = 0; i < City.length; i++) {
City[i].Distance(5, 5); // 错误:这里的5,5是硬编码,并非来自其他城市
i++; // 错误:手动递增i会跳过一些城市,且无法正确配对
City[i].Distance(10, 3);
i++;
City[i].Distance(2, 7);
i++;
}这种尝试是错误的,原因如下:
立即学习“Java免费学习笔记(深入)”;
- 硬编码坐标: Distance(5, 5)等调用中的坐标是固定的,而不是动态地获取其他城市的坐标。
- 不正确的索引递增: i++操作在每次循环迭代中被执行了多次,导致i的值跳跃式增长,无法遍历所有城市,也无法形成正确的城市对。
- 无法实现两两比较: 单个循环无法实现“每个城市与所有其他城市”的比较逻辑。
3. 正确的解决方案:嵌套循环
要实现每个城市与所有其他城市(除了它自身)的距离计算,我们需要使用嵌套循环。外层循环用于选择一个“当前城市”,内层循环用于选择一个“目标城市”与当前城市进行比较。同时,需要一个条件判断来避免城市与自身进行距离计算。
3.1 使用增强型for循环
Java的增强型for循环(foreach循环)提供了一种简洁的方式来遍历集合或数组。
public class Lab2 {
// ... (City数组和Initialization部分与上面相同) ...
public static void main(String[] args) {
// ... (初始化城市数据) ...
System.out.println("--- 使用增强型for循环计算所有城市对距离 ---");
// 外层循环:选择当前城市
for (Cities currentCity : City) {
// 内层循环:选择目标城市
for (Cities otherCity : City) {
// 避免城市与自身进行比较
if (!currentCity.equals(otherCity)) { // 假设Cities类已正确重写equals方法
// 调用当前城市的Distance方法,传入目标城市的坐标
System.out.print("距离从 City[" + currentCity.getCityID() + "] 到 City[" + otherCity.getCityID() + "]: ");
currentCity.Distance(otherCity.getCity_X_Location(), otherCity.getCity_Y_Location());
}
}
}
}
}代码解释:
- for (Cities currentCity : City): 外层循环遍历City数组中的每一个Cities对象,将其赋值给currentCity。这代表了我们要计算距离的起始城市。
- for (Cities otherCity : City): 内层循环也遍历City数组,将其赋值给otherCity。这代表了我们要计算距离的目标城市。
- if (!currentCity.equals(otherCity)): 这是关键的条件判断。它确保我们不会计算一个城市到它自身的距离。为了使equals方法正常工作,Cities类必须正确重写equals方法(通常基于唯一的城市ID)。如果equals方法未重写,默认的equals行为是比较对象的引用,即currentCity == otherCity,这对于数组中不同的对象引用也是有效的。
- currentCity.Distance(otherCity.getCity_X_Location(), otherCity.getCity_Y_Location()): 调用currentCity对象的Distance方法,传入otherCity的X和Y坐标,从而计算出它们之间的距离。
3.2 使用传统索引型for循环
对于不熟悉增强型for循环的开发者,也可以使用传统的基于索引的for循环实现相同的功能:
public class Lab2 {
// ... (City数组和Initialization部分与上面相同) ...
public static void main(String[] args) {
// ... (初始化城市数据) ...
System.out.println("--- 使用传统索引型for循环计算所有城市对距离 ---");
// 外层循环:选择当前城市的索引
for (int i = 0; i < City.length; i++) {
// 内层循环:选择目标城市的索引
for (int j = 0; j < City.length; j++) {
// 避免城市与自身进行比较
if (i != j) { // 比较索引即可避免自身比较
// 获取当前城市和目标城市对象
Cities currentCity = City[i];
Cities otherCity = City[j];
// 调用Distance方法
System.out.print("距离从 City[" + currentCity.getCityID() + "] 到 City[" + otherCity.getCityID() + "]: ");
currentCity.Distance(otherCity.getCity_X_Location(), otherCity.getCity_Y_Location());
}
}
}
}
}代码解释:
- for (int i = 0; i 分别使用索引i和j来遍历城市数组。
- if (i != j): 通过比较索引来判断是否是同一个城市。这种方式简单直接,不需要Cities类重写equals方法。
4. 注意事项与最佳实践
equals()方法的重要性: 如果使用!currentCity.equals(otherCity)来避免自身比较,确保Cities类已正确重写equals方法和hashCode方法。通常,equals方法会基于对象的唯一标识符(如cityID)来判断两个对象是否逻辑相等。如果未重写,equals方法默认行为与==操作符相同,即比较对象引用,这在数组中每个对象都是独立实例的情况下也能工作。
-
数据封装: 在Cities类中,City_X_Location和City_Y_Location等属性建议设置为private,并通过公共的getter方法(如getCity_X_Location()和getCity_Y_Location())来访问,以遵循面向对象编程的封装原则。
public class Cities { private int cityID; // 设为私有 private float City_X_Location; // 设为私有 private float City_Y_Location; // 设为私有 // ... 省略Initialization方法,通常构造函数更合适 ... // 提供公共的getter方法 public float getCity_X_Location() { return City_X_Location; } public float getCity_Y_Location() { return City_Y_Location; } // ... 其他方法和重写的equals/hashCode ... } -
避免重复计算: 上述方法计算的是从A到B的距离,以及从B到A的距离。如果距离是双向对称的(即Distance(A,B) == Distance(B,A)),并且你只关心唯一的城市对,可以优化内层循环,避免重复计算。例如,内层循环可以从i + 1开始:
// 优化:避免重复计算 (A到B和B到A视为同一对) for (int i = 0; i < City.length; i++) { for (int j = i + 1; j < City.length; j++) { // j从 i+1 开始 Cities cityA = City[i]; Cities cityB = City[j]; System.out.print("距离从 City[" + cityA.getCityID() + "] 到 City[" + cityB.getCityID() + "]: "); cityA.Distance(cityB.getCity_X_Location(), cityB.getCity_Y_Location()); // 如果需要,也可以计算 cityB.Distance(cityA.getCity_X_Location(), cityA.getCity_Y_Location()); // 但通常情况下,距离是对称的,只需计算一次 } }这种优化将只计算一半的距离对,减少了计算量。
5. 总结
通过本教程,我们学习了如何利用Java中的嵌套for循环高效地计算一个城市集合中所有不同城市对之间的距离。关键在于:
- 使用外层循环选择当前城市。
- 使用内层循环选择目标城市。
- 通过条件判断(if (!currentCity.equals(otherCity)) 或 if (i != j))避免城市与自身进行比较。
- 遵循封装原则,使用getter方法访问对象属性。
- 根据需求考虑优化,避免重复计算对称距离。
掌握这些技巧将有助于编写更健壮、更高效的Java代码来处理数据集合中的两两比较问题。










