手撕K-Means,华为快递配送题AC代码(逐行注解)

小红的 AI 配送聚类优化

https://www.nowcoder.com/practice/fff828ed40764c94b5ff6fb745dcc1f4

import sys

import math

# ========== 1. 距离函数 ==========

def dist(p1, p2):

"""计算两点之间的欧氏距离"""

return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)

# ========== 2. K-Means 聚类算法 ==========

def kmeans(pts, k, max_iter=50, eps=1e-4):

"""

pts: 所有点的坐标列表

k: 聚类数量

max_iter: 最大迭代次数

eps: 收敛阈值

"""

n = len(pts) # 点的总数

# ----- 2.1 初始化聚类中心 -----

# 计算每个点到原点 (0,0) 的距离

d2ori = []

for i, p in enumerate(pts):

d = math.sqrt(p[0] ** 2 + p[1] ** 2)

d2ori.append((d, i, p)) # (距离, 索引, 点坐标)

# 排序:按距离从小到大,距离相同按输入顺序

d2ori.sort(key=lambda x: (x[0], x[1]))

# 取前 k 个点作为初始聚类中心

centers = [d2ori[i][2] for i in range(k)]

# ----- 2.2 迭代优化 -----

for _ in range(max_iter):

# 分配每个点到最近的聚类中心

belong = [-1] * n # belong[i] 表示点 i 属于哪个簇

for i, p in enumerate(pts):

best = 0

best_d = dist(p, centers[0])

for j, c in enumerate(centers):

d = dist(p, c)

if d < best_d:

best_d = d

best = j

belong[i] = best

# 重新计算每个簇的中心点(平均值)

new_c = []

for j in range(k):

# 找出属于第 j 簇的所有点

clu = [pts[i] for i in range(n) if belong[i] == j]

if clu:

# 计算 x 和 y 的平均值

avg_x = sum(p[0] for p in clu) / len(clu)

avg_y = sum(p[1] for p in clu) / len(clu)

new_c.append((avg_x, avg_y))

else:

# 如果某个簇没有点,保留原中心

new_c.append(centers[j])

# 计算所有中心点的移动距离之和

move = sum(dist(centers[j], new_c[j]) for j in range(k))

centers = new_c

# 如果移动距离小于阈值,收敛,停止迭代

if move < eps:

break

return centers

# ========== 3. 主函数 ==========

def solve():

# 读取所有输入数据

data = sys.stdin.read().strip().split()

if not data:

return

idx = 0

k = int(data[idx]); idx += 1 # 社区个数(聚类数)

n = int(data[idx]); idx += 1 # 快递包裹总数

sp = float(data[idx]); idx += 1 # 行驶速度 km/h

# 读取所有点的坐标

pts = []

for _ in range(n):

x = float(data[idx]); idx += 1

y = float(data[idx]); idx += 1

pts.append((x, y))

# 执行 K-Means 聚类,得到 k 个社区中心

centers = kmeans(pts, k)

# 计算每个中心到原点的距离,并按距离排序

d2ori = []

for c in centers:

d = math.sqrt(c[0] ** 2 + c[1] ** 2)

d2ori.append((d, c))

d2ori.sort(key=lambda x: x[0]) # 按距离升序排序

# 提取排序后的中心坐标

sorted_centers = [c for _, c in d2ori]

# ----- 计算总路程 -----

origin = (0.0, 0.0) # 起点

total = 0.0

cur = origin

# 起点 → 第1个中心 → 第2个中心 → ... → 第k个中心

for c in sorted_centers:

total += dist(cur, c)

cur = c

# 最后一个中心 → 起点

total += dist(cur, origin)

# 计算总时间(秒),向下取整

# 时间 = 距离 / 速度,速度单位 km/h,需要乘以 3600 转换为秒

print(int(total / sp * 3600))

if __name__ == "__main__":

solve()

全部评论

相关推荐

自从我室友在计算机导论课上听说了“刷&nbsp;LeetCode&nbsp;是进入大厂的敲门砖”,整个人就跟走火入魔了一样。他在宿舍门口贴了一张A4纸,上面写着:“正在&nbsp;DP,请勿打扰,否则&nbsp;Time&nbsp;Limit&nbsp;Exceeded。”日记本的扉页被他用黑色水笔加粗描了三遍:“Talk&nbsp;is&nbsp;cheap.&nbsp;Show&nbsp;me&nbsp;the&nbsp;code。”连宿舍聚餐,他都要给我们讲解:“今天的座位安排可以用回溯算法解决,但为了避免栈溢出,我建议用动态规划。来,这是状态转移方程:dp[i][j]&nbsp;代表第&nbsp;i&nbsp;个人坐在第&nbsp;j&nbsp;个位置的最优解。”我让他去楼下取个快递,他不直接去,非要在门口踱步,嘴里念念有词:“这是一个图的遍历问题。从宿舍楼(root)到驿站(target&nbsp;node),我应该用&nbsp;BFS&nbsp;还是&nbsp;DFS?嗯,求最短路径,还是广度优先好。”和同学约好出去开黑,他会提前发消息:“集合点&nbsp;(x,&nbsp;y),我们俩的路径有&nbsp;k&nbsp;个交点,为了最小化时间复杂度,应该在&nbsp;(x/2,&nbsp;y/2)&nbsp;处汇合。”有一次另一个室友低血糖犯了,让他帮忙找颗糖,他居然冷静地分析道:“别急,这是一个查找问题。零食箱是无序数组,暴力查找是&nbsp;O(n)。如果按甜度排序,我就可以用二分查找,时间复杂度降到&nbsp;O(log&nbsp;n)。”他做卫生也要讲究算法效率:“拖地是典型的岛屿问题,要先把连通的污渍区块都清理掉。倒垃圾可以用双指针法,一个指针从左往右,一个从右往左,能最快匹配垃圾分类。”现在我们宿舍的画风已经完全变了,大家不聊游戏和妹子,对话都是这样的:“你&nbsp;Two&nbsp;Sum&nbsp;刷了几遍了?”“别提了,昨天遇到一道&nbsp;Hard&nbsp;题,我连暴力解都想不出来,最后只能看题解。你呢?”“我动态规划还不行,总是找不到最优子结构。今天那道接雨水给我整麻了。”……LeetCode&nbsp;真的害了我室友!!!
老六f:编程嘉豪来了
AI时代还有必要刷lee...
点赞 评论 收藏
分享
评论
2
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务