news 2026/8/28 4:01:48

K-means聚类算法Python实战:从原理到代码实现与最佳K值选择

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
K-means聚类算法Python实战:从原理到代码实现与最佳K值选择

1. 项目概述:从数据到洞察,K-means聚类的实战价值

如果你手头有一堆客户数据、用户行为记录或者是一大堆传感器的读数,第一反应是不是有点懵?数据点密密麻麻,看不出什么规律,更别提从中提炼出有价值的信息来指导决策了。这时候,聚类分析,特别是K-means算法,就成了我们手里的一把“快刀”。它不需要你事先给数据打上任何标签,就能自动地把相似的数据点归到同一个组里,把不相似的分开。想象一下,你有一堆未分类的图片,K-means能帮你把风景照、人像照、宠物照大致分开;或者你有一堆客户信息,它能帮你识别出高价值客户、普通客户和潜在流失客户等不同的群体。这个“数学建模——K-means聚类模型Python代码”的项目,核心就是把这套强大的数学思想,通过Python代码落地,变成一个谁都能上手操作、快速看到结果的分析工具。它解决的正是“如何从无标签数据中发现内在结构”这个经典问题,无论是学生做课程设计、数据分析师做初步探索,还是研究员处理实验数据,都是一个绕不开的实用技能。

2. 核心思路与模型原理解析

2.1 K-means算法的核心思想:迭代寻优的“中心点”游戏

K-means算法的思想非常直观,可以用一个生活化的场景来理解:假设你要在城里开K家便利店,目标是让全城的居民到离自己最近便利店的平均距离最短。一开始,你随机选了K个地点作为便利店(初始中心点)。然后,每个居民(数据点)都会选择去离自己最近的那家店(归属到最近的簇)。接着,你根据每个便利店实际服务的居民住址,重新计算一下,把便利店搬到这些居民住址的中心位置(更新簇中心)。你会发现,搬了新位置后,有些居民离另一家店更近了,于是他们又会更换选择。你就这样不断重复“居民选择最近店”和“店搬到居民中心”这两个步骤,直到便利店的位置不再发生大的变动,或者说居民们的归属稳定下来。这时,你就得到了一个比较合理的便利店布局(聚类结果),同时居民们也自然地分成了K个群体。

用数学语言描述,这个过程就是在优化一个目标函数,即所有数据点到其所属簇中心的距离平方和(Sum of Squared Errors, SSE)。算法通过迭代来最小化这个SSE。这里的关键在于“距离”的定义,最常用的是欧氏距离,也就是我们直观上的直线距离。但根据数据特性,也可以使用曼哈顿距离、余弦相似度等。

2.2 关键参数K的选择:肘部法则与轮廓系数

K-means算法需要我们事先指定聚类的数目K,这既是它的优点(目标明确),也是最大的挑战之一。因为对于未知数据,我们往往不知道分成几类最合适。这里有两个非常实用的辅助工具:

1. 肘部法则:它的思路是,随着聚类数K的增加,每个簇会更紧凑,所有数据点到其簇中心的距离总和(SSE)自然会下降。但是,下降的幅度会变化。开始时,增加K能大幅降低SSE(比如从1类分成2类,效果显著);当K增加到真实类别数附近时,再增加K,SSE的下降幅度会突然变得平缓。如果我们把K和对应的SSE画出来,这个拐点就像人的“肘部”,对应的K值通常是一个较好的选择。这个方法简单直观,但拐点有时不明显,需要主观判断。

2. 轮廓系数:这是一个同时考虑簇内凝聚度和簇间分离度的指标。对于每个数据点i,计算:

  • a(i): i到同簇内所有其他点距离的平均值(凝聚度)。
  • b(i): i到其他所有簇中,所有点距离平均值的最小值(分离度)。 那么点i的轮廓系数 s(i) = (b(i) - a(i)) / max(a(i), b(i))。 s(i)的值在-1到1之间。越接近1,说明该点聚类得越好;越接近-1,说明该点可能被分错了簇;接近0则说明点在簇的边界上。所有点的轮廓系数的平均值,可以作为对整个聚类结果的一个评价。我们可以计算不同K值下的平均轮廓系数,选择使其最大化的K。

在实际操作中,我通常会先使用肘部法则确定一个大概的范围,再用轮廓系数在这个范围内精细挑选,并结合业务理解做最终决定。比如对客户分群,业务上可能希望分成3-5个具有明显差异的群体以便制定策略,那么即使轮廓系数显示K=6略高,我们可能还是会选择K=5。

3. 从零搭建K-means聚类Python代码实战

3.1 环境准备与数据模拟

我们使用Python的核心科学计算库来完成这个项目。首先确保你的环境里安装了numpy,pandas,matplotlibscikit-learn。如果没有,通过pip install numpy pandas matplotlib scikit-learn即可快速安装。

为了演示,我们不直接使用敏感的真实数据,而是用sklearnmake_blobs函数来模拟一份数据。这个函数可以生成符合高斯分布(正态分布)的“一团一团”的数据,非常适合用来测试聚类算法。

import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.datasets import make_blobs from sklearn.preprocessing import StandardScaler # 设置随机种子,确保每次运行结果一致 np.random.seed(42) # 生成模拟数据 # n_samples: 样本数 # centers: 真实的中心点,这里我们设定为4个 # cluster_std: 每个簇的标准差,控制簇的紧密程度 # random_state: 随机状态 X, y_true = make_blobs(n_samples=300, centers=4, cluster_std=0.60, random_state=42) # 查看数据形状 print(f"数据形状: {X.shape}") # 输出 (300, 2),300个样本,每个样本2个特征 print(f"前5个数据点:\n{X[:5]}") # 数据标准化(非常重要!) # K-means基于距离,如果特征量纲不同(如年龄和收入),量级大的特征会主导距离计算。 # StandardScaler将每个特征缩放到均值为0,方差为1。 scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # 可视化原始数据 plt.figure(figsize=(12, 5)) plt.subplot(1, 2, 1) plt.scatter(X[:, 0], X[:, 1], s=50, alpha=0.7) plt.title("原始模拟数据 (未标准化)") plt.xlabel("特征 1") plt.ylabel("特征 2") plt.subplot(1, 2, 2) plt.scatter(X_scaled[:, 0], X_scaled[:, 1], s=50, alpha=0.7) plt.title("标准化后的数据") plt.xlabel("特征 1 (标准化)") plt.ylabel("特征 2 (标准化)") plt.tight_layout() plt.show()

注意:数据标准化是聚类前几乎必不可少的步骤。除非你有充分理由确信所有特征已经在同一尺度上,或者你使用了不受量纲影响的距离度量(如余弦相似度)。忽略这一步是新手常犯的错误,会导致聚类结果完全被某个大数值特征所支配。

3.2 手撕K-means核心代码

虽然scikit-learn提供了高度优化的KMeans类,但自己从头实现一遍是理解算法精髓的最佳方式。下面我们一步步构建一个简易版的K-means。

class SimpleKMeans: def __init__(self, n_clusters=3, max_iter=300, tol=1e-4, random_state=None): """ 初始化K-means参数。 :param n_clusters: 聚类数量K :param max_iter: 最大迭代次数 :param tol: 容忍度,中心点移动距离小于此值则停止迭代 :param random_state: 随机种子 """ self.n_clusters = n_clusters self.max_iter = max_iter self.tol = tol self.random_state = random_state self.centroids = None # 簇中心 self.labels = None # 每个样本的簇标签 self.inertia_ = None # SSE,即样本到其最近中心点的距离平方和 def _initialize_centroids(self, X): """随机初始化中心点。一种简单策略是随机选择数据点作为初始中心。""" np.random.seed(self.random_state) indices = np.random.choice(X.shape[0], self.n_clusters, replace=False) return X[indices] def _compute_distance(self, X, centroids): """计算每个样本到所有中心点的欧氏距离。""" # 利用广播机制高效计算 distances = np.sqrt(((X[:, np.newaxis, :] - centroids) ** 2).sum(axis=2)) return distances def fit(self, X): """训练模型,找到中心点。""" # 1. 初始化中心点 self.centroids = self._initialize_centroids(X) for i in range(self.max_iter): # 2. 计算距离并分配标签(E步) distances = self._compute_distance(X, self.centroids) self.labels = np.argmin(distances, axis=1) # 保存旧中心点用于判断收敛 old_centroids = self.centroids.copy() # 3. 重新计算中心点(M步) for k in range(self.n_clusters): # 找到属于第k簇的所有点 cluster_points = X[self.labels == k] if len(cluster_points) > 0: # 新的中心点是该簇所有点的均值 self.centroids[k] = cluster_points.mean(axis=0) else: # 如果某个簇没有点,则重新随机初始化该中心(避免空簇) self.centroids[k] = X[np.random.randint(0, X.shape[0])] # 4. 检查是否收敛(中心点移动很小) centroid_shift = np.sqrt(((self.centroids - old_centroids) ** 2).sum(axis=1)).max() if centroid_shift < self.tol: print(f"迭代在第 {i+1} 轮收敛。") break # 计算最终的SSE distances = self._compute_distance(X, self.centroids) self.inertia_ = np.sum(np.min(distances, axis=1) ** 2) return self def predict(self, X): """预测新数据的簇标签。""" distances = self._compute_distance(X, self.centroids) return np.argmin(distances, axis=1) # 使用我们手写的模型进行聚类 simple_kmeans = SimpleKMeans(n_clusters=4, random_state=42) simple_kmeans.fit(X_scaled) labels_custom = simple_kmeans.labels centroids_custom = simple_kmeans.centroids print("自定义模型中心点:\n", centroids_custom) print("自定义模型SSE (Inertia):", simple_kmeans.inertia_)

这段代码清晰地揭示了K-means的两个核心步骤:期望步骤(E-step)分配标签,和最大化步骤(M-step)更新中心。循环迭代直到中心点稳定。自己实现一遍后,你会对算法可能遇到的问题(如空簇、初始值敏感)有更深体会。

3.3 使用Scikit-learn的工业级实现

在实际项目中,我们几乎总是使用scikit-learnKMeans,因为它经过高度优化,速度快、功能全、更稳定。

from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score # 使用sklearn的KMeans sklearn_kmeans = KMeans(n_clusters=4, init='k-means++', n_init=10, max_iter=300, random_state=42) sklearn_kmeans.fit(X_scaled) labels_sklearn = sklearn_kmeans.labels_ centroids_sklearn = sklearn_kmeans.cluster_centers_ print("Sklearn模型中心点:\n", centroids_sklearn) print("Sklearn模型SSE (Inertia):", sklearn_kmeans.inertia_) # 计算轮廓系数 silhouette_avg = silhouette_score(X_scaled, labels_sklearn) print(f"轮廓系数 (K=4): {silhouette_avg:.4f}")

这里有几个关键参数需要解释:

  • init='k-means++': 这是默认的初始化策略,比完全随机初始化更聪明。它通过让初始中心点彼此远离,来降低算法对初始值的敏感性,从而更容易得到全局较优解。
  • n_init=10: 算法会用不同的初始中心点运行10次,最终选择SSE最小的那次作为结果。这是克服局部最优的实用方法。
  • max_iter=300: 单次运行的最大迭代次数。
  • random_state: 固定随机种子,保证结果可复现。

实操心得:在生产环境中,init='k-means++'n_init(通常设为10或‘auto’)是必选项,能显著提升结果的稳定性和质量。除非有特殊需求,否则不要使用init='random'

4. 确定最佳K值:肘部法则与轮廓系数的代码实现

现在,我们把选择K值的方法用代码实现出来,并可视化。

def find_optimal_k(X, max_k=10): """ 通过肘部法则和轮廓系数寻找最佳K值。 """ inertias = [] silhouette_scores = [] K_range = range(2, max_k+1) # K至少为2 for k in K_range: kmeans = KMeans(n_clusters=k, init='k-means++', n_init=10, random_state=42) kmeans.fit(X) inertias.append(kmeans.inertia_) # 轮廓系数要求至少2个簇,且每个簇不能只有一个点 if k > 1: score = silhouette_score(X, kmeans.labels_) silhouette_scores.append(score) else: silhouette_scores.append(None) return K_range, inertias, silhouette_scores K_range, inertias, silhouette_scores = find_optimal_k(X_scaled, max_k=10) # 绘制肘部法则图 plt.figure(figsize=(14, 5)) plt.subplot(1, 2, 1) plt.plot(K_range, inertias, 'bo-') plt.xlabel('聚类数量 K') plt.ylabel('SSE (Inertia)') plt.title('肘部法则 (Elbow Method)') plt.grid(True, linestyle='--', alpha=0.7) # 可以尝试标注可能的“肘部” plt.annotate('可能的肘部', xy=(4, inertias[2]), xytext=(5, inertias[2]+2), arrowprops=dict(facecolor='black', shrink=0.05, width=1.5, headwidth=8)) plt.subplot(1, 2, 2) # 过滤掉K=1的情况 plot_K = list(K_range)[1:] # 从2开始 plot_scores = silhouette_scores[1:] plt.plot(plot_K, plot_scores, 'rs-') plt.xlabel('聚类数量 K') plt.ylabel('轮廓系数平均值') plt.title('轮廓系数法 (Silhouette Score)') plt.grid(True, linestyle='--', alpha=0.7) # 标注最大值 max_score_idx = np.argmax(plot_scores) best_k = plot_K[max_score_idx] best_score = plot_scores[max_score_idx] plt.annotate(f'最佳K={best_k}\n分数={best_score:.3f}', xy=(best_k, best_score), xytext=(best_k+1, best_score-0.05), arrowprops=dict(facecolor='red', shrink=0.05)) plt.tight_layout() plt.show() print(f"根据轮廓系数,建议的最佳K值为: {best_k}")

运行这段代码,你会得到两张图。左图是肘部图,SSE随着K增大而下降,在K=4附近曲线斜率变化明显,形成一个“肘部”。右图是轮廓系数图,在K=4时达到了峰值。两者结合,强有力地建议我们将数据分为4类,这也正好符合我们生成数据时设定的centers=4

5. 结果可视化与聚类效果评估

模型训练好了,K值也选定了,最后一步就是把结果直观地展示出来,并做简单的评估。

# 使用最佳K=4重新训练模型 best_kmeans = KMeans(n_clusters=best_k, init='k-means++', n_init=10, random_state=42) best_kmeans.fit(X_scaled) final_labels = best_kmeans.labels_ final_centroids = best_kmeans.cluster_centers_ # 1. 聚类结果散点图 plt.figure(figsize=(10, 8)) # 为每个簇分配不同颜色和标记 colors = ['#FF6B6B', '#4ECDC4', '#FFD166', '#95E1D3', '#FF9A8B', '#AAD8D0'] # 准备更多颜色 markers = ['o', 's', '^', 'D', 'v', '<'] # 准备更多标记 for i in range(best_k): # 画出属于第i簇的点 cluster_points = X_scaled[final_labels == i] plt.scatter(cluster_points[:, 0], cluster_points[:, 1], s=70, alpha=0.7, c=colors[i % len(colors)], marker=markers[i % len(markers)], label=f'簇 {i}') # 画出中心点 plt.scatter(final_centroids[:, 0], final_centroids[:, 1], s=300, c='black', marker='X', label='簇中心', edgecolors='white', linewidth=2) plt.xlabel('特征 1 (标准化)') plt.ylabel('特征 2 (标准化)') plt.title(f'K-means聚类结果 (K={best_k})') plt.legend() plt.grid(True, linestyle='--', alpha=0.3) plt.show() # 2. 轮廓分析图(更细致的评估) from sklearn.metrics import silhouette_samples import matplotlib.cm as cm # 计算每个样本的轮廓系数 sample_silhouette_values = silhouette_samples(X_scaled, final_labels) plt.figure(figsize=(12, 8)) y_lower = 10 for i in range(best_k): # 收集第i簇样本的轮廓系数,并排序 ith_cluster_silhouette_values = sample_silhouette_values[final_labels == i] ith_cluster_silhouette_values.sort() size_cluster_i = ith_cluster_silhouette_values.shape[0] y_upper = y_lower + size_cluster_i color = cm.nipy_spectral(float(i) / best_k) plt.fill_betweenx(np.arange(y_lower, y_upper), 0, ith_cluster_silhouette_values, facecolor=color, edgecolor=color, alpha=0.7) # 在图中标注簇的编号 plt.text(-0.05, y_lower + 0.5 * size_cluster_i, str(i)) y_lower = y_upper + 10 # 为下一个簇留出10个单位的空白 plt.axvline(x=silhouette_avg, color="red", linestyle="--", label=f'平均轮廓系数: {silhouette_avg:.3f}') plt.xlabel("轮廓系数值") plt.ylabel("簇标签") plt.title(f"各簇轮廓系数分布图 (K={best_k})") plt.yticks([]) # 清除y轴刻度 plt.legend() plt.show()

第一张图直观展示了聚类结果,不同颜色和形状的点代表不同簇,黑色“X”是簇中心。第二张轮廓分析图则提供了更深入的洞察:每个簇的轮廓系数都绘制成了一条“山脉”,山脉的长度代表该簇的样本数,宽度代表轮廓系数的分布。理想情况下,所有“山脉”都应该宽且高(系数接近1),并且都超过平均线(红色虚线)。如果某个簇的“山脉”又矮又瘦,或者有很多样本的系数低于0,说明这个簇的划分可能不清晰,或者有样本被错误归类。

6. 高级话题与实战避坑指南

6.1 K-means的局限性及应对策略

K-means强大但并非万能,了解其局限才能正确使用:

  1. 需要预先指定K:如前所述,这是最大的挑战。务必结合肘部法则、轮廓系数和业务理解综合判断。
  2. 对初始值敏感:虽然k-means++初始化大大缓解了此问题,但仍可能陷入局部最优。多运行几次(n_init)是标准做法。
  3. 对异常值敏感:由于使用均值作为中心点,异常值会显著拉偏中心的位置。预处理时进行异常值检测和清洗至关重要。
  4. 假设簇是凸形且大小相近:K-means基于距离,天然地会试图找出球状或超球状的簇。对于流形、环形或不规则形状的簇,效果会很差。下图展示了它的失败案例:
from sklearn.datasets import make_moons, make_circles # 生成非线性数据 X_moons, _ = make_moons(n_samples=300, noise=0.05, random_state=42) X_circles, _ = make_circles(n_samples=300, factor=0.5, noise=0.05, random_state=42) datasets = [('Moons', X_moons), ('Circles', X_circles)] fig, axes = plt.subplots(1, 2, figsize=(12, 4)) for idx, (name, X_data) in enumerate(datasets): # 标准化 X_scaled_data = StandardScaler().fit_transform(X_data) # 用K-means聚类 kmeans_wrong = KMeans(n_clusters=2, random_state=42).fit(X_scaled_data) labels_wrong = kmeans_wrong.labels_ axes[idx].scatter(X_scaled_data[:, 0], X_scaled_data[:, 1], c=labels_wrong, s=50, cmap='viridis', alpha=0.7) axes[idx].set_title(f'K-means on {name} Data') axes[idx].set_xlabel('Feature 1') axes[idx].set_ylabel('Feature 2') plt.tight_layout() plt.show()

对于这类数据,需要考虑使用DBSCAN(基于密度)或谱聚类等算法。

  1. 仅适用于数值数据:K-means计算距离需要数值。对于分类数据,需要先进行编码(如独热编码)。

6.2 特征工程与降维技巧

聚类效果很大程度上取决于输入的特征。如果特征很多且存在冗余,聚类结果会难以解释。

  • 主成分分析(PCA):在聚类前,可以先使用PCA进行降维,既能去除噪声和冗余,又能将数据可视化到二维或三维空间,便于观察聚类效果。但要注意,PCA是线性降维,可能会扭曲数据的原始结构。
  • t-SNE或UMAP:对于可视化高维数据的聚类结构,t-SNE和UMAP是更强大的非线性降维工具。但它们通常只用于可视化,降维后的坐标不建议再输入给K-means做二次聚类,因为其距离关系已被改变。
from sklearn.decomposition import PCA # 假设我们有一个更高维的数据集 # 先进行PCA降维到2维以便可视化 pca = PCA(n_components=2) X_pca = pca.fit_transform(X_scaled) # 使用之前标准化过的数据 # 在PCA降维后的空间进行聚类 kmeans_pca = KMeans(n_clusters=4, random_state=42).fit(X_pca) # 可视化PCA空间中的聚类 plt.scatter(X_pca[:, 0], X_pca[:, 1], c=kmeans_pca.labels_, s=50, cmap='tab10', alpha=0.7) plt.scatter(kmeans_pca.cluster_centers_[:, 0], kmeans_pca.cluster_centers_[:, 1], s=300, c='black', marker='X', label='Centers') plt.xlabel('Principal Component 1') plt.ylabel('Principal Component 2') plt.title('Clustering in PCA-reduced Space') plt.legend() plt.show()

6.3 聚类结果的业务解读与应用

聚类本身不是终点,从聚类结果中提炼出业务洞察才是关键。模型跑完后,你需要分析每个簇的特征:

  • 计算每个簇在各个特征上的统计量:均值、中位数、分布等。这能帮你描述出每个簇的“画像”。例如,在客户分群中,你可能会发现“簇1:高收入、低活跃度”、“簇2:低收入、高活跃度”等。
  • 对比簇与簇之间的差异:是什么特征导致了它们被分为不同的簇?这些差异在业务上意味着什么?
  • 制定针对性策略:根据不同的簇画像,制定不同的运营、营销或产品策略。这才是数据驱动决策的闭环。
# 假设X是我们的原始特征数据(假设有两个特征:'消费金额', '登录频率') # labels是最终的聚类标签 df = pd.DataFrame(X, columns=['Feature1', 'Feature2']) # 这里用模拟数据代替 df['Cluster'] = final_labels # 查看每个簇的基本统计信息 cluster_summary = df.groupby('Cluster').agg(['mean', 'std', 'count']) print(cluster_summary) # 更直观的箱线图对比 fig, axes = plt.subplots(1, df.shape[1]-1, figsize=(14, 5)) for idx, col in enumerate(['Feature1', 'Feature2']): df.boxplot(column=col, by='Cluster', ax=axes[idx]) axes[idx].set_title(f'{col} by Cluster') axes[idx].set_xlabel('Cluster') axes[idx].set_ylabel(col) fig.suptitle('') # 清除自动生成的标题 plt.tight_layout() plt.show()

通过这样的分析,抽象的“簇0”、“簇1”就变成了具体可理解的用户群体或产品类别,后续的行动也就有了依据。记住,聚类是一个探索性的工具,它的结果需要人的智慧和业务知识来赋予意义并验证其合理性。不要完全迷信算法给出的分组,要时刻思考:“这样的分组,在现实世界中说得通吗?”

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/28 3:59:48

DocuQueue实战:构建AI Agent统一文档处理层的关键技术

DocuQueue 这类名字&#xff0c;最近在 AI Agent 的工程讨论里出现得越来越频繁。它给自己的定位是 Document Layer&#xff0c;也就是给 Agent 补一层统一的文档处理能力。我的理解很简单&#xff1a;当 Agent 需要读 PDF、Word、Markdown、网页正文&#xff0c;并且要把这些内…

作者头像 李华
网站建设 2026/8/28 3:58:19

蓝桥杯国赛A~D题解题思维与实战技巧深度解析

1. 项目概述&#xff1a;从“解题”到“解构”的思维跃迁又到了蓝桥杯国赛季&#xff0c;看着论坛和群里大家热火朝天地讨论A~D题&#xff0c;我仿佛回到了几年前自己参赛的时候。第十一届蓝桥杯国赛的A~D题&#xff0c;历来是区分选手基本功和思维灵活度的关键战场。这四道题&…

作者头像 李华
网站建设 2026/8/28 3:57:29

千人联机世界模型:从模型Demo到实时状态同步的工程挑战

RhOS-World: Khora 这个项目最值得关注的地方&#xff0c;不是“世界模型”这个标签&#xff0c;而是“千人联机”四个字。世界模型已经讲过很多&#xff0c;但大多数演示还停留在单机房间、单用户交互和离线仿真阶段。如果“千人联机”是一个可运行目标&#xff0c;那就说明世…

作者头像 李华
网站建设 2026/8/28 3:57:22

莫比乌斯带填字游戏:从网格到邻居函数的设计与实现

看到“Mbius-Strip Crosswords”这个标题时&#xff0c;我脑子里跳出来的第一件事&#xff0c;不是怎么剪一张纸带&#xff0c;而是一堆待处理的邻居关系。填字游戏在平面网格上并不复杂&#xff0c;m行n列的二维数组&#xff0c;上下左右四个方向&#xff0c;边界处停住&#…

作者头像 李华
网站建设 2026/8/28 3:55:35

蓝桥杯国赛题解:状态压缩DP在“搭积木”问题中的应用

1. 从“搭积木”到“状态压缩”&#xff1a;一道蓝桥杯国赛题的深度拆解提起“搭积木”&#xff0c;很多人脑海里浮现的是童年时那些色彩斑斓的塑料块。但在2018年蓝桥杯国赛的赛场上&#xff0c;这道名为“搭积木”的题目&#xff0c;却让无数参赛者感受到了从具象到抽象、从直…

作者头像 李华