最少圆覆盖通信覆盖问题-算法分析设计-贪心算法-java实现

问题描述

假设海岸线是一条无限延伸的直线,陆地在海岸线的一侧,海洋在另外一侧。每个小岛相当于海洋侧的一个点。坐落在海岸线上的基站只能覆盖半径为d的范围。应用直角坐标系,将海岸线作为x轴,设海洋侧在x轴上方。给定海洋中各小岛的位置,以及通信基站的覆盖半径,要求用最少的基站覆盖所有小岛,使得每个小岛都能和陆地通过某基站通信(在某个基站覆盖范围内)。
输入:小岛数目,基站覆盖半径,各小岛坐标
输出:覆盖小岛的最少基站数目及基站坐标,如果无解,输出-1.
最少圆覆盖通信覆盖问题-算法分析设计-贪心算法-java实现_第1张图片

问题分析

因为每一个基站点都在x坐标上,每个基站的通信覆盖区域都为固定半径的圆,所以容易知道y坐标绝对值小于该固定半径的小岛一定能被覆盖。
而要想实现用最少的基站(最少的圆)覆盖这些小岛,即基站覆盖的小岛数要尽量多,没有多余的基站,容易想到该问题与最少线段覆盖点非常类似,只是最少线段覆盖点问题是一维的,而最少圆覆盖是二维的,但贪心求解原理都相同。都是将小岛坐标从左至右(或从右至左)排列,逐一画圆并判断覆盖区域范围,但是最少圆覆盖问题的排序,应该是以小岛坐标为圆上一点时能覆盖的左区间从左至右排序更为准确。
最少圆覆盖通信覆盖问题-算法分析设计-贪心算法-java实现_第2张图片如上图,输入小岛坐标S=[S2,S1,S4,S3],按照以各点为圆上一点时能覆盖的左区间从左至右(从小到大)排序后得到S=[S1,S2,S3,S4]。开始作圆,
从左至右,从S1开始,即先画出黄圈,到S2,因为S2在上一圆内,故继续;到S3,因S3在上一圆(黄圈)外,故保留上一圆(黄圈),另作新圆(蓝圈),到S4,因S4在上一圆(蓝圈)内,故继续。
否则,如果只是按照x坐标从左至右排序,即从S2开始作圆,到S1,S1并不在S2所作的红圈内,故作新圆,如此产生多余的圆。

求解思路

以各小岛坐标为半径为d的圆上一点时能覆盖的左区间,按从小到大排序得S集合,从S中依次考察点S[i]的位置,若S[i]不在上一圆C[j]内,则以该坐标为圆上一点、d为半径作新圆C[j+1],直到S中所有坐标考察完为止。
方法:

  1. if 输入的纵坐标大于基站半径d
  2. then return -1 exit
  3. else 按各坐标的左区间从小到大排序得S
  4. C[0]= X0 + sqrt(d * d – Y0 * Y0);
  5. for i←1 to n do
  6. if S[i]不在圆内
  7. then c = S[i].x + sqrt(d * d – S[i].y * S[i].y);
  8. then C←C∪{c}
  9. return C

算法正确性证明

(自己写的可能不太准确)
命题:对输入任何规模为n的输入实例,算法得到最少基站数;
归纳基础:证明对任何只有1个小岛(纵坐标小于基站半径)的输入实例,贪心法得到最优解,S中只有一个岛,则C中只有一个基站,最少基站集合就是C,显然正确;
归纳步骤:证明:假设对于任何n个小岛的输入实例贪心法都能得到最优解,那么对于任何n+1个小岛的输入实例,贪心法也得到最优解;
考虑输入N={1,2,…,n+1},已排序
由归纳假设,对于N’={2,3,…,n+1},
贪心法得到最优解I’,另I=I’∪{1}({1}表示只覆盖第1个小岛的基站)
若不然,存在包含1的关于N的最优解I^(1表示达到最左区间的坐标),且|I |>|I|;那么I*-{1}是N’和C’的解且|I^*-{1}|>|I-{1}|=|I’|,与I’是关于N’和C’的最优解矛盾。

复杂性计算

排序算法执行O(n log⁡n))次,贪心算法执行O(n)时间,总的算法时间T(n)=O(n log⁡n )

结果测试

最少圆覆盖通信覆盖问题-算法分析设计-贪心算法-java实现_第3张图片
最少圆覆盖通信覆盖问题-算法分析设计-贪心算法-java实现_第4张图片

编程实现

(如有错误欢迎指正!)
其中小岛坐标的排序参考了别人用Comparable接口实现Array排序的方法,没有手写排序算法。

package minCirlcle;
import java.util.Arrays;
import java.util.Scanner;

class S2 implements Comparable<S2> {	//小岛坐标及左区间
	private int x;
	private int y;
	private double left;
	public S2(int x, int y, double left) {
		this.setX(x);
		this.setY(y);
		this.setLeft(left);
	}
	public int compareTo(S2 s) {
		return (int) (this.getLeft() - s.left);
	}
	public int getX() {
		return x;
	}
	public void setX(int x) {
		this.x = x;
	}
	public int getY() {
		return y;
	}
	public void setY(int y) {
		this.y = y;
	}
	public double getLeft() {
		return left;
	}
	public void setLeft(double left) {
		this.left = left;
	}
}
class S22 {//基站坐标
	private double x;
	private int y;
	public S22(double x, int y) {
		this.setX(x);
		this.setY(y);
	}
	public double getX() {
		return x;
	}
	public void setX(double x) {
		this.x = x;
	}
	public int getY() {
		return y;
	}
	public void setY(int y) {
		this.y = y;
	}
}
class verify1{	//合理性检验
	private double tempC;
	private int tempX;
	private int tempY;
	private double tempD;
	private int flag;
	private int flagVer;
	public verify1(S2 S[],S22 C[],int d,int n,int m) {
		for(int i=0;i<n;i++) {
			tempX = S[i].getX();
			tempY = S[i].getY();
			flag = 0;
			for(int j=0;j<=m;j++) {
				tempC=C[j].getX();
				tempD=Math.sqrt((tempY * tempY + Math.pow((tempX - tempC), 2)));
				if(tempD>d) {
					flag = 0;
				}else{
					flag = 1;
					break;
				}
			}
			flagVer = flag + flagVer;
		}
		System.out.println("合理性验证结束");
		if(flagVer==n) {
			System.out.println("基站点能够覆盖所有岛屿");
		}else {
			System.out.println("基站点不能覆盖所有岛屿,请检查算法");
		}
	}
}

public class IPDMinCircle {
	private static int n; // 岛数
	private int m; // 基站数
	public S22 C[] = new S22[n]; // 基站坐标
	private double c; // 基站横坐标
	private static int d; // 基站半径
	private int X1;
	private int Y1;
	private int tempX;
	private int tempY;
	private double tempD; // 小岛和旧圆心的距离
	public static int flag;
	public IPDMinCircle(S2 S[],int n,int d) {	
		X1 = S[0].getX();
		Y1 = S[0].getY();
		c = X1 + Math.sqrt(d * d - Y1 * Y1);
		C[0] = new S22(c,0);
		for (int j = 0; j < n; j++) {
			tempX = S[j].getX();
			tempY = S[j].getY();
			tempD = Math.sqrt((tempY * tempY + Math.pow((tempX - c), 2))); // 上一圆心位置,判断该岛坐标是否在上一圆内
			if ((tempD <= d)) { // 新岛坐标在旧圆内,即该岛位于旧圆内或圆上任意位置
				// 保留旧圆,不作新圆
				continue;
			} else if ((tempD > d)) { // 新岛坐标在旧圆外,即该岛位于旧圆心的右边且在圆外
				// 保留旧圆,另作新圆
				c = tempX + Math.sqrt(d * d - tempY * tempY);
				m = m + 1;
				C[m] = new S22(c,0);
			}
		}
		System.out.println(m+1);
		for (int i = 0; i <= m; i++) {
			System.out.println(C[i].getX() + " " + C[i].getY());
		}
		new verify1(S,C,d,n,m);
	}
	public static void main(String args[]) {
		Scanner in = new Scanner(System.in);
		System.out.println("请输入小岛数:");
		n = in.nextInt();
		System.out.println("请输入 基站半径:");
		d = in.nextInt();
		S2 S[] = new S2[n];
		System.out.println("请输入小岛坐标:");
		for (int i = 0; i < n; i++) {
			int k1 = in.nextInt();
			int k2 = in.nextInt();
			double tleft = Math.sqrt(d * d - k2 * k2) + k1 - d; 
			S[i] = new S2(k1, k2, tleft);
		}
		for (int i = 0; i < n; i++) {
			if (Math.abs(S[i].getY()) > d) {
				flag = -1;
				System.out.println(flag);
			} else {
				flag = 1;
			}
		}
		if (flag > 0) {
			Arrays.sort(S, 0, n); // 排n个数,Arrays.sort(d)则默认排全部
//			for (int i = 0; i < n; i++) { //debug
//				System.out.println(S[i].getX()+" "+S[i].getY()); 
//			}
			new IPDMinCircle(S,n,d);
		}
	}
}

你可能感兴趣的:(算法)