🧮 规格排列组合全攻略:从商品 SKU 到密码设计,一文搞定所有组合难题
电商、编程、生活中的排列组合实用指南,2026 年最新版
一、前言
排列组合问题在我们的日常生活和工作中无处不在:
- 🛒 电商平台:商品 SKU 如何生成?
- 🔐 安全系统:密码有多少种可能?
- 🍱 餐饮行业:套餐搭配有多少种?
- 🎮 游戏设计:装备组合如何计算?
- 📊 数据分析:实验方案如何设计?
掌握排列组合的核心逻辑,能让你在产品设计、系统开发、数据分析等场景中游刃有余。本文将通过实际案例 + 计算公式 + 代码实现,带你彻底搞懂规格排列组合问题。
二、核心概念辨析
2.1 排列 vs 组合
| 概念 | 定义 | 是否考虑顺序 | 公式 |
|---|---|---|---|
| 排列 (Permutation) | 从 n 个元素中选 r 个,考虑顺序 | ✅ 是 | P(n,r) = n!/(n-r)! |
| 组合 (Combination) | 从 n 个元素中选 r 个,不考虑顺序 | ❌ 否 | C(n,r) = n!/r!(n-r)! |
| 笛卡尔积 | 多个集合的所有可能组合 | ✅ 是 | n₁ × n₂ × ... × nₖ |
2.2 规格组合的特殊性
规格组合通常是笛卡尔积问题,即每个维度独立选择:
颜色:红、蓝、绿 (3 种)
尺寸:S、M、L (3 种)
材质:棉、麻 (2 种)
总组合数 = 3 × 3 × 2 = 18 种 SKU
三、常见应用场景详解
3.1 电商商品 SKU 生成
场景:一件 T 恤有多个颜色、尺寸、材质选项
// 商品规格配置
const productSpecs = {
color: ['红色', '蓝色', '黑色', '白色'],
size: ['S', 'M', 'L', 'XL', 'XXL'],
material: ['纯棉', '涤棉']
};
// 计算总 SKU 数量
function calculateSKU(specs) {
return Object.values(specs).reduce((acc, arr) => acc * arr.length, 1);
}
console.log(`总 SKU 数:${calculateSKU(productSpecs)}`); // 4 × 5 × 2 = 40
完整 SKU 生成代码:
function generateSKU(specs) {
const keys = Object.keys(specs);
const result = [];
function backtrack(index, current) {
if (index === keys.length) {
result.push({...current});
return;
}
const key = keys[index];
for (const value of specs[key]) {
current[key] = value;
backtrack(index + 1, current);
}
}
backtrack(0, {});
return result;
}
// 使用示例
const skus = generateSKU(productSpecs);
console.log(`生成 ${skus.length} 个 SKU`);
console.log(skus.slice(0, 5)); // 查看前 5 个
输出示例:
[ {"color": "红色", "size": "S", "material": "纯棉"}, {"color": "红色", "size": "S", "material": "涤棉"}, {"color": "红色", "size": "M", "material": "纯棉"}, {"color": "红色", "size": "M", "material": "涤棉"}, {"color": "红色", "size": "L", "material": "纯棉"}]
3.2 密码组合计算
场景:计算不同密码规则下的可能组合数
| 密码规则 | 字符集大小 | 长度 | 组合数 |
|---|---|---|---|
| 纯数字 | 10 | 6 位 | 10⁶ = 1,000,000 |
| 纯小写字母 | 26 | 6 位 | 26⁶ ≈ 3.09 亿 |
| 大小写 + 数字 | 62 | 8 位 | 62⁸ ≈ 218 万亿 |
| 全字符集 | 95 | 12 位 | 95¹² ≈ 5.4×10²³ |
密码强度计算器:
def calculate_password_strength(charset_size, length):
"""计算密码组合数"""
combinations = charset_size ** length
return combinations
def estimate_crack_time(combinations, attempts_per_second=1000000000):
"""估算破解时间(假设每秒 10 亿次尝试)"""
seconds = combinations / attempts_per_second
if seconds < 60:
return f"{seconds:.2f} 秒"
elif seconds < 3600:
return f"{seconds/60:.2f} 分钟"
elif seconds < 86400:
return f"{seconds/3600:.2f} 小时"
elif seconds < 31536000:
return f"{seconds/86400:.2f} 天"
else:
return f"{seconds/31536000:.2f} 年"
# 示例
charset = 62 # 大小写字母 + 数字
length = 12
combinations = calculate_password_strength(charset, length)
print(f"组合数:{combinations:,}")
print(f"破解时间:{estimate_crack_time(combinations)}")
3.3 餐饮套餐搭配
场景:餐厅套餐选择组合
主菜:5 种
配菜:3 种(可选 2 种)
饮料:4 种
甜点:2 种(可选)
计算方式:
- 主菜:C(5,1) = 5
- 配菜:C(3,2) = 3
- 饮料:C(4,1) = 4
- 甜点:C(2,0) + C(2,1) = 1 + 2 = 3(可选可不选)
总组合 = 5 × 3 × 4 × 3 = 180 种
代码实现:
from math import comb
def calculate_menu_combinations():
# 主菜选 1 种
main_dish = comb(5, 1)
# 配菜选 2 种
side_dish = comb(3, 2)
# 饮料选 1 种
drink = comb(4, 1)
# 甜点可选 0 或 1 种
dessert = comb(2, 0) + comb(2, 1)
total = main_dish * side_dish * drink * dessert
return {
'main_dish': main_dish,
'side_dish': side_dish,
'drink': drink,
'dessert': dessert,
'total': total
}
result = calculate_menu_combinations()
print(f"套餐总组合数:{result['total']}")
3.4 比赛对阵安排
场景:单循环赛制比赛场次计算
n 支队伍,每两队比赛一场
总场次 = C(n,2) = n×(n-1)/2
示例:
- 4 支队伍:C(4,2) = 6 场
- 8 支队伍:C(8,2) = 28 场
- 16 支队伍:C(16,2) = 120 场
淘汰赛场次计算:
n 支队伍单败淘汰赛
总场次 = n - 1
因为每场淘汰 1 队,最后剩 1 队冠军
3.5 产品配置选项
场景:电脑/汽车等可配置产品的选项组合
const laptopConfig = {
cpu: ['i5', 'i7', 'i9'], // 3 种
ram: ['8GB', '16GB', '32GB'], // 3 种
storage: ['256GB', '512GB', '1TB'], // 3 种
gpu: ['集成', '独显'], // 2 种
color: ['银', '灰', '黑'] // 3 种
};
// 理论最大组合
const maxCombinations = 3 * 3 * 3 * 2 * 3 = 162 种
// 但实际有限制:
// - i5 不能配独显
// - 8GB 不能配 1TB 存储
// 实际有效组合会少于理论值
带约束的组合生成:
function generateValidConfigs(specs, constraints) {
const allConfigs = generateSKU(specs);
return allConfigs.filter(config => {
// 检查是否满足所有约束
return constraints.every(constraint => {
return constraint(config);
});
});
}
// 定义约束条件
const constraints = [
config => !(config.cpu === 'i5' && config.gpu === '独显'),
config => !(config.ram === '8GB' && config.storage === '1TB'),
];
const validConfigs = generateValidConfigs(laptopConfig, constraints);
console.log(`有效配置数:${validConfigs.length}`);
四、排列组合计算公式速查
4.1 基础公式表
| 类型 | 场景 | 公式 | 示例 |
|---|---|---|---|
| 全排列 | n 个元素全部排列 | n! | 5! = 120 |
| 选排列 | n 选 r 排列 | P(n,r) = n!/(n-r)! | P(5,3) = 60 |
| 组合 | n 选 r 组合 | C(n,r) = n!/r!(n-r)! | C(5,3) = 10 |
| 重复排列 | n 种选 r 次可重复 | nʳ | 5³ = 125 |
| 重复组合 | n 种选 r 次可重复 | C(n+r-1,r) | C(5+3-1,3) = 35 |
| 笛卡尔积 | 多规格组合 | n₁×n₂×...×nₖ | 3×4×2 = 24 |
4.2 常用阶乘参考
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5,040
8! = 40,320
9! = 362,880
10! = 3,628,800
五、性能优化技巧
5.1 大数据量处理
当组合数过大时,需要优化策略:
// ❌ 不要:生成所有组合再筛选
const all = generateAllCombinations(); // 可能数十亿
const filtered = all.filter(...);
// ✅ 推荐:边生成边筛选(剪枝)
function generateWithPruning(specs, constraints, index = 0, current = {}) {
if (index === Object.keys(specs).length) {
return [current];
}
const key = Object.keys(specs)[index];
const results = [];
for (const value of specs[key]) {
const testConfig = {...current, [key]: value};
// 提前剪枝:如果当前部分已不满足约束,跳过
if (!constraints.some(c => !c(testConfig))) {
results.push(...generateWithPruning(specs, constraints, index + 1, testConfig));
}
}
return results;
}
5.2 分页处理
function getCombinationsPage(specs, page, pageSize) {
const total = calculateSKU(specs);
const start = (page - 1) * pageSize;
const end = Math.min(start + pageSize, total);
// 使用迭代器按需生成
const iterator = createCombinationIterator(specs);
const result = [];
let count = 0;
for (const combo of iterator) {
if (count >= start && count < end) {
result.push(combo);
}
count++;
if (count >= end) break;
}
return {
data: result,
total,
page,
pageSize
};
}
5.3 缓存策略
// Laravel 缓存示例
use Illuminate\Support\Facades\Cache;
function getCachedSKU($productId) {
return Cache::remember(
"product_sku:{$productId}",
3600, // 缓存 1 小时
function () use ($productId) {
return generateProductSKU($productId);
}
);
}
六、常见陷阱与解决方案
| 陷阱 | 说明 | 解决方案 |
|---|---|---|
| 📈 组合爆炸 | 规格过多导致组合数过大 | 限制规格数量,使用分页 |
| 🔗 依赖关系 | 某些规格组合无效 | 添加约束条件过滤 |
| 💾 存储压力 | SKU 数据量过大 | 动态生成,不全部存储 |
| ⚡ 性能问题 | 实时计算耗时长 | 预计算 + 缓存 |
| 🔄 同步问题 | 规格变更后缓存失效 | 设置合理过期时间 |
七、实战案例:电商 SKU 管理系统
7.1 数据库设计
-- 商品表
CREATE TABLE products (
id BIGINT PRIMARY KEY,
name VARCHAR(255),
base_price DECIMAL(10,2)
);
-- 规格组表
CREATE TABLE spec_groups (
id BIGINT PRIMARY KEY,
product_id BIGINT,
name VARCHAR(50) -- 颜色、尺寸等
);
-- 规格值表
CREATE TABLE spec_values (
id BIGINT PRIMARY KEY,
spec_group_id BIGINT,
value VARCHAR(50) -- 红色、S 等
);
-- SKU 表
CREATE TABLE skus (
id BIGINT PRIMARY KEY,
product_id BIGINT,
sku_code VARCHAR(50) UNIQUE,
price DECIMAL(10,2),
stock INT,
specs JSON -- 存储规格组合
);
7.2 SKU 生成服务
<?php
class SKUService
{
public function generateSKUs($productId)
{
$specGroups = SpecGroup::where('product_id', $productId)
->with('specValues')->get();
$specs = [];
foreach ($specGroups as $group) {
$specs[$group->name] = $group->specValues
->pluck('value')->toArray();
}
$combinations = $this->cartesianProduct($specs);
foreach ($combinations as $combo) {
$sku = new SKU();
$sku->product_id = $productId;
$sku->sku_code = $this->generateSKUCode($productId, $combo);
$sku->specs = json_encode($combo);
$sku->price = $this->calculatePrice($productId, $combo);
$sku->stock = 0;
$sku->save();
}
return count($combinations);
}
private function cartesianProduct($arrays)
{
$result = [[]];
foreach ($arrays as $key => $values) {
$temp = [];
foreach ($result as $item) {
foreach ($values as $value) {
$temp[] = array_merge($item, [$key => $value]);
}
}
$result = $temp;
}
return $result;
}
}