Prim算法求最小生成树

如下所示的无向网络包含7个顶点和12条边,边权重之和为243。

这个网络还可以用如下的矩阵来表示:

但是,在保证网络上所有点之间连通性的前提下,可以将某些边去除掉,从而实现该网络的优化。能够做到最多节省的网络如下所示。其权重和为93,与原网络相比共节省了243 − 93 = 150。

network.txt包含一个拥有四十个顶点的网络,以矩阵形式给出。求在保证连通性的前提下,通过去除边可以做到的最多节省。

这里可以使用Prim算法求出最小生成树,然后求出其权重和。

Prim算法描述

从单一顶点开始,Prim算法按照以下步骤逐步扩大树中所含顶点的数目,直到遍及连通图的所有顶点。

  1. 输入:一个加权连通图,其中顶点集合为V,边集合为E;
  2. 初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {};
  3. 重复下列操作,直到Vnew = V:

a) 在集合E中选取权值最小的边(u, v),其中u为集合Vnew中的元素,而v则不是
(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
b) 将v加入集合Vnew中,将(u, v)加入集合Enew中;

  1. 输出:使用集合Vnew和Enew来描述所得到的最小生成树。

具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
from collections import defaultdict

# 构造图
# 顶点由'1', '2', '3', ... '40'构成
def build_gragh():
graph = dict()
with open('network.txt') as f:
lines = f.read().splitlines()
for vh, line in enumerate(lines, start=1):
g = dict()
for vs, v in enumerate(line.split(','), start=1):
try:
g[str(vs)] = int(v)
except ValueError:
g[str(vs)] = None
graph[str(vh)] = g

return graph

# test gragh
# 由字典表示,两个顶点权重值可以表示为:如A和B的权重为graph['A']['B']
"""
graph = {
'A': {'B': 16, 'C': 12, 'D': 21},
'B': {'A': 16, 'D': 17, 'E': 20},
'C': {'A': 12, 'D': 28, 'F': 31},
'D': {'A': 21, 'B': 17, 'C': 28, 'E': 18, 'F': 19, 'G': 23},
'E': {'B': 20, 'D': 18, 'G': 11},
'F': {'C': 31, 'D': 19, 'G': 27},
'G': {'D': 23, 'E': 11, 'F': 27}
}
"""

# 求权重和
def sum_weight(graph, duplicate=True):
sum_ = 0
for v in graph:
for vs in graph[v]:
if graph[v][vs] != None:
sum_ += graph[v][vs]

# 如果计算两遍,则除以2
if duplicate:
return sum_ // 2
else:
return sum_

graph = build_gragh()

# prim算法求最小生成树
def prim_tree(graph):
# 所有顶点
vertes = list(graph.keys())
# 选中顶点
selected = set()
# 将第一个顶点加入
selected.add(vertes[0])
# 移除已选顶点
vertes.pop(0)

new_graph = defaultdict(dict)
# 只要还有未选中顶点,则循环
while vertes:
# 假设最小权重为1000
tmp = 1000
for u in selected:
for v in vertes:
try:
if graph[u][v] != None and graph[u][v] < tmp:
tmp = graph[u][v]
choose_u = u
choose_v = v

except KeyError:
pass

selected.add(choose_v)
vertes.remove(choose_v)
new_graph[choose_u][choose_v] = tmp

return new_graph

print("before: ")
sum1 = sum_weight(graph, duplicate=True)
print(sum1)
print("after:")
# 最小生成树中各条边权重只算一次,所以这里duplicate为False
sum2 = sum_weight(prim_tree(graph), duplicate=False)
print(sum2)

print("saving: {}".format(sum1 - sum2))
1
2
3
4
5
6
7
8
9
10
$ time python3 solve.py
before:
261832
after:
2153
saving: 259679

real 0m0.067s
user 0m0.060s
sys 0m0.004s

Python __new__解释

Python实例化对象操作过程,首先是调用__new__()方法创建实例,然后调用__init__()方法初始化对象。一般情况下,定义类时不需要显式的重写__new__()方法;但是某些情况下,比如继承immutable类型(如int, str, tuple, frozenset)时,因无法在__init__()中修改其元素,所以需要重写__new__()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import math

class Point(tuple):
#第一个参数是类型
def __new__(cls, x, y):
return tuple.__new__(cls, (x, y))

def distance(self, other):
return math.sqrt((self[0]-other[0]) ** 2 + (self[1]-other[1]) ** 2)

def __repr__(self):
return "Point({0}, {1})".format(self[0], self[1])

p = Point(1, 1)
p2 = Point(0, 0)
p3 = Point(3, 4)
print(p.distance(p2))
print(p2.distance(p3))
print(repr(p))

__new__()返回某个类的实例对象,而__init__()不返回任何值,仅做初始化操作。

判断某点是否在三角形内部

Project Euler第102题:

Three distinct points are plotted at random on a Cartesian plane, for which -1000 ≤ x, y ≤ 1000, such that a triangle is formed.

Consider the following two triangles:

A(-340,495), B(-153,-910), C(835,-947)

X(-175,41), Y(-421,-714), Z(574,-645)

It can be verified that triangle ABC contains the origin, whereas triangle XYZ does not.

Using triangles.txt (right click and ‘Save Link/Target As…’), a 27K text file containing the co-ordinates of one thousand “random” triangles, find the number of triangles for which the interior contains the origin.

NOTE: The first two examples in the file represent the triangles in the example given above.

大致意思是给定3个点A, B, C,判断A, B, C组成的三角形是否包含原点P(0, 0),或者说原点是否在三角形ABC的内部。
解答这道题的思路是判断ABC的面积是否等于PAB, PBC, PAC三个面积之和。


P在三角形内部


P在三角形外部

如何计算三角形面积?
在这里,三个点A, B, C分别用坐标表示。如A(-340,495), B(-153,-910), C(835,-947),维基百科上给出了计算公式:

$$
T = \frac{1}{2} \big| (x_A - x_C) (y_B - y_A) - (x_A - x_B) (y_C - y_A) \big|
$$
其中,$x_A$表示$A$的$x$坐标,$y_B$表示$B$的$y$坐标…

Python实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def main():
with open('triangles.txt') as f:
lines = f.read().splitlines()

counts = 0
for line in lines:
points = line.split(',')
points = [int(point) for point in points]
A = points[:2]
B = points[2:4]
C = points[4:]
P = (0, 0)
if area(A, B, C) == area(P, B, C) + area(P, A, C) + area(P, A, B):
counts += 1
print(counts)

# 求面积,根据上面的公式
def area(A, B, C):
return abs((A[0]-C[0]) * (B[1]-A[1]) - (A[0]-B[0]) * (C[1]-A[1]))

if __name__ == '__main__':
main()
1
2
3
4
5
6
$ time python solve.py
228

real 0m0.038s
user 0m0.036s
sys 0m0.000s

函数柯里化

柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

  • Python中函数柯里化:
1
2
3
4
5
6
7
8
import functools
# 原函数:
def func1(a, b, c):
return do_something_with_a_b_c

# 柯里化:
func2 = functools.partial(func1, 1, 2) # 固定参数a,b
# 使用:func2(c)

冯延巳诗词欣赏

冯延巳 (903–960)又名延嗣,字正中,五代广陵(今江苏省扬州市)人。在南唐做过宰相,生活过得很优裕、舒适。他的词多写闲情逸致辞,文人的气息很浓,对北宋初期的词人有比较大的影响。宋初《钓矶立谈》评其“学问渊博,文章颖发,辩说纵横”,其词集名《阳春集》。

南乡子
细雨湿流光,芳草年年与恨长。烟锁凤楼无限事,茫茫,鸾镜鸳衾两断肠。
魂梦任悠扬,睡起杨花满绣床。薄幸不来门半掩,斜阳,负你残春泪几行。

鹊踏枝
谁道闲情抛弃久?每到春来,惆怅还依旧。日日花前常病酒,不辞镜里未颜瘦。
河畔青芜堤上柳,为问新愁,何事年年有?独立小桥风满袖,平林新月人归后。

采桑子
花前失却游春侣,独自寻芳。满目悲凉。纵有笙歌亦断肠。 
林间戏蝶帘间燕,各自双双。忍更思量,绿树青苔半夕阳。

忆江南
去岁迎春楼上月。正是西窗,夜凉时节。玉人贪睡坠钗云。粉销妆薄见天真。
人非风月长依旧。破镜尘筝,一梦经年瘦。今宵帘幕飏花阴。空余枕泪独伤心。

鹊踏枝
六曲阑干偎碧树,杨柳风轻,展尽黄金缕。谁把钿筝移玉柱?穿帘海燕又飞去。
满眼游丝兼落絮,红杏开时,一霎清明雨。浓睡觉来莺乱语,惊残好梦无寻处。