光影切割问题
问题描述:
不少人很爱玩游戏,例如 CS 。 游戏设计也成为程序开发的热点之一,我们假设要设计破旧仓库之类的场景作为战争游戏的背景。仓库的地面会因为阳光从屋顶的漏洞或者窗口照射进来而形成许多光照区域和阴影区域。为了简单起见,假设不同区域的边界都是直线 , 我们把这些直线都叫做“光影线”,并且不存在三条光影线相交于一点的情况。
那么,如果我们需要快速计算某个时刻,在 X 坐标[ A, B] 区间的地板上被光影划分成多少块。如何才能写出算法来计算呢?
【解法一】
这个问题需要先自己归纳寻找规律。由于不存在三直线交于一点的情况,一些情况可以不用考虑。题目要求的是在A,B区间内,通过尝试可以找到规律。如果只有一条线,那么分割成两块空间。如果有两条线,可能相交,也可能不相交(只要在A到B这个区间内不相交就行)。如果相交,那么就分割成四块,否则是三块。
以此类推就会发现,分割块数=线的数量+在这个区间内的交点数量+1,其中1为区间初始平面个数。
也就是说,只需要判断在这个区间内的交点有几个,经过这个区间的线有几条即可。我们可以先进行一次预处理,计算出所有交点的位置,然后在每一次查询时就可以很快查找到了。
【解法二】
进一步分析图1-12的右图可以知道,区域内的交点数目就等于一个边界上交点顺序相对另一个边界交点顺序的逆序总数(这里利用到条件“没有三条直线相交于一个点”)。在右图中,左边界顺序为(a,b,c),右边界为(c,b,a),假设a=1,b=2,c=3,那么(c,b,a)=(3,2,1),它的逆序数为3。
因此,问题转化为求一个N个元素的数组的逆序数。
最直接的求解逆序数方法还是O(N2),如果用分治的策略可以将时间复杂度降为O(N*log2N),求N个元素的逆序数的分治思想如下,首先求前N/2个元素的逆序数,再求后N/2个元素的逆序数,最后在排序过程中合并前后两部分之间的逆序数。
【方法一】直接求解逆序数算法:
package chapter1youxizhileRevertSortCount; /** * 光影切割问题 * 寻找逆序数的直接求法 * @author DELL * */ public class RevertSortCount1 { private int[] a; //寻找逆序数的数组 //构造方法 public RevertSortCount1(int[] a){ this.a = a; } /** * 直接寻找逆序数 * @return */ public int count(){ System.out.print("逆序对为:"); int i,j,cnt=0; for(i=0;i<a.length;i++){ for(j=0;j<i;j++){ if(a[j]>a[i]){ cnt++; System.out.print("("+a[j]+", "+a[i]+") "); } } } System.out.println(); return cnt; } public static void main(String[] args) { int[] a = {3, 5, 4, 8, 2, 6, 9}; RevertSortCount1 rsc = new RevertSortCount1(a); System.out.println("逆序数为:"+rsc.count()); } }
程序运行结果如下:
逆序对为:(5, 4) (3, 2) (5, 2) (4, 2) (8, 2) (8, 6)
逆序数为:6
【方法二】归并的思想
package chapter1youxizhileRevertSortCount; /** * 光影切割问题 * 寻找逆序数的归并算法 * @author DELL * */ public class RevertSortCount2 { private int[] a; //寻找逆序数的数组 private int count = 0; //逆序数 //构造方法 public RevertSortCount2(int[] a){ this.a = a; System.out.print("逆序对为:"); mergeSort(a,0,a.length-1); } /** * 归并排序寻找逆序数 * 合并两个有序数组 * @param a 待排序数组 * @param first 第一个有序数组在a中的起始位置 * @param mid 第一个数组的最后一个位置 * @param last 第二个数组的最后一个位置 */ public void merge(int[] a, int first, int mid, int last){ int[] b; //临时数组 b = new int[a.length]; int i=first,j=mid+1,k=first; while(i<=mid&&j<=last){ if(a[i]<=a[j]){ b[k++]=a[i++]; }else{ for(int l=i;l<=mid;l++){ //输出逆序对 System.out.print("("+a[l]+", "+a[j]+") "); } b[k++]=a[j++]; count += mid-i+1; //记录逆序数 } } while(i<=mid){ //把剩余的添加到b中 b[k++]=a[i++]; } while(j<=last){ //把剩余的添加到b中 b[k++]=a[j++]; } for(i=first;i<=last;i++){ //把排好序的数组重新赋给a a[i]=b[i]; } } /** * 归并排序 * @param a 待排序数组 * @param first 数组起始位置 * @param last 数组结束位置 */ public void mergeSort(int[] a, int first, int last){ int mid = (first+last)/2; //获取中间位置 if(first==last) return; mergeSort(a,first,mid); mergeSort(a,mid+1,last); merge(a,first,mid,last); } public static void main(String[] args) { int[] a = {3, 5, 4, 8, 2, 6, 9}; System.out.print("原数组为:"); for(int i=0;i<a.length;i++){ System.out.print(a[i]+" "); } System.out.println(); RevertSortCount2 rsc = new RevertSortCount2(a); System.out.print(" 排好序的数组为:"); for(int i=0;i<a.length;i++){ System.out.print(a[i]+" "); } System.out.println(); System.out.println("逆序数为:"+rsc.count); } }
程序运行结果如下:
原数组为:3 5 4 8 2 6 9 逆序对为:(5, 4) (3, 2) (4, 2) (5, 2) (8, 2) (8, 6) 排好序的数组为:2 3 4 5 6 8 9 逆序数为:6