用不规则数组打印1-100规则排列的三角形

本文介绍了一种使用不规则数组打印由1到100数字构成的直角三角形的方法,详细解释了多层嵌套循环、自增自减运算符和字符串操作的运用,展示了如何通过break和continue语句控制循环流程。

需求:

1. 打印1-100数字组成的直角三角形,不足位用 '- '补齐

2.使用不规则数组实现

 

技术考核:

1.数组,多维数组,不规则数组

2.自增自减运算符

3.for 多层循环与跳出

  • break 跳出语句
  • continue 继续语句

4.字符串

 

效果图:

 

思路:

1.使用不规则数组实现,可任意定义数字的最大值

2.使用多层嵌套循环

3.break 跳出语句

4.continue 继续语句

 

代码:

	// 不规则数组打印1-100 的梯形
	public static void print100Nums() {
		int[][] arr = new int[20][];

		out : for (int i = 0; i < arr.length; i++) {
				arr[i] = new int[i + 1];
				for (int j = 0; j < arr[i].length; j++) {
					if (i == 0 & j == 0) {
						arr[i][j] = (i + 1) * (j + 1);
					} else if (j == 0) {
						int lastIndex = arr[i - 1].length - 1;
						int lastValue = arr[i - 1][lastIndex];
						arr[i][j] = lastValue + 1;
					} else {
						arr[i][j] = arr[i][j - 1] + 1;
					}
					if(arr[i][j]==100){
						break out;
					}
				}
			}
		int firIndex = 0;
		int senIndex = 0;
		for(int i=0;i<arr.length;i++){
			for(int j=0;j<arr[i].length;j++){
				int value = arr[i][j];
				int len = String.valueOf(value).length();
				if(firIndex!=0 && senIndex != 0 && firIndex == i && senIndex < j){
					System.out.print("-"+"  ");
					continue;
				}
				if(len<2){
					System.out.print(value+"  ");
				} else {
					System.out.print(value+" ");
				}
				if(value == 100){
					firIndex = i;
					senIndex = j;
				}
			}
			System.out.println();
			if(firIndex!=0 && senIndex != 0 && firIndex == i){
				break;
			}
		}
		
	}

 

另一种打印方式:

        for(int [] x: arr){
            for (int y : x){
                System.out.print(y + "   ");
                if(y==100){
                    return;
                }
            }
            System.out.println();
        }

 

总结:

跳转语句
  break 跳出
    用法1:跳出循环;当循环嵌套时,跳出当前的循环;
    用法2:跳出语句块。 用于循环嵌套时跳出外循环。
  continue 继续
    用法:虽然break可以跳出循环,但是有时候要停止一次循环剩余的部分,同时还要继续执行下次循环,这时候就需要continue
  return 返回   

    用法:终止以下所有程序的执行

转载于:https://www.cnblogs.com/xianlei/p/7879440.html

<think>我们面对的问题:在Java中实现一个算法,用于找到不规则区域的最小外接四边形(最小面积外接矩形)。 注意:最小外接四边形通常指的是最小面积外接矩形(因为任意凸多边形的最小面积外接矩形都是矩形,但有时也指最小边数的外接多边形,这里根据上下文,我们理解为最小面积外接矩形)。 步骤: 1. 理解问题:给定一个不规则区域(通常由一组点表示,这些点构成一个多边形),我们需要找到包围这个多边形的最小面积的矩形(外接矩形)。 2. 常见方法:旋转卡壳法(Rotating Calipers)是计算凸多边形最小外接矩形的经典算法。但注意,该算法要求输入为凸多边形。如果输入是非凸的,我们需要先求凸包。 因此,步骤为: a) 如果多边形不是凸的,先计算其凸包(Convex Hull)。 b) 使用旋转卡壳算法在凸包上寻找最小面积外接矩形。 3. 旋转卡壳算法原理: - 最小外接矩形必有一条边与凸多边形的一条边重合(或与凸多边形的边平行)。 - 算法遍历凸多边形的每条边,作为矩形的候选边。 - 对于每条边,通过旋转卡壳确定其他三个方向上的极值点(最左、最右、最上、最下)?实际上,在旋转过程中,我们计算与当前边垂直的方向上的宽度,并记录对应的点。 - 具体步骤: 1) 计算凸包上三个关键方向:与当前边方向垂直的方向(即法向),然后计算凸包在这些方向上的投影,得到宽度和高度。 2) 实际上,我们可以通过点积来求投影。对于当前边,我们有两个点定义一条边,然后我们可以得到该边的方向向量,再旋转90度得到法向量。然后计算凸包上所有点在这两个方向(边方向和法方向)上的最大最小值,从而确定当前方向下的外接矩形面积。 3) 遍历凸包的所有边,记录面积最小的矩形。 4. 实现步骤: a) 计算凸包:使用Andrew's Monotone Chain算法(或其他凸包算法)。 b) 对凸包上的点进行排序(按x坐标,如果x相同则按y)。 c) 初始化最小矩形面积为无穷大。 d) 遍历凸包的每条边(假设凸包有n个点,则边为i从0到n-1,边由点points[i]和points[(i+1)%n]构成)。 e) 对于当前边,计算其方向向量(dx, dy),并计算单位法向量(即垂直方向,有两种可能,取一个即可,因为矩形对称)?实际上,我们并不需要单位化,因为投影时用点积,我们只需要方向。 f) 计算凸包上所有点在该边的方向向量和法向量上的投影(即点积)。然后得到投影的最大值和最小值,从而得到宽度(在法向量上的投影差)和高度(在边方向上的投影差?实际上,矩形的一条边与当前边平行,另一条边与当前边垂直。所以矩形的宽度是法向量方向上的投影差,高度是边方向上的投影差?不对,实际上,矩形的面积由两个方向上的跨度决定。但注意,当前边方向上的跨度实际上就是当前边的长度?不是,因为整个凸包在边方向上的投影跨度就是当前边两个端点投影的差,但法向量方向上的投影跨度就是整个凸包在垂直方向上的宽度。因此,矩形的宽度为边向量方向上的投影差(即当前边的长度?)并不是,因为其他点在该方向上的投影可能超出当前边的端点。所以我们需要计算整个凸包在边向量方向上的最大最小值,以及法向量方向上的最大最小值。然后矩形的宽度为(边方向上的最大值-最小值),高度为(法方向上的最大值-最小值)。但是注意,这样计算出来的矩形并不是我们通常的宽高,但面积是(边方向投影差)乘以(法方向投影差)。 5. 具体计算: 设当前边的向量为:v = (dx, dy) = (x2-x1, y2-y1) 法向量:n = (-dy, dx) 或 (dy, -dx) 都可以,只要垂直。我们取n = (-dy, dx)(旋转90度)。 然后,对于凸包上的每个点p,计算: proj_v = p·v (点积,即p.x*dx + p.y*dy) proj_n = p·n (即p.x*(-dy) + p.y*dx) 然后,我们计算所有点的proj_v的最小值和最大值,以及proj_n的最小值和最大值。 那么,当前方向下的矩形面积 = (max_proj_v - min_proj_v) * (max_proj_n - min_proj_n) 但是注意:这个矩形的面积并不是我们通常的面积单位,因为v和n可能不是单位向量。但是,由于我们只是比较面积大小,而且每个矩形都是用同样的向量(只是方向不同)来计算的,所以不同方向下计算出的面积值可以直接比较(因为每个方向都用了相同的缩放因子?)。实际上,点积的结果与向量的模长有关,所以不同边的向量长度不同,这样比较是不对的。我们需要归一化吗? 6. 修正:实际上,我们不需要计算实际的面积,因为面积公式是: area = (max_proj_v - min_proj_v) * (max_proj_n - min_proj_n) / (|v| * |n|) ? 但是,注意:我们计算点积时,投影的长度实际上是除以了|v|(如果我们用单位向量的话)。但我们没有用单位向量,所以投影的长度是:点积除以|v|。因此,实际宽度和高度应该是: width = (max_proj_v - min_proj_v) / |v| height = (max_proj_n - min_proj_n) / |n| 但是,由于|v| = |n|(因为n是由v旋转90度得到的,所以模长相等),所以面积 = width * height = (max_proj_v - min_proj_v) * (max_proj_n - min_proj_n) / (|v|^2) 然而,我们遍历所有边时,需要最小化面积,而除以|v|^2是一个正数,所以我们可以比较 (max_proj_v - min_proj_v) * (max_proj_n - min_proj_n) 除以 |v|^2 的值。但为了效率,我们可以比较 (max_proj_v - min_proj_v) * (max_proj_n - min_proj_n) 的值,同时记录对应的|v|^2?不行,因为不同边的|v|不同,所以不能直接比较这个乘积。 7. 因此,我们需要计算实际面积:area = ( (max_proj_v - min_proj_v) * (max_proj_n - min_proj_n) ) / (|v|^2) [因为|v|=|n|,所以|v|*|n|=|v|^2] 但是,由于点积的投影长度是除以|v|,所以: 实际宽度 = (max_proj_v - min_proj_v) / |v| 实际高度 = (max_proj_n - min_proj_n) / |v| 所以面积 = ( (max_proj_v - min_proj_v) * (max_proj_n - min_proj_n) ) / (|v|^2) 因此,我们在计算面积时,需要除以当前边长的平方(|v|^2)。 8. 另一种方法:为了避免除法,我们可以使用单位向量?但计算单位向量需要开方,而且每个点都要用单位向量点乘,效率低。 9. 实际上,我们可以不除以|v|^2,而是直接比较 (max_proj_v - min_proj_v) * (max_proj_n - min_proj_n) 的值,但这样比较是不对的,因为不同边的|v|不同。所以我们必须计算实际面积。 10. 优化:在循环之前计算当前边的长度平方,然后最后计算面积时除以这个值。 11. 步骤总结: minArea = Double.MAX_VALUE for (int i = 0, j = n-1; i < n; j = i, i++) { Point p1 = points[j]; Point p2 = points[i]; // 计算边向量 double dx = p2.x - p1.x; double dy = p2.y - p1.y; double lengthSquared = dx*dx + dy*dy; // 如果边长为0,跳过 if (lengthSquared < 1e-12) continue; // 计算法向量 (-dy, dx) double nx = -dy; double ny = dx; double minV = Double.MAX_VALUE, maxV = -Double.MAX_VALUE; double minN = Double.MAX_VALUE, maxN = -Double.MAX_VALUE; for (int k = 0; k < n; k++) { Point p = points[k]; // 计算点p在边向量上的投影(点积) double projV = (p.x - p1.x) * dx + (p.y - p1.y) * dy; // 这里以p1为参考点,避免整体偏移 // 计算点p在法向量上的投影 double projN = (p.x - p1.x) * nx + (p.y - p1.y) * ny; if (projV < minV) minV = projV; if (projV > maxV) maxV = projV; if (projN < minN) minN = projN; if (projN > maxN) maxN = projN; } // 计算实际面积:投影跨度在边方向上的实际长度 = (maxV - minV) / |v|,在法方向上的实际长度 = (maxN - minN) / |v| // 所以面积 = [ (maxV-minV) * (maxN-minN) ] / lengthSquared double area = ( (maxV - minV) * (maxN - minN) ) / lengthSquared; if (area < minArea) { minArea = area; // 同时记录当前矩形?如果需要返回矩形,需要记录矩形的四个顶点 } } 12. 如何得到矩形的四个顶点? 矩形的四个顶点由投影的极值点确定。但是,注意:投影的极值点不一定只有一个,而且矩形的顶点是由两个方向上的极值组合而成。通常,矩形的四个顶点是: A: p1 + (minV / |v|^2) * v + (minN / |v|^2) * n ? 这样计算复杂。 另一种思路:矩形的四条边分别与当前边平行和垂直。矩形的底边在边向量v的方向上,长度为(maxV-minV)/|v|,高度为(maxN-minN)/|v|。但是,我们如何定位矩形? 我们可以这样: 从点p1出发,加上向量v方向的投影和n方向的投影: 矩形的一个顶点:p1 + (minV * v) / |v|^2 + (minN * n) / |v|^2 但是,这样计算比较麻烦。 实际上,我们注意到,投影值minV和maxV是相对于点p1的。所以: 在v方向上,矩形的起点位置是:p1 + (minV / |v|) * (v的单位向量) 但我们可以用向量运算:位置 = p1 + (minV * v) / |v|^2 (因为v的单位向量 = v/|v|,所以minV * (v/|v|) = (minV * v) / |v|,但是这里我们除以了|v|,而点积投影是相对于v向量,所以位置向量应该是 (minV / |v|) * (v/|v|) = minV * v / |v|^2) 因此,矩形的四个顶点: base = p1 // 计算四个顶点 double factorV, factorN; Point[] rect = new Point[4]; // 第一个顶点:对应minV和minN factorV = minV / lengthSquared; factorN = minN / lengthSquared; rect[0] = new Point(p1.x + factorV * dx + factorN * nx, p1.y + factorV * dy + factorN * ny); // 第二个顶点:minV和maxN factorV = minV / lengthSquared; factorN = maxN / lengthSquared; rect[1] = new Point(p1.x + factorV * dx + factorN * nx, p1.y + factorV * dy + factorN * ny); // 第三个顶点:maxV和maxN factorV = maxV / lengthSquared; factorN = maxN / lengthSquared; rect[2] = new Point(p1.x + factorV * dx + factorN * nx, p1.y + factorV * dy + factorN * ny); // 第四个顶点:maxV和minN factorV = maxV / lengthSquared; factorN = minN / lengthSquared; rect[3] = new Point(p1.x + factorV * dx + factorN * nx, p1.y + factorV * dy + factorN * ny); 但是,注意:这样计算出来的点是否构成矩形?而且,我们是以p1为参考点的,但投影值minV, maxV, minN, maxN都是相对于p1的。这个公式成立吗? 13. 实际上,上述计算顶点的方法可能不是最优的,而且容易出错。另一种方法是利用投影的极值点,但是投影的极值点可能不是同一个点,我们需要组合。不过,在旋转卡壳中,通常我们记录极值点,然后通过极值点来构造矩形。 14. 简化:我们并不需要实时计算矩形的四个顶点,可以在找到最小面积矩形时再计算。而且,我们可以通过四个极值点(即投影极值对应的点)来构造矩形的边,但注意,矩形的边与当前边平行或垂直。 15. 然而,上述方法中,我们记录的是投影值,而不是点。所以我们需要在计算投影的过程中记录是哪些点产生了极值。但是,由于凸包是凸的,每个投影方向上的极值点都是凸包上的顶点,所以我们可以记录这些点的索引。 16. 修改内层循环:在计算投影时,记录达到minV, maxV, minN, maxN的点。但是注意,可能有多个点同时达到极值,但任取一个即可。 17. 但是,在旋转卡壳算法中,通常我们不需要显式地记录这些点,而是通过当前边的索引和另外三个卡壳点(通过旋转卡壳指针移动)来得到。不过,上述方法已经遍历了所有点,所以我们可以同时记录极值点。 18. 因此,我们可以在内层循环中记录四个极值点(minVPoint, maxVPoint, minNPoint, maxNPoint)。 19. 然后,矩形的四个顶点由这四个点决定?不对。因为矩形是轴对齐的(在当前坐标系下,即v和n构成的坐标系),所以矩形的四个顶点是这四个点投影形成的矩形的四个角。但是,这四个角不一定在原始点集上,而是需要计算。 20. 实际上,矩形的四个顶点可以通过四个极值点来构造:即分别由minV和minN、minV和maxN、maxV和minN、maxV和maxN这四个组合,然后通过反投影得到坐标。反投影公式: P = p1 + (projV * v) / |v|^2 + (projN * n) / |v|^2 其中,projV和projN是投影值。 21. 因此,我们可以用上述公式计算四个顶点。 22. 但是,注意:我们之前计算的投影值minV, maxV, minN, maxN是相对于点p1的。所以,对于任意投影值(projV, projN),其对应的点为: P = p1 + (projV * v + projN * n) / |v|^2 23. 所以,我们可以这样计算矩形的四个顶点(如第12点所示)。 24. 但是,这样计算出来的矩形是否就是外接矩形?是的,因为在这个局部坐标系下,矩形的边与v和n平行,而且投影值覆盖了整个凸包。 25. 注意:我们计算投影时,是以p1为原点的。所以,对于凸包上的点,其投影值可能为负。 26. 实现细节: - 凸包计算:注意凸包点的顺序(逆时针或顺时针),我们的算法要求凸包点按逆时针顺序(这样法向量向外,但这里不影响投影计算)。 - 避免重复计算:在循环中,每次都要遍历所有点,所以时间复杂度为O(n^2)。但是,凸包的点数通常不多,而且有O(n)的旋转卡壳方法(通过双指针移动,因为凸包的有序性,极值点也是按顺序变化的)。我们可以优化内层循环,使用旋转卡壳技巧将内层循环优化到O(1)。 27. 优化:旋转卡壳的经典实现中,在遍历边时,其他三个极值点(左、右、对踵点)可以通过指针的移动来更新,而不需要每次遍历所有点。具体: - 我们维护三个指针(除了当前边的两个点外),分别代表在v方向上的极值点(实际上就是当前边的两个端点,因为v方向就是边的方向,所以最大投影是当前边的终点,最小投影是起点?不对,因为投影是相对于p1的,所以p1的投影为0,p2的投影为|v|^2。但是,其他点可能投影更大或更小?所以我们需要计算整个凸包的投影极值。 - 但是,由于凸包的有序性,当我们旋转边时,投影极值点也是按顺序移动的。因此,我们可以用双指针技术,在O(n)时间内完成。 28. 经典旋转卡壳算法步骤(最小外接矩形): a) 初始化:凸包点逆时针排列,记为P[0..n-1]。 b) 设置三个指针:left, bottom, right。初始时,left=0, bottom=0, right=0。同时,我们计算初始方向(如第一条边)下,凸包在x轴和y轴上的极值点(即最左、最下、最右点)。但是,这里我们以当前边为x轴。 c) 遍历每条边i(从0到n-1): 1) 当前边为P[i]到P[i+1](模n)。 2) 计算当前边的方向向量v。 3) 移动指针:当点P[right]在v方向上的投影小于下一个点(P[right+1])的投影时,right++(模n)。同理,移动left和bottom?实际上,我们需要移动三个指针:分别对应与当前边垂直的两个方向(法向)和当前边方向?不对。 29. 实际上,经典算法中,最小外接矩形通过三个卡壳点:一个在对面(对踵点),两个在侧边。但这里我们不详细展开优化,因为即使不优化,凸包点数不多时,O(n^2)也是可以接受的。 30. 考虑到实现简单,我们先使用O(n^2)的方法,因为凸包点数通常不会太多(如果输入点很多,但凸包点数可以控制)。 31. 非凸多边形:先求凸包。 32. 代码结构: class Point { double x, y; Point(double x, double y) { this.x = x; this.y = y; } } // 计算凸包 public static List<Point> convexHull(List<Point> points) { ... } // 计算最小外接矩形(返回矩形顶点,四个点,按逆时针顺序) public static Point[] minBoundingRectangle(List<Point> points) { // 1. 计算凸包 List<Point> hull = convexHull(points); if (hull.size() < 3) { // 点少于3,无法构成凸包,直接处理 // 如果只有一个点,则矩形退化为点;两个点则为线段 // 这里我们假设凸包点数>=3 // 对于小于3的情况,可以特殊处理,但这里省略 } int n = hull.size(); // 将凸包转为数组,并确保逆时针(我们的凸包算法保证逆时针) Point[] hullPoints = hull.toArray(new Point[0]); double minArea = Double.MAX_VALUE; Point[] minRect = null; // 四个点 // 遍历每条边 for (int i = 0; i < n; i++) { int j = (i+1) % n; Point p1 = hullPoints[i]; Point p2 = hullPoints[j]; double dx = p2.x - p1.x; double dy = p2.y - p1.y; double lengthSquared = dx*dx + dy*dy; if (lengthSquared < 1e-12) continue; // 跳过零长度边 double nx = -dy; // 法向量 double ny = dx; double minV = 0, maxV = 0; double minN = 0, maxN = 0; // 初始化,用第一个点(即p1)的投影 // 注意:以p1为参考点,所以p1的投影为0 minV = maxV = 0; // 因为p1的投影为0 minN = maxN = 0; // 遍历凸包上所有点 for (int k = 0; k < n; k++) { Point p = hullPoints[k]; // 计算相对于p1的向量 double vx = p.x - p1.x; double vy = p.y - p1.y; // 投影到v double projV = vx * dx + vy * dy; // 投影到n double projN = vx * nx + vy * ny; if (k == 0) { minV = maxV = projV; minN = maxN = projN; } else { if (projV < minV) minV = projV; if (projV > maxV) maxV = projV; if (projN < minN) minN = projN; if (projN > maxN) maxN = projN; } } // 计算面积 double area = (maxV - minV) * (maxN - minN) / lengthSquared; if (area < minArea) { minArea = area; // 计算矩形的四个顶点 // 公式:顶点 = p1 + (projV * v + projN * n) / lengthSquared Point[] rect = new Point[4]; // 注意:四个顶点的投影组合: (minV, minN), (minV, maxN), (maxV, maxN), (maxV, minN) double[] projVs = {minV, minV, maxV, maxV}; double[] projNs = {minN, maxN, maxN, minN}; for (int idx = 0; idx < 4; idx++) { double vFactor = projVs[idx] / lengthSquared; double nFactor = projNs[idx] / lengthSquared; double x = p1.x + vFactor * dx + nFactor * nx; double y = p1.y + vFactor * dy + nFactor * ny; rect[idx] = new Point(x, y); } minRect = rect; } } return minRect; } 33. 凸包算法:这里使用Andrew's Monotone Chain算法,返回逆时针凸包点(不包含重复点,且首尾不连接)。 34. 编写凸包算法: public static List<Point> convexHull(List<Point> points) { if (points.size() <= 1) return points; // 按x坐标排序,如果x相同按y Collections.sort(points, (a, b) -> { if (a.x < b.x) return -1; if (a.x > b.x) return 1; return Double.compare(a.y, b.y); }); List<Point> lower = new ArrayList<>(); List<Point> upper = new ArrayList<>(); // 下凸包 for (Point p : points) { while (lower.size() >= 2) { Point a = lower.get(lower.size()-2); Point b = lower.get(lower.size()-1); if (crossProduct(a, b, p) <= 0) { // 顺时针或共线 lower.remove(lower.size()-1); } else { break; } } lower.add(p); } // 上凸包 for (int i = points.size()-1; i>=0; i--) { Point p = points.get(i); while (upper.size() >= 2) { Point a = upper.get(upper.size()-2); Point b = upper.get(upper.size()-1); if (crossProduct(a, b, p) <= 0) { upper.remove(upper.size()-1); } else { break; } } upper.add(p); } // 合并:去掉首尾重复点(下凸包最后一个点是上凸包的第一个点,上凸包最后一个点是下凸包的第一个点) lower.remove(lower.size()-1); upper.remove(upper.size()-1); lower.addAll(upper); return lower; } // 计算叉积:向量ab和向量ac的叉积 private static double crossProduct(Point a, Point b, Point c) { return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); } 35. 注意:凸包算法返回的点是逆时针顺序,且起点是x最小(如果x相同则y最小)的点。 36. 测试:使用一些简单多边形测试,比如正方形、三角形等。 37. 注意:如果输入点共线,则凸包退化为线段,此时最小外接矩形会退化为一条线段(面积为0)。但算法仍然可以处理。 38. 以上代码没有考虑浮点精度问题,实际中可能需要调整。 39. 总结:先求凸包,然后遍历凸包的每条边,计算以该边为基准的矩形,记录最小面积的矩形。 40. 输出:矩形的四个顶点(按逆时针顺序)。 41. 注意:由于浮点运算,可能会出现精度问题,比如面积计算为负数,所以需要确保投影极值计算正确。 42. 另一种方法:使用现成的库,如Java3D或JTS,但这里我们要求自己实现。 43. 实现代码:按照上述结构编写Java代码。 44. 由于代码较长,我们这里只提供关键函数,并假设Point类已定义。 45. 注意:在计算投影时,我们以p1为参考点,这样p1的投影为0,其他点相对p1。 46. 最后,返回的矩形顶点顺序:我们按 (minV,minN), (minV,maxN), (maxV,maxN), (maxV,minN) 的顺序,这样是逆时针(取决于v和n的旋转方向,但这里n是v逆时针转90度,所以这样得到的四个点是逆时针的)。 47. 验证:例如,一个正方形区域,凸包就是四个点,最小外接矩形应该就是它自己。 48. 开始编写代码。 注意:由于这是一个算法实现,我们提供完整的函数,但需要用户自己整合到项目中。 代码实现:</think>### Java实现不规则区域最小外接四边形算法 在计算机图形学中,寻找不规则区域的最小外接四边形(最小面积外接矩形)是一个常见问题。以下是完整的Java实现方案,采用**旋转卡壳法**(Rotating Calipers)结合**凸包计算**,确保高效精确地找到最小面积外接矩形。 --- #### 算法步骤 1. **计算凸包**:将不规则区域转换为凸多边形(Andrew's Monotone Chain算法)。 2. **旋转卡壳法**:遍历凸包每条边作为矩形基准边,计算最小外接矩形面积。 3. **投影计算**:对每个基准边计算凸包点的投影极值,确定矩形边界。 4. **顶点生成**:根据投影值计算矩形的四个顶点。 --- #### Java代码实现 ```java import java.util.*; class Point { public double x, y; public Point(double x, double y) { this.x = x; this.y = y; } @Override public String toString() { return String.format("(%.2f, %.2f)", x, y); } } public class MinimumBoundingQuadrilateral { // 计算叉积:向量AB × 向量AC private static double crossProduct(Point a, Point b, Point c) { return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); } // 计算凸包(Andrew's Monotone Chain算法) public static List<Point> convexHull(List<Point> points) { if (points.size() <= 1) return points; // 按x坐标排序(x相同则按y) Collections.sort(points, (a, b) -> Double.compare(a.x, b.x) != 0 ? Double.compare(a.x, b.x) : Double.compare(a.y, b.y) ); List<Point> lower = new ArrayList<>(); List<Point> upper = new ArrayList<>(); // 计算下凸包 for (Point p : points) { while (lower.size() >= 2) { Point a = lower.get(lower.size() - 2); Point b = lower.get(lower.size() - 1); if (crossProduct(a, b, p) <= 0) { lower.remove(lower.size() - 1); } else break; } lower.add(p); } // 计算上凸包 for (int i = points.size() - 1; i >= 0; i--) { Point p = points.get(i); while (upper.size() >= 2) { Point a = upper.get(upper.size() - 2); Point b = upper.get(upper.size() - 1); if (crossProduct(a, b, p) <= 0) { upper.remove(upper.size() - 1); } else break; } upper.add(p); } // 合并凸包(去除重复端点) lower.remove(lower.size() - 1); upper.remove(upper.size() - 1); lower.addAll(upper); return lower; } // 计算最小外接矩形(返回矩形的四个顶点) public static Point[] minBoundingRectangle(List<Point> points) { // 1. 计算凸包 List<Point> hull = convexHull(points); int n = hull.size(); if (n < 3) throw new IllegalArgumentException("区域点数不足"); double minArea = Double.MAX_VALUE; Point[] minRect = null; // 2. 遍历凸包的每条边 for (int i = 0; i < n; i++) { Point p1 = hull.get(i); Point p2 = hull.get((i + 1) % n); // 计算基准边向量 double dx = p2.x - p1.x; double dy = p2.y - p1.y; double lengthSq = dx * dx + dy * dy; if (lengthSq < 1e-12) continue; // 跳过零长边 // 计算法向量(逆时针旋转90度) double nx = -dy; double ny = dx; // 3. 计算投影极值 double minV = 0, maxV = 0; double minN = 0, maxN = 0; for (int k = 0; k < n; k++) { Point p = hull.get(k); double vx = p.x - p1.x; double vy = p.y - p1.y; // 投影到基准边向量 double projV = vx * dx + vy * dy; // 投影到法向量 double projN = vx * nx + vy * ny; // 更新极值 if (k == 0 || projV < minV) minV = projV; if (k == 0 || projV > maxV) maxV = projV; if (k == 0 || projN < minN) minN = projN; if (k == 0 || projN > maxN) maxN = projN; } // 4. 计算矩形面积 double area = (maxV - minV) * (maxN - minN) / lengthSq; if (area < minArea) { minArea = area; // 5. 计算矩形顶点 Point[] rect = new Point[4]; double[] projVs = {minV, minV, maxV, maxV}; double[] projNs = {minN, maxN, maxN, minN}; for (int j = 0; j < 4; j++) { double factorV = projVs[j] / lengthSq; double factorN = projNs[j] / lengthSq; double x = p1.x + factorV * dx + factorN * nx; double y = p1.y + factorV * dy + factorN * ny; rect[j] = new Point(x, y); } minRect = rect; } } return minRect; } // 示例用法 public static void main(String[] args) { // 定义不规则区域(示例:L形多边形) List<Point> points = Arrays.asList( new Point(0, 0), new Point(2, 0), new Point(2, 1), new Point(1, 1), new Point(1, 2), new Point(0, 2) ); // 计算最小外接矩形 Point[] boundingRect = minBoundingRectangle(points); // 输出结果 System.out.println("最小外接矩形的四个顶点:"); for (Point p : boundingRect) { System.out.println(p); } } } ``` --- #### 算法说明 1. **凸包计算**: - 使用Andrew's Monotone Chain算法,时间复杂度 $O(n \log n)$。 - 将输入点集转换为凸多边形,确保后续计算正确性[^1]。 2. **旋转卡壳法**: - 遍历凸包每条边作为矩形基准边。 - 对每条边计算凸包点的投影极值(基准边方向和法线方向)。 - 根据投影极值计算矩形面积,记录最小面积对应的矩形。 3. **顶点生成**: - 通过投影值反推矩形的四个顶点坐标: $$ \text{顶点} = P_1 + \frac{\text{proj}_V \cdot \vec{V} + \text{proj}_N \cdot \vec{N}}{|\vec{V}|^2} $$ - 顶点按逆时针顺序排列,便于后续图形处理。 4. **复杂度**: - 凸包计算:$O(n \log n)$ - 旋转卡壳:$O(n^2)$(凸包点数通常远小于原始点数) --- #### 测试用例 输入一个L形多边形: ```java List<Point> points = Arrays.asList( new Point(0, 0), new Point(2, 0), new Point(2, 1), new Point(1, 1), new Point(1, 2), new Point(0, 2) ); ``` 输出结果: ``` 最小外接矩形的四个顶点: (0.00, 0.00) (0.00, 2.00) (2.00, 2.00) (2.00, 0.00) ``` --- #### 应用场景 - **计算机视觉**:目标检测中的边界框生成。 - **地理信息系统**:地图要素的最小包围框计算。 - **游戏开发**:碰撞检测的包围盒生成。 - **工业设计**:零件的最小包装盒计算。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值