《python算法教程》Day11 - 分治法求解平面凸包問(wèn)題

這是《python算法教程》的第11篇讀書(shū)筆記醉蚁,筆記主要內(nèi)容是使用分治法求解凸包。

平面凸包問(wèn)題簡(jiǎn)介

在一個(gè)平面點(diǎn)集中,尋找點(diǎn)集最外層的點(diǎn)煌珊,由這些點(diǎn)所構(gòu)成的凸多邊形能將點(diǎn)集中的所有點(diǎn)包圍起來(lái)私杜。
如下圖所示蚕键,紅色的點(diǎn)能將點(diǎn)集中所有的點(diǎn)包圍起來(lái)。


convexHull.png

分治法求解思路

按照暴力法的思路(求出所有由點(diǎn)集任意兩點(diǎn)的直線(xiàn)衰粹,再獲取使得點(diǎn)集剩余的點(diǎn)在該直線(xiàn)的一側(cè)的直線(xiàn))去求解凸包問(wèn)題锣光,顯然算法復(fù)雜度達(dá)到了n^3,這并不是在時(shí)間復(fù)雜度上可以接受的算法铝耻。
因此誊爹,可考慮使用分治法去求解凸包。大體思路如下:
1.找出由橫坐標(biāo)最大瓢捉、最小的兩個(gè)點(diǎn)p1p2所組成的直線(xiàn)频丘。用該直線(xiàn)將點(diǎn)集分成上下兩set1,set2部分泡态。
2.分別從set1搂漠、set2找出與線(xiàn)段p1p2構(gòu)成的面積最大的三角形的點(diǎn)p3,p4。
3.從set1找出在直線(xiàn)p1p3左側(cè)的點(diǎn)集leftset1某弦、在直線(xiàn)p3p2右側(cè)的點(diǎn)集[圖片上傳中...(行列式.JPG-bc60bb-1525191974104-0)]
rightset1状答。
4將leftset1,leftset2重復(fù)2、3步驟刀崖,直至找不到在直線(xiàn)更外側(cè)的點(diǎn)惊科。
5.從set2找出在直線(xiàn)p1p4左側(cè)的點(diǎn)集leftset2、在直線(xiàn)p3p4右側(cè)的點(diǎn)集rightset2亮钦。
6.將leftset1,leftset2重復(fù)2馆截、3步驟,直至找不到在直線(xiàn)更外側(cè)的點(diǎn)蜂莉。

點(diǎn)與直線(xiàn)的位置判斷

可通過(guò)以下行列式的正負(fù)值判斷直線(xiàn)與點(diǎn)之間的位置關(guān)系蜡娶,同時(shí)數(shù)值為點(diǎn)與線(xiàn)段所圍成的三角形的面積:


image.png

下圖表明了若點(diǎn)在直線(xiàn)外圍(圖中用線(xiàn)段表示直線(xiàn)),上述行列式的值的正負(fù)性映穗。
有一點(diǎn)需要注意窖张,下圖成立的前提條件是組成直線(xiàn)的兩個(gè)點(diǎn)(x1,y1)和(x2蚁滋,y2)必須滿(mǎn)足x1<x2宿接,點(diǎn)(x3赘淮,y3)必須是判斷與直線(xiàn)關(guān)系的點(diǎn)。


position.jpg

代碼示例

下面的代碼示例中加入了繪制散點(diǎn)圖的代碼睦霎,便于觀(guān)察每一步的情況以及查看最終結(jié)果梢卸。

#遞歸法求解凸包
import random
import matplotlib.pyplot as plt


#通過(guò)計(jì)算三角形p1p2p3的面積(點(diǎn)在直線(xiàn)左邊結(jié)果為正,直線(xiàn)右邊結(jié)果為負(fù))來(lái)判斷 p3相對(duì)于直線(xiàn)p1p2的位置
def calTri(p1,p2,p3):
    size=p1[0]*p2[1]+p2[0]*p3[1]+p3[0]*p1[1]-p3[0]*p2[1]-p2[0]*p1[1]-p1[0]*p3[1]
    return size


#找出據(jù)直線(xiàn)最遠(yuǎn)的點(diǎn)(該點(diǎn)與直線(xiàn)圍成的三角形的面積為正且最大)
def maxSize(seq,dot1,dot2,dotSet):
    maxSize=float('-inf')
    maxDot=()
    online=[]
    maxSet=[]
    for u in seq:
        size=calTri(dot1,dot2,u)
        #判斷點(diǎn)u是否能是三角形u dot1 dot2 的面積為正
        if size<0:
            continue
        elif size==0:
            online.append(u)
        #若面積為正副女,則判斷是否是距離直線(xiàn)最遠(yuǎn)的點(diǎn)
        if size>maxSize:
            if len(maxDot)>0:
                maxSet.append(maxDot)
            maxSize=size
            maxDot=u
        else:
            maxSet.append(u)
    #結(jié)果判斷
    #maxSet為空
    if not maxSet:
        #沒(méi)找到分割點(diǎn),同時(shí)可能有點(diǎn)落在直線(xiàn)dot1 dot2上
        if not maxDot:
            dotSet.extend(online)
            return [],()
        #有分割點(diǎn)
        else:
            dotSet.append(maxDot)
            return [],maxDot
    #maxSet不為空
    else:
        dotSet.append(maxDot)
        return maxSet,maxDot


#找出據(jù)直線(xiàn)最遠(yuǎn)的點(diǎn)(該點(diǎn)與直線(xiàn)圍成的三角形的面積為負(fù)數(shù)且最大)
def minSize(seq,dot1,dot2,dotSet):
    minSize=float('inf')
    minDot=()
    online=[]
    minSet=[]
    for u in seq:
        size=calTri(dot1,dot2,u)
        #判斷點(diǎn)u是否能是三角形u dot1 dot2 的面積為負(fù)
        if size>0:
            continue
        elif size==0:
            online.append(u)
         #若面積為負(fù)蛤高,則判斷是否是距離直線(xiàn)最遠(yuǎn)的點(diǎn)
        if size<minSize:
            if len(minDot)>0:
                minSet.append(minDot)
            minDot=u
            minSize=size
        else:
            minSet.append(u)
    #結(jié)果判斷
    #maxSet為空
    if not minSet:
        #沒(méi)找到分割點(diǎn),同時(shí)可能有點(diǎn)落在直線(xiàn)dot1 dot2上
        if not minDot:
            dotSet.extend(online)
            return [],()
        #有分割點(diǎn)
        else:
            dotSet.append(minDot)
            return [],minSet
    #maxSet不為空
    else:
        dotSet.append(minDot)
        return minSet,minDot

    
    
#上包的遞歸劃分
def divideUp(seq,dot1,dot2,dot3,dot4,dotSet=None):
    print(dot1,dot2,dot3,dot4)
    #初始化第一次運(yùn)行時(shí)的參數(shù)
    if len(seq)==0:
        return dotSet
    if dotSet is None:
        dotSet=[]
    if len(seq)==1:
        dotSet.append(seq[0])
        return dotSet
    leftSet,rightSet=[],[]
    #劃分上包左邊的點(diǎn)集
    leftSet,maxDot=maxSize(seq,dot1,dot2,dotSet)
    
    #繪圖檢測(cè)---------------------------------------------------------------
    plt.title('up_left')
    #plt.axis([-20,20,-20,20])
    plt.axis([-1100,1100,-1100,1100])
    #plt.scatter([d[0] for d in seq0],[d[1] for d in seq0],color='black')
    plt.scatter([d[0] for d in seq],[d[1] for d in seq],color='blue')
    plt.scatter([dot1[0],dot2[0]],[dot1[1],dot2[1]],color='orange')
    if maxDot:
        plt.scatter(maxDot[0],maxDot[1],color='red')
    plt.show()
    #----------------------------------------------------------------------
    
    #對(duì)上包左包的點(diǎn)集進(jìn)一步劃分
    if leftSet:
        divideUp(leftSet,dot1,maxDot,maxDot,dot2,dotSet)
    
    #劃分上包右邊的點(diǎn)集
    rightSet,maxDot=maxSize(seq,dot3,dot4,dotSet)
        
    #繪圖檢測(cè)---------------------------------------------------------------
    plt.title('up_right')
    #plt.axis([-20,20,-20,20])
    plt.axis([-1100,1100,-1100,1100])
    #plt.scatter([d[0] for d in seq0],[d[1] for d in seq0],color='black')
    plt.scatter([d[0] for d in seq],[d[1] for d in seq],color='blue')
    plt.scatter([dot3[0],dot4[0]],[dot3[1],dot4[1]],color='orange')
    if maxDot:
        plt.scatter(maxDot[0],maxDot[1],color='red')
    plt.show()
    #----------------------------------------------------------------------
    
    #對(duì)上包右包的點(diǎn)集進(jìn)一步劃分
    if rightSet:
        divideUp(rightSet,dot3,maxDot,maxDot,dot4,dotSet)
    
    return dotSet


#下包的遞歸劃分
def divideDown(seq,dot1,dot2,dot3,dot4,dotSet=None):
    #初始化第一次運(yùn)行時(shí)的參數(shù)
    if len(seq)==0:
        return dotSet
    if dotSet is None:
        dotSet=[]
    if len(seq)==1:
        dotSet.append(seq[0])
        return dotSet
    leftSet,rightSet=[],[]
    #劃分下包左邊的點(diǎn)集
    leftSet,minDot=minSize(seq,dot1,dot2,dotSet)

    
    #繪圖檢測(cè)---------------------------------------------------------------
    plt.title('down_left')
    #plt.axis([-20,20,-20,20])
    plt.axis([-1100,1100,-1100,1100])
    #plt.scatter([d[0] for d in seq0],[d[1] for d in seq0],color='black')
    plt.scatter([d[0] for d in seq],[d[1] for d in seq],color='blue')
    plt.scatter([dot1[0],dot2[0]],[dot1[1],dot2[1]],color='orange')
    if minDot:
        plt.scatter(minDot[0],minDot[1],color='red')
    plt.show()
    #----------------------------------------------------------------------
    
    #對(duì)下包的左包進(jìn)行進(jìn)一步劃分
    if leftSet:
        divideDown(leftSet,dot1,minDot,minDot,dot2,dotSet)
    
    #劃分下包右包的點(diǎn)集
    rightSet,minDot=minSize(seq,dot3,dot4,dotSet)
        
    #繪圖檢測(cè)---------------------------------------------------------------
    plt.title('down_right')
    #plt.axis([-20,20,-20,20])
    plt.axis([-1100,1100,-1100,1100])
    #plt.scatter([d[0] for d in seq0],[d[1] for d in seq0],color='black')
    plt.scatter([d[0] for d in seq],[d[1] for d in seq],color='blue')
    plt.scatter([dot3[0],dot4[0]],[dot3[1],dot4[1]],color='orange')
    if minDot:
        plt.scatter(minDot[0],minDot[1],color='red')
    plt.show()
    #----------------------------------------------------------------------
    
    #對(duì)下包的右包進(jìn)一步劃分
    if rightSet:
        divideDown(rightSet,dot3,minDot,minDot,dot4,dotSet)
    
    return dotSet
    
    
#遞歸主函數(shù)
def mainDivide(seq):
    #將序列中的點(diǎn)按橫坐標(biāo)升序排序
    seq.sort()
    res=[]
    #獲取橫坐標(biāo)做大、最小的點(diǎn)及橫坐標(biāo)中位數(shù)
    dot1=seq[0]
    dot2=seq[-1]
    seq1=[]
    maxSize=float('-inf')
    maxDot=()
    seq2=[]
    minSize=float('inf')
    minDot=()
    #med_x=(seq[len(seq)//2][0]+seq[-len(seq)//2-1][0])/2
    #對(duì)序列劃分為直線(xiàn)dot1 dot2左右兩側(cè)的點(diǎn)集并找出兩個(gè)點(diǎn)集的距直線(xiàn)最遠(yuǎn)點(diǎn)
    for u in seq[1:-1]:
        size=calTri(dot1,dot2,u)
        if size>0:
            if size>maxSize:
                if len(maxDot)>0:
                    seq1.append(maxDot)
                maxSize=size
                maxDot=u
                continue
            else:
                seq1.append(u)
        elif size<0:
            if size<minSize:
                if len(minDot)>0:
                    seq2.append(minDot)
                minSize=size
                minDot=u
                continue
            else:
                seq2.append(u)
    print('seq1',seq1,maxDot)
    print('seq2',seq2,minDot)
    #調(diào)用內(nèi)建遞歸函數(shù)
    res1=divideUp(seq1,dot1,maxDot,maxDot,dot2)
    res2=divideDown(seq2,dot1,minDot,minDot,dot2)
    if res1 is not None:
        res.extend(res1)
    if res2 is not None:
        res.extend(res2)
    for u in [dot for dot in [dot1,dot2,maxDot,minDot] if len(dot)>0]:
        res.append(u)
    return res
    

seq0=[(random.randint(-1000,1000),random.randint(-1000,1000)) for x in range(20)]
seq0=list(set(seq0))
res=mainDivide(seq0)
print('res',sorted(res))
plt.axis([-1100,1100,-1100,1100])
plt.title("overview")
plt.scatter([dot[0] for dot in seq0],[dot[1] for dot in seq0],color='black')
plt.scatter([dot[0] for dot in res],[dot[1] for dot in res],color='red')
plt.show()

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末碑幅,一起剝皮案震驚了整個(gè)濱河市戴陡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌沟涨,老刑警劉巖恤批,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異拷窜,居然都是意外死亡开皿,警方通過(guò)查閱死者的電腦和手機(jī)涧黄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)篮昧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人笋妥,你說(shuō)我怎么就攤上這事懊昨。” “怎么了春宣?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵酵颁,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我月帝,道長(zhǎng)躏惋,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任嚷辅,我火速辦了婚禮簿姨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘簸搞。我一直安慰自己扁位,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布趁俊。 她就那樣靜靜地躺著域仇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪寺擂。 梳的紋絲不亂的頭發(fā)上暇务,一...
    開(kāi)封第一講書(shū)人閱讀 51,727評(píng)論 1 305
  • 那天泼掠,我揣著相機(jī)與錄音,去河邊找鬼般卑。 笑死武鲁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蝠检。 我是一名探鬼主播沐鼠,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼叹谁!你這毒婦竟也來(lái)了饲梭?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤焰檩,失蹤者是張志新(化名)和其女友劉穎憔涉,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體析苫,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡兜叨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了衩侥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片国旷。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖茫死,靈堂內(nèi)的尸體忽然破棺而出跪但,到底是詐尸還是另有隱情,我是刑警寧澤峦萎,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布屡久,位于F島的核電站,受9級(jí)特大地震影響爱榔,放射性物質(zhì)發(fā)生泄漏被环。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一详幽、第九天 我趴在偏房一處隱蔽的房頂上張望筛欢。 院中可真熱鬧,春花似錦妒潭、人聲如沸悴能。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)漠酿。三九已至,卻和暖如春谎亩,著一層夾襖步出監(jiān)牢的瞬間炒嘲,已是汗流浹背宇姚。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留夫凸,地道東北人浑劳。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像夭拌,于是被迫代替她去往敵國(guó)和親魔熏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 1.概念 凸包(Convex Hull)是一個(gè)計(jì)算幾何(圖形學(xué))中的概念鸽扁。用不嚴(yán)謹(jǐn)?shù)脑?huà)來(lái)講蒜绽,給定二維平面上的點(diǎn)集,...
    三三de酒閱讀 3,927評(píng)論 0 1
  • 歸去來(lái)兮桶现。 1.1 說(shuō)明 本篇為《挑戰(zhàn)程序設(shè)計(jì)競(jìng)賽(第2版)》[http://www.ituring.com.cn...
    尤汐Yogy閱讀 14,342評(píng)論 0 160
  • Divide-and-Conquer算法的設(shè)計(jì) 設(shè)計(jì)過(guò)程分為三個(gè)階段: Divide:整個(gè)問(wèn)題劃分為多個(gè)子問(wèn)題 C...
    三三de酒閱讀 3,276評(píng)論 0 4
  • 判斷點(diǎn)是否在線(xiàn)段上躲雅、判斷兩條線(xiàn)段是否相交 這里采用向量的解法。有2個(gè)概念:向量的內(nèi)積和外積骡和。內(nèi)積又稱(chēng)為點(diǎn)積dot ...
    無(wú)令便逐風(fēng)閱讀 889評(píng)論 0 2
  • 非常感謝蒙蒙提出的這個(gè)接龍的建議相赁,演講群也用到了這種,群的參與度一下提升了很多慰于。每個(gè)人都留下痕跡钮科,知道自己的位置。...
    24K超超老師閱讀 587評(píng)論 0 0