Haskell lesson:實現Graham掃描算法

作爲Haskell方面第一次上手練習,儘量不用到庫裏面的函數,然後通過一步一步實現Graham掃描算法,來熟悉haskell函數運行機制,瞭解調試過程,最重要的是要對函數的型態瞭然於胸。

參考書籍選自《Real World Haskell》和《Haskell趣學指南》。

一、問題描述

  • 利用Graham掃描算法求N個點的凸包(http://wiki.mbalib.com/wiki/%E5%87%B8%E5%8C%85)。
  • 凸包的定義:給定平面上的一個(有限)點集(即一組點),這個點集的凸包就是包含點集中所有點的最小面積的凸多邊形。

二、通過Haskell實現Graham算法

本次練習構造瞭如下幾個函數用來輔助計算,最高層次爲grahamHull,通過該函數獲得最終結果。函數列舉如下:

  • 從一個二維座標點構成的集合中取出最小的點,最小的點定義爲具有最小y值的點,如果多個點具有最小y值,那麼在y最小的點中取x最小的點。
getMinDot :: (Num a, Ord a) => [(a,a)] -> (a,a)
getMinDot ((x,y):pairs) =
  if pairs == []
  then (x,y)
  else if or [y<snd (getMinDot pairs), and [y==snd (getMinDot pairs), x<fst (getMinDot pairs)]]
  then (x,y)
  else getMinDot pairs
  • 把二維座標點構成的集合以列表形式排列,並把最小的點放到開頭。
getNewList :: (Num a, Ord a) => [(a,a)] -> [(a,a)]
getNewList l = getMinDot l : [(x,y)| (x,y)<-l, (x,y) /= getMinDot l]
  • 判斷任意三個點方位走向
data Direction = DirLeft | DirRight | DirStraight deriving (Show, Eq)

getDir :: (Num a, Ord a) => (a,a) -> (a,a) -> (a,a) -> Direction
getDir (x1,y1) (x2,y2) (x3,y3) = let (xa,ya) = (x2-x1,y2-y1)
                                     (xb,yb) = (x3-x1,y3-y1) in
                                   if xa*yb-xb*ya > 0
                                   then DirLeft
                                   else if xa*yb-xb*ya < 0
                                   then DirRight
                                   else DirStraight
  • 判斷三個以上點連成的線段的方位走向
getDirList :: (Num a, Ord a) => [(a,a)] -> [Direction]
getDirList ((x1,y1):(x2,y2):(x3,y3):dotlist) =
  if dotlist == []
  then getDir (x1,y1) (x2,y2) (x3,y3):[]
  else getDir (x1,y1) (x2,y2) (x3,y3) : getDirList ((x2,y2):(x3,y3):dotlist)
  • 根據每個點的極角大小進行排序(fast sort)
sortByAngle :: (Num a, Ord a) => [(a,a)] -> [(a,a)]
sortByAngle l = let newList = getNewList l in
                     case newList of
                       ((origX,origY):(x0,y0):dotlist) -> (origX,origY):mySort ((origX,origY):(x0,y0):dotlist)
                         where mySort ((srcX,srcY):(x0,y0):dotlist) =
                                 if dotlist == []
                                 then [(x0,y0)]
                                 else mySort ((srcX,srcY):[(x,y)|(x,y)<-dotlist, or [getDir (srcX,srcY) (x0,y0) (x,y) == DirRight, getDir (srcX,srcY) (x0,y0) (x,y) == DirStraight]])++[(x0,y0)]++mySort ((srcX,srcY):[(x,y)|(x,y)<-dotlist, getDir (srcX,srcY) (x0,y0) (x,y) == DirLeft])
                               mySort [(x,y)] = []
  • Graham掃描法尋找凸包
grahamHull :: (Num a, Ord a) => [(a,a)] -> [(a,a)]
grahamHull l = let sortedList = sortByAngle l in
                 doGrahamHull sortedList
                 where doGrahamHull l = case l of
                         [(x1,y1),(x2,y2),(x3,y3)] -> [(x1,y1),(x2,y2),(x3,y3)]
                         ((x1,y1):(x2,y2):(x3,y3):(x4,y4):list) ->
                           if getDir (x2,y2) (x3,y3) (x4,y4) == DirRight
                           then doGrahamHull ((x1,y1):(x2,y2):(x4,y4):list)
                           else (x1,y1):doGrahamHull ((x2,y2):(x3,y3):(x4,y4):list)
  • 運算結果:
*Main> grahamHull [(-1,0),(0,0),(1,0),(1,1),(2,1),(0,2)]
[(-1,0),(1,0),(2,1),(0,2)]

三、總結

這次練習有兩點特別值得注意:一是在開頭提到過的類型系統,注意函數的類型,對一些函數如果不確定型態可以在ghci中鍵入:t func_name進行查詢。二是模式匹配和層次抽象,這和代數其實是能對應起來的。

發佈了146 篇原創文章 · 獲贊 81 · 訪問量 53萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章