题目如下:
已知平面上若干个点的坐标。
需要求出在所有的组合中,4个点间平均距离的最小值(四舍五入,保留2位小数)。
比如有4个点:a,b,c,d,则平均距离是指:ab, ac, ad, bc, bd, cd 这6个距离的平均值。
每个点的坐标表示为:横坐标,纵坐标
坐标的取值范围是:1~1000
所有点的坐标记录在in.txt中,请读入该文件,然后计算。
注意:我们测试您的程序的时候,in.txt 可能会很大,比如包含上万条记录。
举例:
如果,in.txt 内的值为:
10,10
20,20
80,50
10,20
20,10
则程序应该输出:
11.38
解题的关键是:上面红色的文字,上万条记录。
点的数量小的话,直接用10试探法,很容易就可以得出答案。
评分标准里说:考查考生对算法的优化。
如果直接用10试探,当点数量到达1000的时候,经测试要一分钟+,就这个题目。
所以说,上w个点更加不行了,所以这个算法已经否定。
其实一开始自己也没有想到更为绝妙的算法。后来经老师指点了一下:把平面上的点分块。就这么一句话,我们把思路想出来了,然后不到10分钟,代码也出来了。1w个点也不是问题。我测试过6000个点,一般10秒左右就可以算出。所以,找方法是最最重要的。
在此我做一张图,以更让读者简单易懂
如图,区域分为4*4的小方块,蓝色方块每个方块内的点做组合,计算平均距离,绿色方块同理,这样可以遍历所有的方块,直到找出平均距离最短的4点。这样,算法非常容易就能写出来。可以想到:假如一个方块内只有3个点或者小于3个点,那么这三个点就完全不用考虑了,自己可以想到。这样另外一个问题出来了,也是这个方法最致命的一点:假如每一个划分的小方块都最多只能找到3个点呢?问题就在这里,解决也不是没有办法,这就是区块划分的问题了,动态规划它,但是这个没有定量,所以也很难确定最优时间区块的划分,因为所有点不清楚分布的分散与否。
以下为我的代码,java代码:
- import java.io.IOException;
- import java.io.RandomAccessFile;
- import java.util.ArrayList;
- import java.util.List;
- public class Ti5Demo {
- static boolean first = false ; // 是否是第一次记录最小距离
- static boolean has10 = false ; // 是否找到10
- static float minL = 0 ; // 记录当前计算出来的最小距离
- static float tempL = 0 ; // 临时记录当前的最小距离
- static int[] tempDot = new int[4] ; // 这个是临时保存这4个点在List中的位置
- static float a1, a2, a3, a4, a5, a6 ; // 临时存放4点的6个距离
- public static void main(String[] args) throws IOException {
- List<Node> arr = new ArrayList<Node>() ;
- List<Node> tArr = new ArrayList<Node>() ;
- String curr ;
- RandomAccessFile r = new RandomAccessFile("src/p4.txt", "r") ;
- while(r.read() != -1) {
- r.seek(r.getFilePointer()-1) ;
- curr = r.readLine() ;
- Node n = new Node() ;
- String te[] = curr.split(",") ;
- n.x = Integer.valueOf(te[0]) ;
- n.y = Integer.valueOf(te[1]) ;
- arr.add(n) ;
- }
- /**
- * 历遍每一个小方块,逐个计算
- */
- int i, j, k, l ; // i,j为x轴最小值和最大值,k,l为y轴最小值和最大值
- int t ; // 查找的临时变量
- int dia = 5 ; // 分块的大小
- long time = System.currentTimeMillis() ;
- for(i = 0, j = dia*2; j <= 1000; i+=dia, j+=dia) {
- for(k = 0, l = dia*2; l <= 1000; k+=dia, l+=dia) {
- for(t = 0; t < arr.size(); t++) {
- if(arr.get(t).x >= i && arr.get(t).x <= j && arr.get(t).y >= k && arr.get(t).y <= l) {
- tArr.add(arr.get(t)) ;
- }
- }
- if(tArr.size() > 3) {
- p(tArr) ;
- }
- tArr.clear() ;
- }
- }
- System.out.println("用时:" + (System.currentTimeMillis() - time)) ;
- System.out.printf("%3.2f", minL) ;
- }
- /**
- * 求4个点的平均距离,并比较当前已记录的最小距离,并更新最小距离变量
- * @param arr
- * @param numArr
- * @return
- */
- public static void co(List<Node> arr, int[] numArr) {
- int temp = 0 ;
- for(int i = 0; i < numArr.length; i++) {
- if(numArr[i] == 1) {
- tempDot[temp] = i ;
- temp++ ;
- }
- }
- a1 = twoDot(arr.get(tempDot[0]), arr.get(tempDot[1])) ;
- a2 = twoDot(arr.get(tempDot[0]), arr.get(tempDot[2])) ;
- a3 = twoDot(arr.get(tempDot[0]), arr.get(tempDot[3])) ;
- a4 = twoDot(arr.get(tempDot[1]), arr.get(tempDot[2])) ;
- a5 = twoDot(arr.get(tempDot[1]), arr.get(tempDot[3])) ;
- a6 = twoDot(arr.get(tempDot[2]), arr.get(tempDot[3])) ;
- tempL = (a1 + a2 + a3 + a4 + a5 + a6) / 6 ;
- if(first == true) {
- if(minL > tempL) {
- minL = tempL ;
- }
- }else if(first == false) {
- minL = tempL ;
- first = true ;
- }
- }
- /**
- * 计算两点间距离
- * @param n1
- * @param n2
- * @return
- */
- public static float twoDot(Node n1, Node n2) {
- float x = Math.abs(n1.x - n2.x) ;
- float y = Math.abs(n1.y - n2.y) ;
- return (float) Math.sqrt(x*x + y*y) ;
- }
- /**
- * 组合处理代码
- * @param arr
- */
- public static void p(List<Node> arr) {
- int[] num = new int[arr.size()] ;
- for(int i = 0; i < 4; i++) {
- num[i] = 1 ;
- }
- has10 = false ;
- co(arr, num) ; // 计算4个点
- while(true) {
- int left1 = 0 ;
- int bound; //第一个"10"组合的索引
- for(int i = 0; i < arr.size()-1; i++) {
- if(num[i] == 1 && num[i+1] == 0) {
- bound = i ;
- num[i] = 0 ;
- num[i+1] = 1 ;
- for(int j = 0; j < left1; j++) {
- num[j] = 1 ;
- }
- for(int j = left1; j < bound; j++) {
- num[j] = 0 ;
- }
- has10 = true ;
- break ;
- }else if(num[i] == 1) {
- left1++ ; // =2
- }
- }
- if(has10 == false) {
- break ;
- }
- co(arr, num) ; // 计算4个点
- has10 = false ;
- }
- }
- }