算法分析: 从N条成绩单信息选择M位不重复同学发奖

最优数据选择算法
<?php
/**
 * author: selfimpr
 * blog: http://blog.youkuaiyun.com/lgg201
 * mail: lgg860911@yahoo.com.cn
 * copyright: 原创作品, 转载请附原始连接.
 *
 * 根据某种比较算法读取数据集中的最大N条数据, 并且, 数据有特征字段, 结果集中每条数据的特征字段必须唯一
 * 应用场景举例:
 * 		某班要发放单科成绩优秀奖, 共有N条单科成绩数据, 现要选取M名同学作为嘉奖对象, 但每人最多只能获奖一次.
 * 这里提供两种实现方式, 并加以比较其时间复杂度, 以便优选算法
 * 	算法1: 先对所有成绩排序, 然后从中选择
 * 	算法2: 不经过排序, 一遍扫描数据集选择数据
 * 时间复杂度比较结果:
 *  算法1时间消耗 : 算法2时间消耗 = log N : M
 */
define('WEIGHT',				'weight');
define('TIME',					'time');
define('UID',					'uid');
define('FEED_ID',				'feed_id');

define('FEED_ID_MIN',			10000);
define('WEIGHT_MIN',			20);
define('TIME_MIN',				1000);
define('UID_MIN',				1000);

#产生测试数据
function generate_datas() {
	#定义最大值
	if ( !defined(FEED_ID_MAX) ) {
		define('FEED_ID_MAX',			FEED_ID_MIN + FEED_ID_COUNT);
		define('WEIGHT_MAX',			WEIGHT_MIN + WEIGHT_COUNT);
		define('TIME_MAX',				TIME_MIN + TIME_COUNT);
		define('UID_MAX',				UID_MIN + UID_COUNT);
	}
	$feed_id	= FEED_ID_MIN;
	$datas		= array();
	while ( $feed_id ++ < FEED_ID_MAX ) {
		array_push($datas, array(
			FEED_ID		=> $feed_id, 
			TIME		=> rand(TIME_MIN, TIME_MAX), 
			UID			=> rand(UID_MIN, UID_MAX), 
			WEIGHT		=> rand(WEIGHT_MIN, WEIGHT_MAX), 
		));
	}
	return $datas;
}
#输出测试数据
function output_datas($datas, $fp) {
	foreach ( $datas as $data ) {
		fprintf($fp, "%d %d %d %d\n", $data[FEED_ID], $data[WEIGHT], $data[UID], $data[TIME]);
	}
}
#权重排序(先权重, 后时间)
function weight_sort($a, $b) {
	if ( $a[WEIGHT] != $b[WEIGHT] ) return $a[WEIGHT] - $b[WEIGHT];
	return $a[TIME] - $b[TIME];
}
#权重排序(先权重, 后时间), 倒序, 供先排序后选择算法使用
function weight_sort_reverse($a, $b) {
	if ( $b[WEIGHT] != $a[WEIGHT] ) return $b[WEIGHT] - $a[WEIGHT];
	return $b[TIME] - $a[TIME];
}
#权重选择(基于已排序数据集)
function weight_select($datas, $uniq_key, $count) {
	$result	= array();
	foreach ( $datas as $data ) {
		if ( array_key_exists($data[$uniq_key], $result) ) continue;
		$result[$data[$uniq_key]]	= $data;
		if ( -- $count <= 0 ) break;
	}
	return $result;
}

/**
 * compare_select_uniq_n 
 * 读取数据集$datas中的数据, 应用$compare比较函数, 选取$uniq_key数据项不重复的最大前$count条数据
 * 复杂度计算:
 * 	假设: 
 * 		1. 数据集$datas长度为N
 * 		2. 要获取的记录数$count为M
 * 	计算:
 * 		复杂度	= O( M * 2 ) + O( M * M * 1 ) + O( (N - M) * 1 ) + O( (N - M) * M * 2 ) + O( (N - M) * 1 )
 * 				= O( 2 * M * (M + 1) ) + O( 2 * (N - M) * (M + 1) )
 *				= O( 2 * (M + N - M) * (M + 1) )
 * 				= O( 2 * N * (M + 1) )
 * 				= O( N * M )	#忽略常数
 * @param mixed $datas 
 * @param mixed $uniq_key 
 * @param mixed $compare 
 * @param mixed $count 
 * @access public
 * @return void
 */
function compare_select_uniq_n($datas, $uniq_key, $compare, $count) {
	$tmp_datas	= array();			#结果集空间
	$tmp_length	= 0;				#结果集空间当前长度
	$index		= 0;				#遍历目标数据集$datas的下标
	$length		= count($datas);	#目标数据集$datas的长度
	#step 1: 构造初始的$count个临时数据
#复杂度: O(M * 2)
	while ( $count -- > 0 && $index < $length ) {
		$data	= $datas[$index];
		$index ++;

		#寻找唯一键相同的已有数据, 比较替换
#复杂度: O(M * M * 2)
		foreach ( $tmp_datas as $i => $ele ) {
			if ( $ele[$uniq_key] == $data[$uniq_key] ) {
				if ( $compare($data, $ele) > 0 ) 
					$tmp_datas[$i]	= $data;
				break;
			}
		}

		$tmp_datas[$count]	= $data;
	}
	#step2: 遍历剩余数据, 比较替换
#复杂度: O( (N - M) * 1 )
	while ( $index < $length ) {
		$data	= $datas[$index];
		$index ++;

		#寻找结果集中要替换的数据下标
		$cmp_i	= 0;
#复杂度: O( (N - M) * M * 2 )
		foreach ( $tmp_datas as $i => $ele ) {
			#优先选择唯一键相同的数据
			if ( $ele[$uniq_key] == $data[$uniq_key] ) {
				$cmp_i	= $i;
				break;
			#否则查找当前结果集中的最小记录
			} else if ( $compare($tmp_datas[$cmp_i], $ele) > 0 ) {
				$cmp_i	= $i;
			}
		}
		#检查要替换的目标数据, 如果符合替换条件则替换
#复杂度: O( (N - M) * 1 )
		if ( $compare($data, $tmp_datas[$cmp_i]) > 0 ) 
			$tmp_datas[$cmp_i]	= $data;
	}
	return $tmp_datas;
}
#基于排序然后选择的算法
function compare_select_uniq_n_sort($datas, $uniq_key, $compare, $count) {
	usort($datas, $compare);
	return weight_select($datas, $uniq_key, $count);
}
#测试入口封装
function test_wrap($datas, $uniq_key, $compare, $count, $func, $timefp, $outfp = NULL) {
	$begin	= microtime(true);
	$datas	= $func($datas, $uniq_key, $compare, $count);
	$end	= microtime(true);
	fprintf($timefp, "%s: %ss\n", strpos($func, 'sort') === FALSE ? '自建算法' : '排序算法', $end - $begin);
	if ( $outfp ) {
		#对结果数据排序以方便价差
		usort($datas, 'weight_sort');
		output_datas($datas, $outfp);
	}
}

define('FEED_ID_COUNT',			100000);		#产生的测试数据中FEED_ID个数
define('WEIGHT_COUNT',			100);		#产生的测试数据中的WEIGHT个数上限
define('TIME_COUNT',			5000);		#产生的测试数据中的TIME个数上限
define('UID_COUNT',				5000);		#产生的测试数据中的UID个数上限

#输出句柄
$stdout	= fopen('php://stdout', 'w');
$stderr	= fopen('php://stderr', 'w');

#产生测试数据
$datas	= generate_datas();

#输出排序的测试数据集供检查正确性
#$tmp_datas	= $datas;
#usort($tmp_datas, 'weight_sort');
#output_datas($tmp_datas, $stdout);

define('RETRIEVE_COUNT',		50);		#要取回的个数

test_wrap($datas, UID, 'weight_sort', RETRIEVE_COUNT, 'compare_select_uniq_n_sort', $stdout);
test_wrap($datas, UID, 'weight_sort_reverse', RETRIEVE_COUNT, 'compare_select_uniq_n', $stdout);
/*
 +------------------+--------------------------------------------------------------+
 |                                 测试结果                                        |
 +------------------+--------------------------------------------------------------+
 |     测试条件     |                           结果数据                           |
 +------------------+--------------------------------------------------------------+
 | N: 1000          | 排序算法: 0.030486106872559s                                 |
 | M: 50            | 自建算法: 0.18151593208313s                                  |
 +------------------+--------------------------------------------------------------+
 | N: 100000        | 排序算法: 6.3918650150299s                                   |
 | M: 50            | 自建算法: 19.431954860687s                                   |
 +------------------+--------------------------------------------------------------+
 | N: 1000          | 排序算法: 0.029729843139648s                                 |
 | M: 10            | 自建算法: 0.044672012329102s                                 |
 +------------------+--------------------------------------------------------------+
 | N: 100000        | 排序算法: 5.9424071311951s                                   |
 | M: 10            | 自建算法: 4.2767288684845s                                   |
 +------------------+--------------------------------------------------------------+
 |                               测试结果分析                                      |
 +------------------+--------------------------------------------------------------+
 | 1. 快速排序法时间复杂度为O( N * log N ), 权重选择时间忽略                       |
 | 2. 自建选择算法时间复杂度为O( N * M )                                           |
 | 根据以上两条, 期望为两种算法时间消耗比例为M : log N                             |
 | 1. 50 : log 1000 = 5 : 1, 测试结果符合期望                                      |
 | 2. 50 : log 100000 = 5 : 1.3, 测试结果符合期望                                   |
 | 3. 10 : log 1000 = 1 : 1, 测试结果略有偏差, 但整体数据量小, 此误差可接受        |
 | 4. 10 : log 100000 = 1 : 1.3, 测试结果符合期望                                   |
 +------------------+--------------------------------------------------------------+
 */


### 抽奖系统的后台实现 抽奖系统的核心在于中奖逻辑和发奖逻辑的设计实现。以下是基于提供的引用以及专业知识构建的一个完整的解决方案。 #### 中奖逻辑设计 中奖逻辑的关键是如何高效地判断用户是否能够获得某个奖项,同时减少查询耗时。如果奖品数量较多,则可以通过概率计算的方式替代传统的初始化奖池方法[^1]。这种方式无需提前加载所有奖品到内存中,而是通过实时的概率算法动态分配奖品给用户。 以下是一个简单的中奖逻辑代码示例: ```java import java.util.Random; public class LotteryService { private Random random = new Random(); /** * 判断用户是否中奖并返回对应的奖品ID。 * * @param awardRates 奖品及其对应概率列表 * @return 返回中奖的奖品ID或者null表示未中奖 */ public String drawAward(List<AwardRateInfo> awardRates) { double totalProbability = 0; for (AwardRateInfo info : awardRates) { totalProbability += info.getAwardRate().doubleValue(); } double randValue = random.nextDouble() * totalProbability; double cumulativeProb = 0; for (AwardRateInfo info : awardRates) { cumulativeProb += info.getAwardRate().doubleValue(); if (randValue <= cumulativeProb) { return info.getAwardId(); // 返回中奖奖品ID } } return null; // 如果没有匹配则返回null } } ``` 此代码实现了基于概率分布的随机抽选机制,其中 `awardRates` 是一个包含所有奖品及其概率的集合[^2]。 --- #### 发奖逻辑设计 发奖逻辑主要涉及如何将中奖结果记录下来,并通知前端展示给用户。通常情况下,发奖过程分为两步:一是更新数据库中的库存状态;二是向用户提供反馈信息。 下面是一段伪代码用于说明发奖流程: ```java @Service public class AwardDistributionService { @Autowired private AwardRepository awardRepository; /** * 处理发奖。 * * @param userId 用户唯一标识符 * @param rewardId 获得的奖励ID */ public void distributeAward(String userId, String rewardId) { Optional<DrawAwardInfo> optionalAward = awardRepository.findById(rewardId); if (!optionalAward.isPresent()) { throw new RuntimeException("Invalid Reward ID"); } DrawAwardInfo award = optionalAward.get(); // 更新库存(假设每次发放都会扣减) int updatedRows = awardRepository.decreaseStock(award.getId(), 1); if (updatedRows != 1) { throw new RuntimeException("Failed to update stock."); } // 记录日志或发送消息至队列处理后续业务 log.info("User {} has received the award {}", userId, award.getAwardName()); } } ``` 该部分代码展示了如何从数据库获取具体奖品详情,并完成库存扣除操作[^3]。 --- #### 综合考虑性能优化 针对大规模并发场景下的抽奖活动,建议引入缓存层来减轻数据库压力。例如 Redis 可作为临时存储介质保存热门奖品的状态变化数据。此外,在高频率调用接口的情况下,还可以采用异步任务框架(如 Spring Batch 或 RabbitMQ)分批执行复杂的事务性工作流。 --- ### 总结 以上方案涵盖了抽奖系统核心功能模块——即 **中奖判定** 和 **发奖管理** 的基本架构模式。实际开发过程中还需要充分考虑到安全性、扩展性和兼容性的需
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值