<?php
/**
* author: selfimpr
* blog: http://blog.csdn.net/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, 测试结果符合期望 |
+------------------+--------------------------------------------------------------+
*/
算法分析: 从N条成绩单信息选择M位不重复同学发奖
最新推荐文章于 2022-12-15 12:12:47 发布