1. 题目背景与核心考察点
2023年12月GESP C++七级考试中的"商品交易"题目,是一道典型的算法与数据结构综合应用题。这道题目主要考察以下几个核心能力:
- 复杂场景建模能力:需要将现实中的商品交易场景抽象为计算机可处理的数据结构和算法
- 贪心算法应用:题目通常要求找到最优交易策略,这往往涉及贪心算法的设计与实现
- STL容器熟练度:需要灵活运用map、vector等STL容器来处理交易数据
- 边界条件处理:商品交易涉及数量、价格等数值计算,需要特别注意整数溢出等边界情况
提示:GESP七级考试已经要求考生具备独立解决中等难度算法问题的能力,这类题目往往不是单一知识点的考察,而是多个知识点的综合应用。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 题目描述还原与需求分析
根据GESP考试的命题规律和"商品交易"这个主题,我们可以还原出题目的大致描述:
场景设定:
- 有N种商品参与交易,每种商品有初始库存量和单价
- 市场上有M个买家和卖家,每个交易者有自己的需求/供给量和报价
- 交易规则遵循"价格优先、时间优先"的原则
核心需求:
- 实现交易撮合系统,匹配买卖双方的订单
- 计算交易完成后的总成交金额
- 输出每种商品的最终库存情况
输入输出示例:
code复制输入:
商品种类数 N
每种商品的初始库存 (商品ID, 库存量, 单价)
交易者数量 M
每个交易者的订单 (买家/卖家标志, 商品ID, 数量, 报价)
输出:
总成交金额
每种商品的最终库存
2.1 数据结构设计
为了高效处理这个交易系统,我们需要设计合适的数据结构:
cpp复制struct Commodity {
int id;
int stock;
int basePrice;
};
struct Order {
bool isBuy; // true为买,false为卖
int commodityId;
int amount;
int price;
int timestamp; // 用于时间优先原则
};
// 全局变量
vector<Commodity> commodities;
vector<Order> orders;
2.2 交易规则实现
交易撮合的核心逻辑需要考虑价格优先和时间优先原则:
cpp复制void matchOrders() {
// 按商品ID分组订单
map<int, vector<Order>> buyOrders;
map<int, vector<Order>> sellOrders;
// 填充buyOrders和sellOrders...
// 对每种商品进行撮合
for(auto& commodity : commodities) {
auto& buys = buyOrders[commodity.id];
auto& sells = sellOrders[commodity.id];
// 排序:买家按价格降序,卖家按价格升序
sort(buys.begin(), buys.end(), [](const Order& a, const Order& b) {
return a.price > b.price || (a.price == b.price && a.timestamp < b.timestamp);
});
sort(sells.begin(), sells.end(), [](const Order& a, const Order& b) {
return a.price < b.price || (a.price == b.price && a.timestamp < b.timestamp);
});
// 撮合逻辑...
}
}
3. 核心算法实现与优化
3.1 贪心算法设计
商品交易问题的核心是贪心算法的应用 - 总是优先匹配最有利可图的交易:
- 对于每个商品,将买方订单按报价从高到低排序
- 将卖方订单按报价从低到高排序
- 从最高报价的买方和最低报价的卖方开始尝试匹配
cpp复制void greedyMatch(vector<Order>& buys, vector<Order>& sells, Commodity& commodity) {
int buyIdx = 0, sellIdx = 0;
long long total = 0; // 使用long long防止溢出
while(buyIdx < buys.size() && sellIdx < sells.size()) {
Order& buy = buys[buyIdx];
Order& sell = sells[sellIdx];
if(buy.price >= sell.price) {
int tradeAmount = min(buy.amount, sell.amount);
total += tradeAmount * sell.price; // 实际交易价格
buy.amount -= tradeAmount;
sell.amount -= tradeAmount;
commodity.stock += (sell.isBuy ? -tradeAmount : tradeAmount);
if(buy.amount == 0) buyIdx++;
if(sell.amount == 0) sellIdx++;
} else {
break; // 无法继续撮合
}
}
cout << "商品" << commodity.id << "成交金额:" << total << endl;
}
3.2 时间复杂度分析
假设有N种商品,每种商品平均有M个订单:
- 排序阶段:O(M log M) 对每种商品的买卖订单排序
- 撮合阶段:O(M) 线性扫描匹配
- 总体复杂度:O(N * M log M)
对于GESP七级考试来说,这个复杂度是完全可接受的,因为题目通常会限制N和M的范围。
4. 边界条件与常见错误
4.1 整数溢出问题
商品交易涉及大量数值计算,特别需要注意整数溢出:
cpp复制// 错误示例:使用int可能导致溢出
int total = amount * price;
// 正确做法:使用long long
long long total = (long long)amount * price;
4.2 浮点数精度问题
虽然本题使用整数价格,但如果题目涉及浮点数计算,需要注意:
提示:在金融计算中,通常使用整数表示最小货币单位(如分),避免浮点数精度问题。
4.3 库存不足处理
当卖方库存不足时,需要正确处理部分成交:
cpp复制// 在撮合逻辑中
int available = commodity.stock;
if(!sell.isBuy) { // 是卖单
available = sell.amount;
}
int tradeAmount = min(buy.amount, available);
4.4 测试用例设计
好的测试用例应该包括:
- 正常情况:多个买卖订单成功撮合
- 边界情况:只有一个买家或一个卖家
- 极端情况:报价完全不匹配无法成交
- 大数据量:测试程序效率
示例测试用例:
code复制输入:
2
1 100 50
2 200 30
4
0 1 50 55 // 卖商品1,50件,单价55
1 1 30 60 // 买商品1,30件,单价60
0 1 20 52 // 卖商品1,20件,单价52
1 1 40 58 // 买商品1,40件,单价58
预期输出:
商品1成交金额:30*52 + 10*55 = 2110
商品1剩余库存:100 - 30 - 10 = 60
5. 完整代码实现与解析
以下是完整的参考实现,包含详细注释:
cpp复制#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;
struct Commodity {
int id;
int stock;
int basePrice;
};
struct Order {
bool isBuy; // true为买,false为卖
int commodityId;
int amount;
int price;
int timestamp;
};
vector<Commodity> commodities;
vector<Order> orders;
void processTransactions() {
// 按商品ID分组
map<int, vector<Order>> buyMap, sellMap;
for(const auto& order : orders) {
if(order.isBuy) {
buyMap[order.commodityId].push_back(order);
} else {
sellMap[order.commodityId].push_back(order);
}
}
// 处理每种商品
for(auto& commodity : commodities) {
auto& buys = buyMap[commodity.id];
auto& sells = sellMap[commodity.id];
// 排序:买家降序,卖家升序
sort(buys.begin(), buys.end(), [](const Order& a, const Order& b) {
return a.price > b.price ||
(a.price == b.price && a.timestamp < b.timestamp);
});
sort(sells.begin(), sells.end(), [](const Order& a, const Order& b) {
return a.price < b.price ||
(a.price == b.price && a.timestamp < b.timestamp);
});
long long total = 0;
int buyIdx = 0, sellIdx = 0;
while(buyIdx < buys.size() && sellIdx < sells.size()) {
auto& buy = buys[buyIdx];
auto& sell = sells[sellIdx];
if(buy.price >= sell.price) {
int tradeAmount = min(buy.amount, sell.amount);
total += (long long)tradeAmount * sell.price;
buy.amount -= tradeAmount;
sell.amount -= tradeAmount;
if(buy.amount == 0) buyIdx++;
if(sell.amount == 0) sellIdx++;
} else {
break;
}
}
// 更新库存(简化处理,实际应根据成交情况)
for(const auto& sell : sells) {
if(!sell.isBuy) {
commodity.stock -= sell.amount;
}
}
cout << "商品" << commodity.id << "总成交金额:" << total << endl;
cout << "剩余库存:" << commodity.stock << endl;
}
}
int main() {
// 读取输入(实际考试中这部分可能已提供)
int N, M;
cin >> N;
commodities.resize(N);
for(int i = 0; i < N; ++i) {
cin >> commodities[i].id >> commodities[i].stock >> commodities[i].basePrice;
}
cin >> M;
orders.resize(M);
for(int i = 0; i < M; ++i) {
int type;
cin >> type >> orders[i].commodityId >> orders[i].amount >> orders[i].price;
orders[i].isBuy = (type == 1);
orders[i].timestamp = i;
}
processTransactions();
return 0;
}
6. 解题技巧与应试建议
- 理解题意优先:花2-3分钟彻底理解题目要求和交易规则,可以在草稿纸上画出简单的交易流程图
- 模块化编程:将问题分解为数据输入、订单排序、撮合逻辑、结果输出等独立模块
- 合理使用STL:熟练运用sort、map等STL组件可以大幅提高编码效率
- 边界测试:至少留出5分钟测试边界情况,如空订单、最大数量值等
- 代码风格:保持良好缩进和变量命名,方便检查逻辑错误
重要提示:GESP考试中,部分测试用例会故意设计导致整数溢出的情况,务必在涉及乘法的计算中使用long long类型。
7. 题目变体与扩展思考
在实际编程竞赛中,商品交易问题可能有多种变体:
- 多级价格优先:除了主要价格,还需要考虑次级价格因素
- 部分成交:允许订单部分成交,剩余部分继续参与撮合
- 交易费用:每笔交易收取固定或比例费用
- 限价订单:引入更复杂的订单类型,如限价单、市价单等
- 实时撮合:处理持续不断的新订单流
例如,考虑交易费用的变体题目:
cpp复制// 在撮合逻辑中加入交易费用计算
double feeRate = 0.001; // 0.1%的交易费
long long fee = (long long)(tradeAmount * sell.price * feeRate);
total += tradeAmount * sell.price - fee;
这类扩展题目在更高级别的编程竞赛中经常出现,七级考生可以尝试理解其解题思路,为后续学习做准备。
