Haskell lesson:实现Graham扫描算法

本文介绍如何使用Haskell语言实现Graham扫描算法来计算凸包。文章详细阐述了算法的基本步骤,包括找到最小点、按角度排序点集、确定点的方向等,并提供了完整的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作为Haskell方面第一次上手练习,尽量不用到库里面的函数,然后通过一步一步实现Graham扫描算法,来熟悉haskell函数运行机制,了解调试过程,最重要的是要对函数的型态了然于胸。

参考书籍选自《Real World Haskell》和《Haskell趣学指南》。

一、问题描述

二、通过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进行查询。二是模式匹配和层次抽象,这和代数其实是能对应起来的。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叶玄青

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值