多边形的面积(使用Python递归实现)

6
我正在尝试解决Exploring Python书中的一个练习。但是,我猜我不理解递归的概念。我已经写了一些递归函数。因此,我知道一些方面。但是,我没有足够的经验。而且我已经停止学习编程大约一年了。
无论如何,让我给你完整的问题:
多边形可以用(x,y)对的列表表示,其中每个对都是一个元组:[(x1,y1),(x2,y2),(x3,y3),...(xn,yn)]。编写一个递归函数来计算多边形的面积。这可以通过“切掉”一个三角形来实现,利用一个三角形的角落(x1,y1),(x2,y2),(x3,y3)的面积为(x1y1+x2y2+x3y2-y1x2-y2x3-y3x1)/2的事实。
尽管问题已经给出了公式,但我使用了另一个公式。因为我对多边形的面积进行了一些研究。如果您查看{{link1:here}},则该公式是不同的。
逐步描述我的程序会更好,以便解释我的意图。 好的,我不得不声明全局范围,因为涉及到递归:
area = 0
x = [0] * 3
y = [0] * 3 

然后,我创建了一个递归函数。这个函数总是返回0作为结果。所以我的实际问题是:

def areaofpolygon(polygon, i):
    global area, x, y # My variables 
    try: # I prefered using try statement from using if-else statements. So it is the easier I guess.
        x[i], y[i] = polygon[i] # X and Y coordinates from tuple
        area += (x[i]*y[i+1] - x[i+1]*y[i]) #My formula
    except IndexError:
        return area/2
    
    areaofpolygon(polygon, i+1)   # Here, this is my weird recursion

而我的主函数:

  def main():
      mypolygon = [(1,2), (2,5), (1,4)] # I declared polygon as tuples
      # I called my function and started to count from zero, and the result will be prompted.
      print(areaofpolygon(mypolygon,0))
        
      return 0
  if __name__ == '__main__':
      main()

这是完整的代码,没有注释:

'''
Created on Feb 24, 2012

@author: msarialp
'''
area = 0
x = [0] * 3
y = [0] * 3
def areaofpolygon(polygon, i):
    global area, x, y
    try:
        x[i], y[i] = polygon[i]
        area += (x[i]*y[i+1] - x[i+1]*y[i])
    except IndexError:
        return area/2
    
    areaofpolygon(polygon, i+1)   
def main():
    mypolygon = [(1,2), (2,5), (1,4)]
    print(areaofpolygon(mypolygon,0))
    
    return 0
if __name__ == '__main__':
    main()

编辑一

阅读了您的回答后,我明白了我的代码问题所在。因此,我决定分享程序的最新版本,以便获得其他帮助。 同样,我不得不声明全局变量。如何应用来自senderle的(lop_triangle)函数?

area = 0
x = [0] * 3
y = [0] * 3

我的函数用于将元组分解并获取x和y坐标。

def sides_of_polygon(polygon, i):
    global x, y
    try:
        x[i], y[i] = polygon[i]
        return sides_of_polygon(polygon, i+1)
    except IndexError:
        return x, y

我的函数计算多边形的面积(与之前相同)

def area_of_polygon(x, y, i):
    global area
    try:
        area += x[i]*y[i+1] - x[i+1]*y[i]
        return area_of_polygon(x, y, i+1)
    except IndexError:
        return area/2.0

我的主函数...

def main():
    mypolygon = [(1,2), (2,5), (1,4)]
    dx, dy = sides_of_polygon(mypolygon, 0)
    print(area_of_polygon(dx,dy,0))
    
    return 0
if __name__ == '__main__':
    main()

请在不提供完整解决方案的情况下帮助我改进我的代码。
编辑二:
与senderle讨论后,我理解了问题所在,他的解决方案比我的更好,因此建议您使用它。无论如何,他帮助我纠正了我的代码。我不得不再次更改我的公式。
area += x[i]*y[(i+1) % 3] - x[(i+1) % 3]*y[i]

他还补充说,对于更长的多边形,顶点数量必须为3。感谢大家抽出时间。

快速提示:def areaofpolygon(polygon, i=0): 可能会稍微澄清一些。 - yurisich
问题:四边形的公式是否为(x1y1 + x2y2 + x3y3 + x4y3 – y1x2 –y2x3 – y3x4 - y4x1) / 2 - yurisich
根据问题,是的,它是这样的。但是,在我看来,真正的答案是不同的。它应该是 (x1y2 + x2y3 + x3y4 - x2y1 - x3y2 - x4y3) / 2。 - mustafaSarialp
好的,你问题的一部分将会自动解决——xy 的最后一组匹配项(+ x3y2)被从相同的值 yx (- y3x4) 中减去了。因此,所有的 xy 组合(除了最后一组)都会相乘...但是对于后半部分呢?...嗯。 - yurisich
你实际上不需要声明全局变量。你可以在递归函数内部将变量作为参数传递给函数调用。 - Joel Cornett
显示剩余2条评论
3个回答

5
您的公式实现存在缺陷。它预测了x、y列表中尚未设置的值,使用了(x[i]*y[i+1] - x[i+1]*y[i])
如果在try-except块内部放置一个打印语句,您将看到您只是乘以零并获得零面积。
try:
    x[i], y[i] = polygon[i]
    area += (x[i]*y[i+1] - x[i+1]*y[i])
    print x[i], y[i+1], x[i+1], y[i]
except IndexError, e:
    return area/2

#1 0 0 2
#2 0 0 5

此外,你没有返回递归调用areaofpolygon的结果,因此你永远不会得到那个`area/2`。你需要:`return areaofpolygon(polygon, i+1)`。并确保你实际上除以2.0,以便得到浮点精度,因为你的点是整数。
尝试只使用你找到的公式或在另一个问题中建议的公式。
更新:
这里是你代码的修正版本:
#!/usr/bin/env python

from random import randint
from shapely.geometry import Polygon

area = 0

def areaofpolygon(polygon, i):
    global area
    if i == 0: 
        area = 0

    try:
        x1, y1 = polygon[i]
        x2, y2 = polygon[i+1]
        area += (x1*y2) - (x2*y1)

    except IndexError, e:
        x1, y1 = polygon[0]
        x2, y2 = polygon[-1]
        area += (x2*y1) - (x1*y2)
        return abs(area/2.0)

    return areaofpolygon(polygon, i+1)   

def main():
    mypolygon = [(randint(0, 100), randint(0, 100)) for _ in xrange(10)]
    print mypolygon

    area = areaofpolygon(mypolygon, 0)
    assert area == Polygon(mypolygon).area

    print "Test passed."
    return 0

if __name__ == '__main__':
    main()

### Output ###
$ ./test.py 
[(29, 76), (85, 49), (27, 80), (94, 98), (19, 1), (75, 6), (55, 38), (74, 62), (0, 25), (93, 94)]
Test passed.
$ ./test.py 
[(13, 37), (98, 74), (42, 58), (32, 64), (95, 97), (34, 62), (34, 59), (21, 76), (55, 32), (76, 31)]
Test passed.
$ ./test.py 
[(38, 67), (66, 59), (16, 71), (53, 100), (64, 52), (69, 31), (45, 23), (52, 37), (27, 21), (42, 74)]
Test passed.

请注意,您不需要全局的x、y列表。而且您还遗漏了方程式的最后一部分,在此部分中您需要使用最后一个点和第一个点。

我尝试了您的代码,但它有一些问题。我猜您误解了这个公式,请再次查看http://mathworld.wolfram.com/PolygonArea.html的公式。 顺便提一下,公式应该是(x1y2 + x2y3 - x2y1 - x3y2) 我已经做出了更改,但没有任何变化... try: ... x3, y3 = polygon[i+2] area += (x1y2+x2y3-x2y1-x3y2) ... x3, y3 = polygon[-2] area += (x1y2+x2y3-x2y1-x3y2) 根据我们的多边形,答案应该是2。 - mustafaSarialp
@guguk - 我认为我所缺少的就是对最终面积调用abs()函数。我刚刚更新了示例,并使用shapely Polygon类进行测试,以确保面积计算匹配。我的代码遵循了公式。也许在except块中的命名方式让你感到困惑,实际上它是使用最后一个点和第一个点。 - jdi

5
递归的本质如下:
  1. 确定一个简单的基本情况并解决它。
  2. 构思一个过程,当重复时,将更复杂的情况减少到该基本情况。
  3. 将步骤#2中的过程应用于问题,直到达到基本情况。
在你的情况下,第一步很容易。最小的多边形是三角形。三角形的面积是 (x1y2 + x2y3 + x3y1 - y1x2 - y2x3 - y3x1) / 2。(看起来问题陈述有误...)
第二步也很容易,因为问题陈述已经给出了:给定一个n个顶点的多边形,砍掉一个三角形,确定其面积,并将其添加到结果为(n-1)个顶点的多边形的面积中。
我们将其分解成几个部分。首先,解决步骤#1的函数:
def area_of_triangle(points):
    (x1, y1), (x2, y2), (x3, y3) = points
    return abs(x1 * y2 + x2 * y3 + x3 * y1 - y1 * x2 - y2 * x3 - y3 * x1) / 2

很简单。现在我们需要一个解决#2的函数。我们只需要一个函数将三角形去掉并返回它和结果更小的多边形:

def lop_triangle(points):
    triangle = [points[0], points[-1], points[-2]]
    polygon = points[:-1]
    return triangle, polygon

如果不是很明显,这个步骤只是用多边形的第一个点和最后两个点创建了一个三角形。接着,它移除了多边形的最后一个点,现在相当于把三角形切掉了。(画一个n边形,将其顶点从0到n标记,看看它是如何工作的)所以现在我们有一个三角形和一个更简单的多边形。
现在,让我们把所有东西都结合起来。第三步在某些方面是最难的,但因为我们已经解决了前两个问题,所以第三个问题更容易理解。
def area_of_polygon(points):
    if len(points) == 3:
        return area_of_triangle(points)
    else:
        triangle, polygon = lop_triangle(points)
        return area_of_triangle(triangle) + area_of_polygon(polygon)

所有的魔法都发生在最后一行。每次area_of_polygon获得一个三角形,它只返回三角形的面积。但是当它获得一个更大的多边形时,它会砍掉一个三角形,取这个三角形的面积,并加到……一个较小多边形的面积中。所以说,多边形有5个顶点。第一次调用area_of_polygon(c1)时,它砍掉一个三角形,取其面积,然后再次调用area_of_polygon(c2),但这次使用四点多边形。然后,area_of_polygon再次砍掉一个三角形,并调用area_of_polygon(c3),但这次使用三点多边形。然后它就不必再次调用area_of_polygon。它只需将三角形的面积返回给上一个调用(c2)。这将结果与(c2)中的三角形总和,并将该值返回给(c1)。这样你就得到了答案。
另外,就价值而言,在三行中可以非常清晰地写出沃尔夫拉姆公式
def area_of_polygon(vertices):
    pairs = zip(vertices, vertices[1:] + vertices[0:1])
    return sum(x1 * y2 - y1 * x2 for (x1, y1), (x2, y2) in pairs) / 2

@Droogans,我以为你会做出类似于我帖子底部的函数。 - senderle
这是非常完美的解释。非常感谢。但是,当我追踪您的代码时,我发现了一些错误。我猜是您的公式有问题(我的意思是您的最后一个解决方案)。但我注意到了您的提示并创建了一个解决方案。我试着自己做因为这是一个练习... - mustafaSarialp
@goguk,你能否发布一份生成错误结果的点列表吗?我找不到任何问题。 - senderle
你的结果与http://www.wolframalpha.com/input/?i=triangle+with+vertices+%281%2C2%29%2C+%282%2C5%29%2C+%281%2C4%29相同吗? - mustafaSarialp
让我们在聊天室继续这个讨论:http://chat.stackoverflow.com/rooms/8242/discussion-between-senderle-and-guguk - senderle
显示剩余2条评论

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接