分治法-最近對問題和凸包問題


前面博客中有用蠻力法解決過最近對問題和凸包問題。

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)。解決上包問題和解決下包問題十分類似,代碼也只有極少的改動,代碼寫得不太好,有很多重復的部分,其實這些應該都可以合並的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM