图表画法中的凸包问题:给菜园围一圈栅栏需要花多少钱?

前言

最近日子苦还房贷 ,听人说大头菜现在商机很足,于是我决定自己去种大头菜,来实现快速的发家致富

image.png

好景不长,种的大肉菜才刚长出来一点点,昨天夜里就冒出来了一头野猪拱了我好几颗菜,看来必须要上栅栏了。但是怎么围栅栏这事难倒我了。

早先种大头菜的时候偷懒一把种子直接甩了出去,现如今长的七零八落。也不好把大头菜都拔起来再摆整齐种下去,这大大增加了围栅栏的难度。

但是没有关系!隔壁的猴子同学家里的菜园围的严严实实,一次也没被野猪偷过。找他来帮帮忙不就可以完美解决了吗?

image.png

想到这里我就以一颗长的最好看的大头菜为报酬,请了隔壁的猴子同学来授之以渔。

猴子同学的思路讲解

这个问题可以抽象为 凸包问题

在给定的点中,找出有限点构造一个最小凸边形,使其可以将给定的所有点都包含在内

最关键的一点是把所有的菜【一下简化为点】,都放在栅栏的内侧。所以可以从 判断点是否在线段的某一边 来下手。

image.png

判断点是否在线段的某一边

那么如何判断呢?这里要引入一个数学概念,叉乘

由叉乘的定义可知,在三维坐标系中。向量 a 叉乘 向量 b 可以得到一个垂直于 a b 向量组成平面的新向量 c,且它的模长等于向量 a b 形成的平行四边形面积。且 c 根据 a b 的方向不同,也会有自己的方向。这里可以根据右手定则来得知向量 c 的方向。

那么在这里,我们不需要用到全部 叉乘 的特点。由于我们凸包构造的是二维坐标系,那么扩展到三维的话 z 向量系数一定为 0。所以简单来说二维平面上三点 A, B, C 构造出的向量 ab 叉乘 ac 得出的向量必定是在 z 轴上的向量(0x, 0y, kz)。那么通过计算出单位向量 z 的系数 k。 如果 k > 0 ,那么点 C 在向量 ab 的左边。 k = 0, 点 C 在向量 ab 上。 k < 0,点 C 在向量 ab 右边。

叉乘的计算可以写为


image.png

所以如果在二维坐标系中,就可以简化为 U ✖️ V = (u1v2 - u2v1)k

这里把 u v 更换为 x y, 那么我们可以根据 (x1y2 - x2y1) 的值是否大于等于 0 来进行判断

然后以它为核心思想,从最基础的穷举方法来开始优化

给定 n 个点,那么会有 n * (n - 1) / 2 条线,对每一条线进行计算,剩余的 (n - 2) 个点是否都位于这点线段的一侧。如果是,那么这两点为 凸点。

穷举法复杂度为 O(n^3)

这里可以继续对穷举法进行优化,首先取纵坐标最小的一个点,如果有两个就取其中一个,它肯定在凸包上。

然后以它为极坐标中心,对剩下的所有点进行一个角度排序,排序结果如下图。

image.png

然后就按顺序依次对每一个点 P_k 开始依次尝试剩下的 P_{n-k} 个点,找到第一个满足条件的点【其他点都在P_kP_{n-k}线段左侧】。

这样可以降低时间复杂度到 O(n^2)

到了这一步,想继续优化就得换一个思路,考虑是否可以不再对每一个点都进行尝试。Graham 介绍

这里通过从最低点开始,然后分析凸包的特点找规律,距离它角度最小的第一个点肯定是凸包上的点【当然角度最大的也是】,那么把 p0p1 入栈,接着考虑在线段 p1p2 是否是左转【可以想象一个人先沿着 p0p1 走,到了 p1 的时候是需要向左转还是向右转】。如果是向左,那么这个点入栈,继续下一个点。如果这个点向右,那么说明当前栈顶不是凸包上的点,出栈。然后再次拿栈顶两点来和当前拿的点比较。这样如此反复。

复杂度 O(n * log n)

最后附上 Graham 的简单实现。

// 叉乘
function crossProduct(p0, p1, p2) {
  const vectorA = {
    x: p1.x - p0.x,
    y: p1.y - p0.y,
  }
  const vectorB = {
    x: p2.x - p0.x,
    y: p2.y - p0.y,
  }
  // 向量叉乘,这里简化了 z 轴
  return vectorA.x * vectorB.y - vectorA.y * vectorB.x
}

function getConvexHull(pointData) {
  const result = []
  const arr = pointData.sort((a, b) => a.y - b.y)
  const p0 = arr.shift()

  // 最低点一定在凸包上,有多个最低点可以随便选一个
  result.push(p0)

  // 按角度排序
  const sortedPoint = arr
    .map((p) => {
      const cos = (p.x - p0.x) / Math.sqrt(Math.pow(p.y - p0.y, 2) + Math.pow(p.x - p0.x, 2))
      return {
        ...p,
        cos,
      }
    })
    .sort((a, b) => b.cos - a.cos)
    .map((p) => {
      return { x: p.x, y: p.y }
    })

  // 按照凸包的性质,第一个点必定在凸包上
  result.push(sortedPoint.shift())

  sortedPoint.forEach((p, index) => {
    while (crossProduct(result[result.length - 2], result[result.length - 1], p) < 0) {
      // 在右方,说明栈顶不是凸包上的点
      result.pop()
    }
    // 在一条线上的情况,多去一个点,属于优化操作
    if (crossProduct(result[result.length - 2], result[result.length - 1], p) === 0) result.pop()

    // 在左边及线上
    result.push(p)
  })

  return result
}

const pointData = [
  { x: 1, y: 28 },
  { x: 2, y: 1 },
  { x: 2, y: 12 },
  { x: 3, y: 46 },
  { x: 4, y: 1 },
  { x: 5, y: 11 },
  { x: 6, y: 21 },
  { x: 5, y: 51 },
  { x: 6, y: 1 },
  { x: 7, y: 43 },
  { x: 10, y: 45 },
]
const areaData = getConvexHull(JSON.parse(JSON.stringify(pointData)))

/*
 * console areaData:
 *
 * {x: 2, y: 1}
 * {x: 6, y: 1}
 * {x: 10, y: 45}
 * {x: 5, y: 51}
 * {x: 3, y: 46}
 * {x: 1, y: 28}
 */

后记

通过猴子同学的一番讲解操作,我成功的学会了如何围栅栏,虽然在最后大头菜经济泡沫了,但是万万没想到,我在这次经济泡沫中大肆兜售的【我不可能能用这么少的钱围起最好的栅栏】一书同样让我实现了财富自由。

-------------------

原文链接:[https://mp.weixin.qq.com/s/7Mek0vdv7r04zkj7oyISuw] (https://mp.weixin.qq.com/s/7Mek0vdv7r04zkj7oyISuw)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容