提出问题

集装箱海运家具, 沙发, 茶几, 椅子等等, 有多少套家具,以及每个家具的长宽高都会告诉你.

把所有的家具都装进集装箱里, 要求通过算法算出一共需要多少集装箱.

  • 1.要考虑怎样装, 需要的集装箱才最少, 因为一个集装箱很贵的.
  • 2.要考虑怎样摆放, 占用的体积最小, 找最优解. 比如, 茶几和沙发摞在一起, 旁边还有空余的位置, 那是否还可以再塞个椅子进去。

你会怎样设计算法?

分解问题

这是一个典型的三维装箱问题(3D Bin Packing Problem, 3D-BPP),它是NP-hard问题,意味着没有已知的算法能在多项式时间内找到绝对最优解。因此,我们通常使用启发式算法(Heuristics)或近似算法来寻找一个足够好的解,即尽量少用集装箱。

以下是一个使用C#设计启发式算法的思路和代码框架:

核心思路:

  1. 容器选择: 通常海运有标准集装箱尺寸,如20GP, 40GP, 40HC。我们需要确定使用哪种尺寸的集装箱,或者允许算法选择混合使用(这会更复杂)。为简化,我们先假设使用同一种标准尺寸的集装箱,例如40HC(内尺寸约为:长12.03m, 宽2.35m, 高2.69m)。注意:单位要统一! 比如都用毫米(mm)或厘米(cm)。
  2. 物品表示: 每个家具是一个三维长方体,有长、宽、高。
  3. 旋转: 家具可以旋转摆放以更好地利用空间。一个长方体有最多6种基本朝向(不考虑绕垂直轴的90度旋转,因为那可以通过交换长宽实现)。
  4. 放置策略: 这是算法的关键。需要决定:
    • 物品顺序: 先放大件还是小件?通常先放大件(如按体积或最长边排序)效果较好(First Fit Decreasing - FFD 变种)。
    • 放置位置: 在容器的哪个位置放置物品?常用的策略是在可选空间中寻找“最合适”的位置,例如“最低-最左-最靠里”的角落。
    • 空间管理: 如何记录和管理容器内的剩余空间?这可以很复杂。常见方法有:
      • 层叠法(Layer-based): 一层一层地填充。
      • 最大空间法(Maximal Spaces): 维护一个剩余空间块的列表。
      • 三维坐标/体素法: 将容器空间离散化(计算量可能很大)。
      • 简单坐标点法: 维护一组可以放置物品的“锚点”(通常是已放置物品的角点或容器的角点)。
  5. 算法流程 (启发式 - 基于 FFD 和锚点/最低位置策略):
    • 初始化:

      • 获取所有家具列表及其尺寸。
      • 定义集装箱内部尺寸。
      • 对家具列表进行排序(例如,按体积降序)。
      • 创建一个空的集装箱列表。
    • 主循环: 遍历排序后的家具列表:
      • 对于当前家具 item:

        • 尝试放入现有集装箱: 遍历当前已打开的集装箱列表 containers

          • 对于每个集装箱 container:

            • 尝试找到一个有效位置放置 item(考虑所有6种旋转)。
            • 寻找位置 (启发式):
              • 维护一个该容器内可放置物品的“锚点”列表 anchorPoints(初始为 (0,0,0))。
              • 按一定顺序(如 Z坐标升序, Y升序, X升序)遍历 anchorPoints
              • 对每个锚点 p,尝试 item 的所有6种旋转 r
              • 检查 item 以旋转 r 放置在 p 时:
                • 是否完全在集装箱边界内?
                • 是否与该集装箱内已放置的任何其他物品 placedItem 发生碰撞?
              • 如果找到第一个有效的位置 (p, r)
                • item 放置在 containerp 点,使用旋转 r。记录其位置和尺寸。
                • 更新 containeranchorPoints:移除 p,并根据新放置的 item 添加新的潜在锚点(例如,新物品的右上角、前上角、右前角等)。需要仔细处理,避免重复和无效点。
                • 标记 item 已放置,跳出当前集装箱的尝试,处理下一个家具。
        • 如果现有集装箱都放不下:
          • 创建一个新的集装箱 newContainer
          • item 放入 newContainer(通常放在 (0,0,0) 位置,选择一个合适的旋转)。必须检查: 如果物品本身就比集装箱大,则无法放置,需要报错。
          • 记录放置信息,初始化 newContainer 的锚点列表。
          • newContainer 添加到 containers 列表中。
    • 结束: 所有家具处理完毕后,containers 列表的大小就是所需的集装箱数量。

C# 代码框架:

using System;
using System.Collections.Generic;
using System.Linq; // 3D Point/Vector Structure
public struct Point3D
{
public decimal X, Y, Z;
public Point3D(decimal x, decimal y, decimal z) { X = x; Y = y; Z = z; }
public override string ToString() => $"({X}, {Y}, {Z})";
} // Dimensions Structure
public struct Dimensions
{
public decimal Length, Width, Height; // L, W, H correspond to X, Y, Z axes when placed
public decimal Volume => Length * Width * Height;
public Dimensions(decimal l, decimal w, decimal h) { Length = l; Width = w; Height = h; }
public override string ToString() => $"[{Length}x{Width}x{Height}]"; // Get dimensions for different rotations
public Dimensions GetRotation(int rotationType)
{
switch (rotationType)
{
case 0: return new Dimensions(Length, Width, Height); // LWH (XYZ)
case 1: return new Dimensions(Length, Height, Width); // LHW (XZY)
case 2: return new Dimensions(Width, Length, Height); // WLH (YXZ)
case 3: return new Dimensions(Width, Height, Length); // WHL (YZX)
case 4: return new Dimensions(Height, Length, Width); // HLW (ZXY)
case 5: return new Dimensions(Height, Width, Length); // HWL (ZYX)
default: throw new ArgumentOutOfRangeException(nameof(rotationType));
}
}
} // Represents a furniture item
public class Item
{
public string Name { get; }
public Dimensions OriginalDimensions { get; }
public decimal Volume => OriginalDimensions.Volume;
// Potentially add weight, fragility, stacking constraints later public Item(string name, decimal length, decimal width, decimal height)
{
Name = name;
// Ensure non-negative dimensions
OriginalDimensions = new Dimensions(
Math.Max(0, length),
Math.Max(0, width),
Math.Max(0, height)
);
} public override string ToString() => $"{Name} {OriginalDimensions}";
} // Represents an item placed inside a container
public class PlacedItem
{
public Item SourceItem { get; }
public Point3D Position { get; } // Bottom-Back-Left corner of the item in container coordinates
public Dimensions PlacedDimensions { get; } // Dimensions after rotation // Bounding Box for collision detection
public Point3D MinCorner => Position;
public Point3D MaxCorner => new Point3D(Position.X + PlacedDimensions.Length, Position.Y + PlacedDimensions.Width, Position.Z + PlacedDimensions.Height); public PlacedItem(Item sourceItem, Point3D position, Dimensions placedDimensions)
{
SourceItem = sourceItem;
Position = position;
PlacedDimensions = placedDimensions;
} // AABB Collision Check
public bool Intersects(PlacedItem other)
{
return (this.MinCorner.X < other.MaxCorner.X && this.MaxCorner.X > other.MinCorner.X) &&
(this.MinCorner.Y < other.MaxCorner.Y && this.MaxCorner.Y > other.MinCorner.Y) &&
(this.MinCorner.Z < other.MaxCorner.Z && this.MaxCorner.Z > other.MinCorner.Z);
}
// Check if this item intersects with a potential placement
public bool Intersects(Point3D potentialPos, Dimensions potentialDims)
{
Point3D potMin = potentialPos;
Point3D potMax = new Point3D(potentialPos.X + potentialDims.Length, potentialPos.Y + potentialDims.Width, potentialPos.Z + potentialDims.Height); return (this.MinCorner.X < potMax.X && this.MaxCorner.X > potMin.X) &&
(this.MinCorner.Y < potMax.Y && this.MaxCorner.Y > potMin.Y) &&
(this.MinCorner.Z < potMax.Z && this.MaxCorner.Z > potMin.Z);
}
} // Represents a single container
public class Container
{
public int Id { get; }
public Dimensions Dimensions { get; }
public List<PlacedItem> PlacedItems { get; }
public List<Point3D> AnchorPoints { get; private set; } // Potential placement corners // Keep track of occupied volume/space for heuristics? (Optional) public Container(int id, decimal length, decimal width, decimal height)
{
Id = id;
Dimensions = new Dimensions(length, width, height);
PlacedItems = new List<PlacedItem>();
// Start with the main corner as the only anchor point
AnchorPoints = new List<Point3D> { new Point3D(0, 0, 0) };
} // Tries to find a position and rotation to place the item
public bool TryPlaceItem(Item item, out PlacedItem placement)
{
placement = null; // Sort anchor points: typically Z, Y, X ascending to fill bottom-up, left-right, back-front
var sortedAnchors = AnchorPoints.OrderBy(p => p.Z).ThenBy(p => p.Y).ThenBy(p => p.X).ToList(); foreach (Point3D anchor in sortedAnchors)
{
for (int rotationType = 0; rotationType < 6; rotationType++)
{
Dimensions rotatedDims = item.OriginalDimensions.GetRotation(rotationType); // Check if item fits within container boundaries at this anchor
if (anchor.X + rotatedDims.Length <= Dimensions.Length &&
anchor.Y + rotatedDims.Width <= Dimensions.Width &&
anchor.Z + rotatedDims.Height <= Dimensions.Height)
{
// Check for collisions with already placed items
bool collision = false;
foreach (PlacedItem existingItem in PlacedItems)
{
// Simple AABB check
if (existingItem.Intersects(anchor, rotatedDims))
{
collision = true;
break;
}
} if (!collision)
{
// Found a valid placement!
placement = new PlacedItem(item, anchor, rotatedDims);
return true; // Return the first valid placement found
}
}
}
}
return false; // Could not find a place for this item in this container
} // Actually place the item and update anchors
public void PlaceItem(PlacedItem placement)
{
PlacedItems.Add(placement); // Update anchor points - this is a crucial and potentially complex step
// A simple strategy: remove the used anchor and add new potential anchors
Point3D placedPos = placement.Position;
Dimensions placedDims = placement.PlacedDimensions; // Remove the anchor point that was used for placement
AnchorPoints.RemoveAll(p => p.X == placedPos.X && p.Y == placedPos.Y && p.Z == placedPos.Z); // Add new potential anchor points based on the corners of the placed item
// Only add points that are within the container bounds
// More sophisticated logic would check if these points are already covered or invalid
Point3D[] potentialNewAnchors = {
new Point3D(placedPos.X + placedDims.Length, placedPos.Y, placedPos.Z),
new Point3D(placedPos.X, placedPos.Y + placedDims.Width, placedPos.Z),
new Point3D(placedPos.X, placedPos.Y, placedPos.Z + placedDims.Height)
}; foreach (var newAnchor in potentialNewAnchors)
{
// Basic check: is it inside the container?
if (newAnchor.X < Dimensions.Length && newAnchor.Y < Dimensions.Width && newAnchor.Z < Dimensions.Height)
{
// Basic check: does it overlap with the item just placed? (Shouldn't if corners are correct)
// More advanced: check if it's inside *any* existing item or outside container
// Avoid duplicates
if (!AnchorPoints.Any(p => p.X == newAnchor.X && p.Y == newAnchor.Y && p.Z == newAnchor.Z))
{
// Further check: Is this point supported? (Simple heuristic: is Z>0 requires something below?)
// For simplicity now, just add if inside bounds and not duplicate.
AnchorPoints.Add(newAnchor);
}
}
} // Optional: Refine anchor points - remove points that are now inside the newly placed item
// AnchorPoints.RemoveAll(p => IsInside(p, placement)); // Need IsInside check // Optional: Sort anchors again if needed for the next TryPlaceItem call
// AnchorPoints = AnchorPoints.OrderBy(p => p.Z).ThenBy(p => p.Y).ThenBy(p => p.X).ToList();
} // Helper to check if a point is strictly inside a placed item's volume
private bool IsInside(Point3D point, PlacedItem item)
{
return point.X > item.MinCorner.X && point.X < item.MaxCorner.X &&
point.Y > item.MinCorner.Y && point.Y < item.MaxCorner.Y &&
point.Z > item.MinCorner.Z && point.Z < item.MaxCorner.Z;
}
} // The main packer class
public class Packer
{
public Dimensions ContainerDimensions { get; } public Packer(decimal containerLength, decimal containerWidth, decimal containerHeight)
{
ContainerDimensions = new Dimensions(containerLength, containerWidth, containerHeight);
} public List<Container> PackItems(List<Item> itemsToPack)
{
// 1. Sort items (e.g., by volume descending) - FFD heuristic
var sortedItems = itemsToPack.OrderByDescending(item => item.Volume).ToList(); List<Container> containers = new List<Container>();
int containerIdCounter = 1;
HashSet<Item> packedItems = new HashSet<Item>(); // Keep track of packed items foreach (var item in sortedItems)
{
if (packedItems.Contains(item)) continue; // Should not happen with list processing, but safe check bool placed = false; // 2. Try placing in existing containers
foreach (var container in containers)
{
if (container.TryPlaceItem(item, out PlacedItem placement))
{
container.PlaceItem(placement);
Console.WriteLine($"Placed {item.Name} in Container {container.Id} at {placement.Position} with rotation {placement.PlacedDimensions}");
placed = true;
packedItems.Add(item);
break; // Move to the next item (First Fit)
}
} // 3. If not placed, open a new container
if (!placed)
{
// Check if the item can fit in an empty container at all (any rotation)
bool fitsAnyhow = false;
PlacedItem initialPlacement = null;
for(int r=0; r<6; ++r)
{
var dims = item.OriginalDimensions.GetRotation(r);
if(dims.Length <= ContainerDimensions.Length &&
dims.Width <= ContainerDimensions.Width &&
dims.Height <= ContainerDimensions.Height)
{
initialPlacement = new PlacedItem(item, new Point3D(0,0,0), dims);
fitsAnyhow = true;
break;
}
} if (fitsAnyhow)
{
Container newContainer = new Container(containerIdCounter++, ContainerDimensions.Length, ContainerDimensions.Width, ContainerDimensions.Height);
newContainer.PlaceItem(initialPlacement); // Place at (0,0,0) with the found rotation
containers.Add(newContainer);
packedItems.Add(item);
Console.WriteLine($"Opened Container {newContainer.Id} and placed {item.Name} at {initialPlacement.Position} with rotation {initialPlacement.PlacedDimensions}");
}
else
{
// Item is too large for the container
Console.WriteLine($"Error: Item {item.Name} ({item.OriginalDimensions}) is too large to fit in the container ({ContainerDimensions}).");
// Decide how to handle this - skip item, throw exception?
}
}
} Console.WriteLine($"\nPacking complete. Total containers used: {containers.Count}");
return containers;
}
} // Example Usage
public class Example
{
public static void Main(string[] args)
{
// --- Configuration ---
// Use internal dimensions of a 40ft High Cube container in cm
decimal containerL = 1203m;
decimal containerW = 235m;
decimal containerH = 269m; // Use decimal for potentially better precision with cm/mm
Console.WriteLine($"Using Container Dimensions: {containerL}cm x {containerW}cm x {containerH}cm"); // --- Furniture List (Example Data in cm) ---
List<Item> furniture = new List<Item>
{
// Sofas (L x W x H)
new Item("Sofa 1", 200m, 90m, 80m),
new Item("Sofa 2", 220m, 95m, 85m),
// Coffee Tables
new Item("Coffee Table 1", 120m, 60m, 45m),
new Item("Coffee Table 2", 100m, 100m, 40m),
// Chairs
new Item("Chair 1", 60m, 60m, 90m),
new Item("Chair 2", 60m, 60m, 90m),
new Item("Chair 3", 55m, 58m, 95m),
new Item("Chair 4", 55m, 58m, 95m),
// Larger item test
new Item("Wardrobe", 150m, 60m, 200m),
// More items
new Item("Bookshelf", 80m, 30m, 180m),
new Item("Side Table 1", 40m, 40m, 60m),
new Item("Side Table 2", 40m, 40m, 60m),
new Item("Ottoman", 70m, 70m, 40m), // Add many more small items to test filling gaps
// ... (e.g., 20 small boxes 30x30x30)
// for (int i = 0; i < 20; i++) { furniture.Add(new Item($"Small Box {i+1}", 30m, 30m, 30m)); }
}; Console.WriteLine($"\nItems to pack ({furniture.Count} total):");
foreach(var item in furniture) Console.WriteLine($"- {item}"); // --- Packing ---
Packer packer = new Packer(containerL, containerW, containerH);
List<Container> resultContainers = packer.PackItems(furniture); // --- Output Results ---
Console.WriteLine($"\n--- Packing Summary ---");
Console.WriteLine($"Total Containers Needed: {resultContainers.Count}");
for (int i = 0; i < resultContainers.Count; i++)
{
decimal packedVolume = resultContainers[i].PlacedItems.Sum(p => p.PlacedDimensions.Volume);
decimal totalVolume = resultContainers[i].Dimensions.Volume;
decimal utilization = totalVolume > 0 ? (packedVolume / totalVolume) * 100 : 0;
Console.WriteLine($"Container {resultContainers[i].Id}: Contains {resultContainers[i].PlacedItems.Count} items. Volume Utilization: {utilization:F2}%");
// Optionally print items in each container
// foreach(var placed in resultContainers[i].PlacedItems) {
// Console.WriteLine($" - {placed.SourceItem.Name} at {placed.Position} as {placed.PlacedDimensions}");
// }
}
}
}

关键点和改进方向:

  1. 单位一致性: 代码中使用 decimal 和厘米(cm)作为例子。确保所有输入尺寸和容器尺寸使用相同单位。
  2. 锚点管理: PlaceItem 中更新锚点的逻辑非常基础。更高级的算法会更智能地管理剩余空间,例如使用Maximal Spaces或更复杂的锚点生成/消除规则,以避免产生无法使用的小碎片空间或无效锚点。
  3. 启发式选择:
    • 排序: 按体积降序排序是常见的 FFD 启发式。也可以尝试按最长边、面积等排序。
    • 锚点选择: TryPlaceItem 中按 Z, Y, X 排序锚点尝试填充底部。可以尝试其他顺序。
    • 旋转选择: 当前代码尝试所有6种旋转。可以根据锚点和周围空间优先尝试更“可能”成功的旋转。
  4. 性能: 对于大量物品,碰撞检测(Intersects)和锚点管理会成为瓶颈。可能需要空间分区数据结构(如 Octree)来加速碰撞检测。
  5. 稳定性/约束: 当前算法纯粹是几何装箱。没有考虑:
    • 重量: 重物应在底部。
    • 易碎性: 不能将重物压在易碎品上。
    • 堆叠限制: 某些物品不能堆叠或只能承受有限的重量。
    • 方向: 有些家具(如沙发)可能不能倒置或侧放。
    • 这些约束需要添加到 Item 类和 TryPlaceItem 的检查逻辑中,会显著增加复杂性。
  6. 最优性: 这个启发式算法不保证找到绝对最少的集装箱数量。更复杂的算法(如禁忌搜索、模拟退火、遗传算法)或精确算法(分支定界法,但非常慢)可能得到更好的结果,但实现难度大得多。
  7. 用户界面/输入: 实际应用中,需要从文件、数据库或UI读取家具列表和尺寸。
  8. 可视化: 输出放置坐标后,使用3D可视化工具展示装箱结果会非常有帮助。

这个框架提供了一个起点。根据实际需求的复杂性和对最优性的要求,可以进一步优化和扩展这个算法。

三维装箱问题(3D Bin Packing Problem, 3D-BPP)的更多相关文章

  1. The 17th Zhejiang Provincial Collegiate Programming Contest B.Bin Packing Problem

    题意 给定n个物品,和一个容量为C的桶 需要求出为了装下这些物品,分别使用首次适应算法(FF).最佳适应算法(BF)需要的桶的数量 \(n \leq 10^6\) 思路 BF:容易想到可以用set维护 ...

  2. UVa 102 - Ecological Bin Packing(规律,统计)

    题目来源:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=3&pa ...

  3. UVa - 102 - Ecological Bin Packing

    Background Bin packing, or the placement of objects of certain weights into different bins subject t ...

  4. 使用webgl(three.js)搭建3D智慧园区、3D大屏,3D楼宇,智慧灯杆三维展示,3D灯杆,web版3D,bim管理系统——第六课

    前言: 今年是建国70周年,爱国热情异常的高涨,为自己身在如此安全.蓬勃发展的国家深感自豪. 我们公司楼下为庆祝国庆,拉了这样的标语,每个人做好一件事,就组成了我们强大的祖国. 看到这句话,深有感触, ...

  5. paper 157:文章解读--How far are we from solving the 2D & 3D Face Alignment problem?-(and a dataset of 230,000 3D facial landmarks)

    文章:How far are we from solving the 2D & 3D Face Alignment problem?-(and a dataset of 230,000 3D ...

  6. 如何用webgl(three.js)搭建处理3D隧道、3D桥梁、3D物联网设备、3D高速公路、三维隧道桥梁设备监控-第十一课

    开篇废话: 跟之前的文章一样,开篇之前,总要写几句废话,大抵也是没啥人看仔细文字,索性我也想到啥就聊啥吧. 这次聊聊疫情,这次全国多地的疫情挺严重的,本人身处深圳,深圳这几日报导都是几十几十的新增病例 ...

  7. Bin Packing

    Bin Packing 题目链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=85904#problem/F 题目: A set of  ...

  8. 3d模型 手办制作 3d model manual production

    3d模型 手办制作 3d model manual production 作者:韩梦飞沙 Author:han_meng_fei_sha 邮箱:313134555@qq.com E-mail: 313 ...

  9. 单图像三维重建、2D到3D风格迁移和3D DeepDream

    作者:Longway Date:2020-04-25 来源:单图像三维重建.2D到3D风格迁移和3D DeepDream 项目网址:http://hiroharu-kato.com/projects_ ...

  10. 物联网3D,物业基础设施3D运维,使用webgl(three.js)与物联网设备结合案例。搭建智慧楼宇,智慧园区,3D园区、3D物业设施,3D楼宇管理系统——第八课

    写在前面的废话: 很久没有更新文章了,这段时间一直忙于项目落地,虽然很忙,但是感觉没有总结,没有提炼的日子,总是让人感觉飘飘忽忽的. 所幸放下一些事,抽出一些时间,把近期的项目做一些整理与记录.也算是 ...

随机推荐

  1. JS深度理解

    事件循环 程序运行需要有自己专属的内存空间,可以把这块内存简单理解为进程 每个应用至少有一个进程,进程间相互独立,要通信,也需要双方同意 线程 有进程后,就可以运行程序的代码 运行代码的 [人] 称为 ...

  2. Q:oracle小于1的number,不显示小数点前的0?

    oracle存储number类型数字  如果数字小于1 如0.35就会存储.35  省略掉前面的数字0 方法1: oracle 数据库字段值为小于1的小数时,转换到char类型处理,会丢失小数点前面的 ...

  3. Kafka常用命令总结

    1.清空某个topic数据 需要在service设置delete.topic.enable=true ./bin/kafka-topics.sh --zookeeper 172.23.75.105:2 ...

  4. Calcite 获取jdbc连接流程

    一.类调用 简介:calcite可以连接各数据源,做查询.可以收口查询权限,查询多引擎需求 二. 获取Connection发送的请求 请求介绍文档:https://calcite.apache.org ...

  5. 每次下载idea都必装的十个插件!

    IDEA必备插件 Alibaba Java Coding Guidelines 功能: 阿里巴巴Java开发规范插件,用于代码规范检查. 特点: 基于阿里巴巴Java开发手册,提供实时代码规范检查,帮 ...

  6. CF895C Square Subsets 题解

    看到 \(a_i\le 70\) 后,发现 \(n\) 啥用没有,因为只需要枚举 \(1-70\) 选几个即可. 看到求完全平方数后,想到分解质因数,由于 \(a_i\le 70\),所以只有 \(1 ...

  7. 什么是A型或者B型剩余电流保护器?

    我国的剩余电流保护装置(RCD)指导性标准GB/Z 6829-2008(IEC/TR 60755:2008,MOD)<剩余电流动作保护器的一般要求> 从产品的基本结构.剩余电流类型.脱扣方 ...

  8. 【P5】Verilog搭建流水线MIPS-CPU

    课下 Thinking_Log 1.为何不允许直接转发功能部件的输出 直接转发会使一些组合逻辑部件增加新的长短不一的操作延迟,不利于计算设置流水线是时钟频率(保证流水线吞吐量?). 2.jal中将NP ...

  9. 写于vue3.0发布前夕的helloworld之四

    OK.接上回到render: with(this){return _c('div',{attrs:{"id":"app"}},[_v(_s(msg))])} 接 ...

  10. “决策-寻找过程”的黄金秘密工具,1/e 法则之应用(尤其日常生活中的应用)

    https://www.ccgxk.com/magicword/327.html 目录 引言 著名的 1/e 法则内容和解释 应用到生活中的 1/e 法则是什么样? 相亲案例 看书.看电影案例 生活质 ...