• 分治法-最近对问题和凸包问题


    前面博客中有用蛮力法解决过最近对问题和凸包问题。

    4.6.1 最近对问题

    设P1,P2,P3,…,Pn是平面上n个点构成的集合S,解决问题之前,假定这些点都是按照它们的x轴坐标升序排列的。我们可以画一条垂直线x=c,将这些点分为两个包含n/2个点的子集S1、S2,分别位于直线x=c的两侧。遵循分治的思想,分别递归的求出S1、S2的最近对,比如d1、d2,并设d=min{d1,d2}。此时d并不是所有点对的最小距离,离分界线x=c为d的两侧仍可能存在更小距离的点对,因此我们还需在以x=c对称、宽度为2d的垂直带中检查是否存在这样的点对。设C1、C2分别是该垂直带位于直线x=c两侧的点集,对于C1中的每个点P(x0,y0),我们都需要检查C2中的点是否小于d。显然,这种点坐标y应在区间(y-d,y+d)内。

    代码实现:

    /**
         * 分治法解决最近对问题
         * @author xiaofeig
         * @since 2015.9.19
         * @param 目标点集(要求是按x坐标排好序的)
         * @return 返回最近距离的两点下标以及最近距离的平方
         * */
        public static int[] closestPoints(Point[] points){
            if(points==null||points.length==0||points.length==1){
                return null;
            }else if(points.length==2){
                int d=(points[0].x-points[1].x)*(points[0].x-points[1].x)+(points[0].y-points[1].y)*(points[0].y-points[1].y);
                return new int[]{0,1,d};
            }
            int c=points.length/2;
            Point[] leftPart=Arrays.copyOfRange(points, 0, c);
            Point[] rightPart=Arrays.copyOfRange(points, c, points.length);
            int[] leftResult=closestPoints(leftPart);
            int[] rightResult=closestPoints(rightPart);
            int[] result;//最终结果
            if(leftResult==null){
                result=rightResult;
                //子集下标恢复成母集下标
                result[0]=result[0]+c;
                result[1]=result[1]+c;
            }else if(rightResult==null){
                result=leftResult;
            }else{
                if(leftResult[2]<rightResult[2]){
                    result=leftResult;
                }else{
                    result=rightResult;
                    //子集下标恢复成母集下标
                    result[0]=result[0]+c;
                    result[1]=result[1]+c;
                }
            }
            
            //比较位于x=c两侧的垂直带中的点距
            int leftIndex;
            for(leftIndex=c-1;leftIndex>=0;leftIndex--){
                if(points[leftIndex].x<=points[c].x-result[2]){
                    break;
                }
            }
            leftIndex++;
            int rightIndex;
            for(rightIndex=c;rightIndex<points.length;rightIndex++){
                if(points[rightIndex].x>=points[c].x+result[2]){
                    break;
                }
            }
            rightIndex--;
            while(leftIndex<c){
                int index=c;
                while(index<=rightIndex){
                    int d=(points[leftIndex].x-points[index].x)*(points[leftIndex].x-points[index].x)+(points[leftIndex].y-points[index].y)*(points[leftIndex].y-points[index].y);
                    if(d<result[2]){
                        result[0]=leftIndex;
                        result[1]=index;
                        result[2]=d;
                    }
                    index++;
                }
                leftIndex++;
            }
            return result;
        }

    由于点集需要按照x坐标排序,这里顺便也给出合并排序的代码:

    /**
         * 合并排序,按照x坐标
         * @author xiaofeig
         * @since 2015.9.17
         * @param array 要排序的点集
         * */
        public static void quickSort(Point[] array){
            if(array.length>1){
                int s=partition(array);
                Point[] leftPart=Arrays.copyOfRange(array, 0, s);
                Point[] rightPart=Arrays.copyOfRange(array, s+1, array.length);
                quickSort(leftPart);
                quickSort(rightPart);
                //合并中轴元素的左右两部分,包括中轴元素
                for(int i=0;i<leftPart.length;i++){
                    array[i]=leftPart[i];
                }
                for(int i=0;i<rightPart.length;i++){
                    array[i+leftPart.length+1]=rightPart[i];
                }
            }
        }
        /**
         * 合并排序划分区
         * @author xiaofeig
         * @since 2015.9.17
         * @param array 要分区的数组
         * @return 返回中轴元素的最终下标
         * */
        public static int partition(Point[] array){
            int i=0,j=array.length;
            do{
                do{
                    i++;
                }while(i<array.length-1&&array[i].x<array[0].x);
                do{
                    j--;
                }while(j>1&&array[j].x>array[0].x);
                Point temp=array[i];
                array[i]=array[j];
                array[j]=temp;
            }while(i<j);
            Point temp=array[i];
            array[i]=array[j];
            array[j]=temp;
            if(array[j].x<=array[0].x){//中轴右侧元素均大于中轴元素时无需交换
                temp=array[0];
                array[0]=array[j];
                array[j]=temp;
            }else{
                j--;//中轴右侧元素均大于中轴元素时须将j值降至0
            }
            return j;
        }

    算法分析:

    关于该算法对n个预排序点的运行时间,与如下递推式:

    T(n)=2T(n/2)+M(n),M(n)是合并较小子问题所用的时间

    可以得出T(n)属于O(nlogn)。

     

    4.6.2 凸包问题

    这次我们讨论的是用分治算法解决凸包问题,这个算法也称为快包,因为它的操作和快速排序的操作十分类似。假设P1,P2,…,Pn是平面上n>1个点构成的集合S,且这些点都是按照x轴坐标升序排列的,则可以证明位于最左边和最右边的P2,Pn一定是该集合的凸包顶点。向量P1Pn把点分为两个集合:向量P1Pn左侧和右侧的点分别构成的集合S1,S2,我们将凸包位于向量P1Pn左侧的部分成为上包,位于右侧的部分成为下包

    先来说说如何构建上包,如果S1为空,则线段P1P2就是上包;如果不为空,我们可以在S1中找到距离直线P1Pn最远的点Pmax,也就是在S1中找到一个点Pmax使三角形PmaxP1Pn的面积最大。可以证明如下几点:

    • Pmax是上包的顶点
    • 包含在三角形PmaxP1Pn之中的点都不可能是上包的顶点

      clip_image001

    当找到Pmax之后,我们可以令Pn=Pmax,继续以递归的方式寻找位于P1Pn上侧的凸包顶点。

    至于如何判断三角形P1P2P3的面积是否是最大的,可以通过如下行列式来判断,它等于行列式绝对值的1/2:

    image

    当P3(x3,y3)位于向量P1P2左侧时,表达式符号为正;位于右侧时,符号为负(正负可以判断S1,S2)。

    代码实现:

    /**
         * 分治法解决凸包问题
         * @author xiaofeig
         * @since 2015.9.20
         * @param points 目标点集(要求是按x坐标排好序的)
         * @return 返回有序的极点集合
         * */
        public static List<Integer> convexHull(Point[] points){
            List<Integer> indexs=new LinkedList<Integer>();
            for(int i=0;i<points.length;i++){
                indexs.add(i);
            }
            List<Integer> upperHull=upperHull(points, indexs);//得到上包结果
            List<Integer> lowerHull=lowerHull(points, indexs);//得到下包结果
            
            //将上包结果和下包结果合并,并返回
            List<Integer> result=new LinkedList<Integer>();
            result.add(indexs.get(0));
            for(int i=0;i<lowerHull.size();i++){
                result.add(lowerHull.get(i));
            }
            result.add(indexs.get(indexs.size()-1));
            for(int i=0;i<upperHull.size();i++){
                result.add(upperHull.get(i));
            }
            return result;
        }
        
        /**
         * 分治法解决上包问题
         * @author xiaofeig
         * @since 2015.9.20
         * @param points 目标点集(要求是按x坐标排好序的)
         * @param indexs 目标点集序列
         * @return 返回有序的点集序列
         * */
        public static List<Integer> upperHull(Point[] points,List<Integer> indexs){
            int dmax=0;//记录最大距离
            Integer pmax=0;//记录最大距离那点的下标
            
            Point p1=points[indexs.get(0)];//分界向量起点
            Point p2=points[indexs.get(indexs.size()-1)];//分界向量终点
            
            List<Integer> newIndexs=new LinkedList<Integer>();//位于分界向量左侧的点集下标
            for(int i=1;i<indexs.size()-1;i++){
                int d=p1.x*p2.y+points[indexs.get(i)].x*p1.y+p2.x*points[indexs.get(i)].y-points[indexs.get(i)].x*p2.y-p2.x*p1.y-p1.x*points[indexs.get(i)].y;
                if(d>0){
                    newIndexs.add(indexs.get(i));
                    if(d>dmax){
                        dmax=d;
                        pmax=indexs.get(i);
                    }
                }
            }
            if(pmax==0){
                return new LinkedList<Integer>();
            }
            //构建新目标点集序列
            List<Integer> newIndexs1=new LinkedList<Integer>();
            List<Integer> newIndexs2=new LinkedList<Integer>();
            newIndexs1.add(pmax);
            newIndexs2.add(indexs.get(0));
            for(Integer i:newIndexs){
                newIndexs1.add(i);
                newIndexs2.add(i);
            }
            newIndexs1.add(indexs.get(indexs.size()-1));
            newIndexs2.add(pmax);
            
             //处理结果点集序列
            List<Integer> result1=upperHull(points, newIndexs1);
            List<Integer> result2=upperHull(points, newIndexs2);
            result1.add(pmax);
            for(Integer i:result2){
                result1.add(i);
            }
            return result1;
        }
        
        /**
         * 分治法解决下包问题
         * @author xiaofeig
         * @since 2015.9.20
         * @param points 目标点集(要求是按x坐标排好序的)
         * @param indexs 目标点集序列
         * @return 返回有序的点集序列
         * */
        public static List<Integer> lowerHull(Point[] points,List<Integer> indexs){
            int dmin=0;//记录最大距离
            Integer pmin=0;//记录最大距离那点的下标
            
            Point p1=points[indexs.get(0)];//分界向量起点
            Point p2=points[indexs.get(indexs.size()-1)];//分界向量终点
            
            List<Integer> newIndexs=new LinkedList<Integer>();//位于分界向量左侧的点集下标
            for(int i=1;i<indexs.size()-1;i++){
                int d=p1.x*p2.y+points[indexs.get(i)].x*p1.y+p2.x*points[indexs.get(i)].y-points[indexs.get(i)].x*p2.y-p2.x*p1.y-p1.x*points[indexs.get(i)].y;
                if(d<0){
                    newIndexs.add(indexs.get(i));
                    if(d<dmin){
                        dmin=d;
                        pmin=indexs.get(i);
                    }
                }
            }
            if(pmin==0){
                return new LinkedList<Integer>();
            }
            //构建新目标点集序列
            List<Integer> newIndexs1=new LinkedList<Integer>();
            List<Integer> newIndexs2=new LinkedList<Integer>();
            newIndexs1.add(indexs.get(0));
            newIndexs2.add(pmin);
            for(Integer i:newIndexs){
                newIndexs1.add(i);
                newIndexs2.add(i);
            }
            newIndexs1.add(pmin);
            newIndexs2.add(indexs.get(indexs.size()-1));
            
             //处理结果点集序列
            List<Integer> result1=lowerHull(points, newIndexs1);
            List<Integer> result2=lowerHull(points, newIndexs2);
            result1.add(pmin);
            for(Integer i:result2){
                result1.add(i);
            }
            return result1;
        }

    算法分析:

    快报有着和快速排序相同的最差效率θ(n2)。解决上包问题和解决下包问题十分类似,代码也只有极少的改动,代码写得不太好,有很多重复的部分,其实这些应该都可以合并的。

    宁可孤独,也不违心。宁可抱憾,也不将就。
  • 相关阅读:
    可爱精灵宝贝 DP/爆搜
    那些年留的坑
    吃某种零食ing
    NOIP模拟测试13
    NOIP模拟测试12
    NOIP模拟测试11
    大佬 (数学)
    BZOJ3331 BZOJ2013 压力
    LOJ2586 APIO2018 选圆圈
    BZOJ3398 牡牛和牝牛
  • 原文地址:https://www.cnblogs.com/fei-er-blog/p/4827137.html
Copyright © 2020-2023  润新知