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

<?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, 测试结果符合期望                                   |
 +------------------+--------------------------------------------------------------+
 */


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值