最小三角形+分治+java

一、问题描述

Description

给你 N 个点,你要找到一个周长最小的三角形。

为了减小问题的难度,这里的三角形也包括共线的三点。


Input

第一行包含一个整数 N 表示点的个数。

接下来 N 行每行有两个整数,表示这个点的坐标(x,y) 。

Output

输出只有一行,包含一个6位小数,为周长最短的三角形的周长(四舍五入)。

Sample Input 1 

4
1 1
2 3
3 3
3 4

Sample output 1 

3.414214


二、解题分析

        如果没有约束条件可以直接按输入的数据顺序,三重循环,取三点求周长,得到最少周长。

但本题需要优化搜索空间并减少计算量,所以采用分治方法。

问题分解:首先,将原始问题分解为更小的子问题。在这里,可以将点集分成两个部分,分别在左右两边递归计算最小周长。(按x坐标或有坐标排序后,用二分)

计算子问题的最小周长:对左右两个部分分别计算最小周长。通过递归,这些子问题会一直分解到最小规模,即一个或两个点的情况,这时直接计算这些点之间的距离即可。(分别计算左右部分的周长)

合并子问题的解:获得左右两个部分的最小周长后,比较两部分的最小周长值,取其中较小的值作为整体的最小周长。(比较左右得到的最小周长)

考虑中间部分:在合并阶段,还需要考虑左右两个部分之间的情况。这时候,可能存在一个最小周长的三角形跨越了左右两个部分,所以需要考虑中间部分的情况。(中间部分也可能是最小三角形,将于中点距离大于左右两个部分的最小周长添加到一个集合,再将和里的点按y坐标排序,然后暴力查找,条件是取的两个点的dy要小于左右两个部分的最小周长)。

比较并更新最小周长:在中间部分,以中间的一个点作为基准,向左右两边延伸找到可能构成更小周长三角形的点集。然后计算这些点集中的三角形周长,选取最小的作为中间部分的最小周长,并与左右部分的最小周长进行比较,取较小者。

最终结果:最终得到的最小周长即为整个点集构成的最小周长三角形。

为什么能得到最小周长?

        通过递归地考虑左右两部分和中间部分,分治算法能够覆盖整个搜索空间,并最终得到最小周长的三角形。

三、代码实现

import java.util.*;

public class myMinTriangle {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt(); // 读取输入整数n
        Point points[] = new Point[n]; // 创建Point数组存储坐标点
        for(int i=0; i<n; i++){
            double x = sc.nextDouble(); // 读取x坐标
            double y = sc.nextDouble(); // 读取y坐标
            points[i] = new Point(x, y); // 存储坐标点到数组中
        }

        Arrays.sort(points, Comparator.comparingDouble(p -> p.x)); // 按照x坐标对点进行排序

        double minPerimeter = findMinTrianglePerimeter(points, 0, n - 1); // 寻找最小周长的三角形
        System.out.printf("%.6f\n", minPerimeter); // 输出结果
    }

    // 计算两点之间的距离
    static double calculateDistance(Point a, Point b) {
        double dx = a.x - b.x; // 计算x方向的差值
        double dy = a.y - b.y; // 计算y方向的差值
        return Math.sqrt(dx * dx + dy * dy); // 返回两点之间的欧氏距离
    }

    // 寻找最小周长的三角形
    static double findMinTrianglePerimeter(Point[] points, int left, int right) {
        if (left + 2 > right) return Double.MAX_VALUE; // 如果点的数量小于3个,返回最大值

        if (left + 2 == right) { // 如果只有3个点
            return calculateDistance(points[left], points[left + 1]) + // 计算三角形的周长
                    calculateDistance(points[left + 1], points[left + 2]) +
                    calculateDistance(points[left], points[left + 2]);
        }

        int mid = (left + right) / 2; // 计算中间点的索引
        double leftMin = findMinTrianglePerimeter(points, left, mid); // 递归处理左半部分
        double rightMin = findMinTrianglePerimeter(points, mid + 1, right); // 递归处理右半部分
        double minDistance = Math.min(leftMin, rightMin); // 计算左右两部分的最小周长

        List<Point> strip = new ArrayList<>(); // 创建strip列表存储中间部分的点
        for (int i = left; i <= right; i++) {
            if (Math.abs(points[i].x - points[mid].x) < minDistance) { // 找出位于中间部分的点
                strip.add(points[i]); // 将满足条件的点添加到strip列表中
            }
        }

        strip.sort(Comparator.comparingDouble(p -> p.y)); // 按照y坐标对strip列表进行排序

        for (int i = 0; i < strip.size(); i++) {
            for (int j = i + 1; j < strip.size() && strip.get(j).y - strip.get(i).y < minDistance; j++) {
                for (int k = j + 1; k < strip.size() && strip.get(k).y - strip.get(i).y < minDistance; k++) {
                    double perimeter = calculateDistance(strip.get(i), strip.get(j)) + // 计算三角形的周长
                            calculateDistance(strip.get(j), strip.get(k)) +
                            calculateDistance(strip.get(i), strip.get(k));
                    minDistance = Math.min(minDistance, perimeter); // 更新最小周长
                }
            }
        }

        return minDistance; // 返回最小周长
    }
}

class Point {
    double x;
    double y;

    public Point() {
    }

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

motu2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值