TopN算法与排行榜
来源:互联网 发布:软件测试管理购买 编辑:程序博客网 时间:2024/04/27 23:35
在系统中,我们经常会遇到这样的需求:将大量(比如几十万、甚至上百万)的对象进行排序,然后只需要取出最Top的前N名作为排行榜的数据,这即是一个TopN算法。常见的解决方案有三种:
(1)直接使用List的Sort方法进行处理。
(2)使用排序二叉树进行排序,然后取出前N名。
(3)使用最大堆排序,然后取出前N名。
第一种方案的性能是最差的,后两种方案性能会好一些,但是还是不能满足我们的需求。最主要的原因在于使用二叉树和最大堆排序时,都是对所有的对象进行排序,而不是将代价花费在我们需要的少数的TopN上。为此,我自己实现了TopNOrderedContainer来解决这个问题。
思路是这样的,使用一个长度为N的数组,来存放最Top的N个对象,越Top的对象其在数组中的Index就越小。这样,每次加入一个对象时,就与Index最大的那个对象比较,如果比其更Top,则交换两个对象的位置。如果被交换的对象是数组中的最后一个对象(Index最大),则该对象会被抛弃。如此,可以保证容器中始终保持的都是最Top的N个对象。
接下来我们看具体的实现。
如果一个对象要参与TopN排行榜,则其必须实现IOrdered接口,表明其可以被Top排序。
/// IOrdered 参与排行榜排序的对象必须实现的接口。
/// </summary>
/// <typeparam name="TOrderedObj">参与排行榜排序的对象的类型</typeparam>
public interface IOrdered<TOrderedObj>
{
bool IsTopThan(TOrderedObj other);
}
之所以使用泛型参数TOrderedObj,是为了避免派生类在实现IsTopThan方法时,需要将参数other进行向下转换。
接下来是TopNOrderedContainer实现的源码:
/// TopNOrderedContainer 用于始终保持排行榜前N名的Object。该实现是线程安全的。
/// zhuweisky 2009.05.23
/// </summary>
/// <typeparam name="TID">被排名的对象的标志类型</typeparam>
/// <typeparam name="TObj">被排名的对象类型</typeparam>
public class TopNOrderedContainer<TObj> where TObj : IOrdered<TObj>
{
private TObj[] orderedArray = null;
private int validObjCount = 0;
private SmartRWLocker smartRWLocker = new SmartRWLocker();
#region TopNumber
private int topNumber = 10;
public int TopNumber
{
get { return topNumber; }
set { topNumber = value; }
}
#endregion
#region Ctor
public TopNOrderedContainer() { }
public TopNOrderedContainer(int _topNumber)
{
this.topNumber = _topNumber;
}
#endregion
#region Initialize
public void Initialize()
{
if (this.topNumber < 1)
{
throw new Exception("The value of TopNumber must greater than 0 ");
}
this.orderedArray = new TObj[this.topNumber];
}
#endregion
#region Add List
public void Add(IList<TObj> list)
{
if (list == null)
{
return;
}
using (this.smartRWLocker.Lock(AccessMode.Write))
{
foreach (TObj obj in list)
{
this.DoAdd(obj);
}
}
}
#endregion
#region Add
public void Add(TObj obj)
{
using (this.smartRWLocker.Lock(AccessMode.Write))
{
this.DoAdd(obj);
}
}
#endregion
#region GetTopN
public TObj[] GetTopN()
{
using (this.smartRWLocker.Lock(AccessMode.Read))
{
return (TObj[])this.orderedArray.Clone();
}
}
#endregion
#region Private
#region DoAdd
private void DoAdd(TObj obj)
{
if (obj == null)
{
return;
}
if (this.validObjCount < this.topNumber)
{
this.orderedArray[this.validObjCount] = obj;
this.Adjust(this.validObjCount);
++this.validObjCount;
return;
}
if (this.orderedArray[this.topNumber - 1].IsTopThan(obj))
{
return;
}
this.orderedArray[this.topNumber - 1] = obj;
this.Adjust(this.topNumber - 1);
}
#endregion
#region Adjust
/// <summary>
/// Adjust 调整posIndex处的对象到合适的位置。
/// 与相邻前一个对象比较,如果当前对象更加Top,则与前一个对象交换位置。
/// </summary>
private void Adjust(int posIndex)
{
TObj obj = this.orderedArray[posIndex];
for (int index = posIndex; index > 0; index--)
{
if (obj.IsTopThan(this.orderedArray[index - 1]))
{
TObj temp = this.orderedArray[index - 1];
this.orderedArray[index - 1] = obj;
this.orderedArray[index] = temp;
}
else
{
break;
}
}
}
#endregion
#endregion
}
源码面前毫无秘密。
但是有几点我还是需要说明一下:
(1)ESBasic.ObjectManagement.TopNOrderedContainer位于我的ESBasic.dll类库中,其实现时用到的SmartRWLocker是一个读写锁,也是ESBasic.dll类库中的一员。你可以从这里下载ESBasic.dll直接试用。
(2)为何不将TopN排序直接实现为一个静态方法,如:
如果要是这样实现,那我们就没有办法继续动态的Add新的TObj对象进来,如果要达到这样的目的,就只有构造新的list,再次调用static GetTopN方法,如此会重复做一些工作。
最后,我们来测试一下TopNOrderedContainer与List.Sort方法的性能比较,测试的对象数目为500000个,取出Top20。测试代码如下:
{
#region UserID
private string userID;
public string UserID
{
get { return userID; }
set { userID = value; }
}
#endregion
#region Score
private int score;
public int Score
{
get { return score; }
set { score = value; }
}
#endregion
public UserData(string _userID, int _score)
{
this.userID = _userID;
this.score = _score;
}
#region IOrdered<string> 成员
public bool IsTopThan(UserData other)
{
return this.Score > other.Score;
}
public override string ToString()
{
return this.score.ToString();
}
#endregion
}
{
List<UserData> list = new List<UserData>();
for (int i = 0; i < 500000; i++)
{
list.Add(new UserData("User" + i.ToString(), i * i * i - 3 * i * i + 4 * i + 8));
}
List<UserData> list2 = new List<UserData>();
for (int i = 0; i < 500000; i++)
{
list2.Add(new UserData("User" + i.ToString(), i * i * i - 3 * i * i + 4 * i + 8));
}
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
list.Sort(this);
stopwatch.Stop();
long ms1 = stopwatch.ElapsedMilliseconds;
stopwatch.Reset();
stopwatch.Start();
TopNOrderedContainer<UserData> container = new TopNOrderedContainer<UserData>(20);
container.Initialize();
container.Add(list2);
UserData[] res = container.GetTopN();
stopwatch.Stop();
long ms2 = stopwatch.ElapsedMilliseconds;
}
#region IComparer<UserData> 成员
public int Compare(UserData x, UserData y)
{
return (y.Score - x.Score);
}
#endregion
测试的结果显示,使用List.Sort方法需要1287ms,而TopNOrderedContainer只花了78ms。
- TopN算法与排行榜
- topN 算法
- TopN算法
- 算法 topN
- TopN算法
- TopN算法
- 热门文章排行榜topn推荐实例
- scala 实现topN算法
- scala 实现topN算法
- golang之TopN算法
- 数据算法-hadoop3 TopN
- 传智播客学习之topN算法
- 算法(2) TopN Mapreduce/Spark
- TopN算法实战 排序算法RangePartitioner解密
- 热度TopN排名算法的设计
- Hash表算法及TopN问题
- 大数据求TopN的优化算法
- TOPN 子句与SET ROWCOUNTN 之对比
- 在应用程序之间传递动态程序集
- 关于【对象“***.rem”已经断开连接或不在服务器上】异常的解决方法
- DataRabbit 企业级数据访问框架(21)-- DataRabbit 4.0 & DataRabbit 与三层架构融合Demo源码
- DCFramework 动态分布式计算框架(01)-- 基础结构
- DCFramework 动态分布式计算框架(00) -- 序
- TopN算法与排行榜
- 我的架构经验小结(四)-- 实战中演化的三层架构
- DataRabbit 轻量的数据访问框架(20)-- 实时同步的实体缓存 SyncEntityCache
- DataRabbit 轻量的数据访问框架(19)-- 读写分离与隔离级别
- 使用动态代理记录方法执行的时间
- 宽容与忍耐
- XNA基础(03) —— 动画与帧率
- XNA基础(02) —— 绘制基础
- XNA基础(01) —— 游戏循环