当前位置: 首页 > news >正文

TopN算法与排行榜

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

    在系统中,我们经常会遇到这样的需求:将大量(比如几十万、甚至上百万)的对象进行排序,然后只需要取出最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排序。

复制代码

    /// <summary>
    /// IOrdered 参与排行榜排序的对象必须实现的接口。
    /// </summary>
    /// <typeparam name="TOrderedObj">参与排行榜排序的对象的类型</typeparam>
    public interface IOrdered<TOrderedObj>
    {
        bool IsTopThan(TOrderedObj other);
    }

复制代码

      之所以使用泛型参数TOrderedObj,是为了避免派生类在实现IsTopThan方法时,需要将参数other进行向下转换。

      接下来是TopNOrderedContainer实现的源码:

复制代码

    /// <summary>
    /// 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排序直接实现为一个静态方法,如:

      public static TObj[] GetTopN<TObj>(IList<TObj> list) where TObj : IOrdered<TObj>

      如果要是这样实现,那我们就没有办法继续动态的Add新的TObj对象进来,如果要达到这样的目的,就只有构造新的list,再次调用static GetTopN方法,如此会重复做一些工作。

      最后,我们来测试一下TopNOrderedContainer与List.Sort方法的性能比较,测试的对象数目为500000个,取出Top20。测试代码如下:  

复制代码

    public class UserData : IOrdered<UserData>
    {
        #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
    }

复制代码

 

复制代码

        private void button4_Click(object sender, EventArgs e)
        {
            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

转载于:https://my.oschina.net/u/1266221/blog/737954

相关文章:

  • Servlet 生命周期、工作原理
  • POJ 2375
  • 关于SQL镜像配置报错
  • 共享库
  • Oracle 函数返回表实例2种写法实例
  • 重走java--Step 2
  • 高并发系统之队列术
  • servlet 开发出错原因分析
  • java--集合框架
  • 粗浅看Struts2和Hibernate框架
  • Eclipse将引用了第三方jar包的Java项目打包成jar文件的两种方法
  • jquery内容选择器(匹配包含指定选择器的元素)
  • Unity3D Shader入门指南(转)
  • 文件上传大小限制问题
  • 百度地图路线检索(3)
  • 《Javascript高级程序设计 (第三版)》第五章 引用类型
  • 【译】React性能工程(下) -- 深入研究React性能调试
  • 10个最佳ES6特性 ES7与ES8的特性
  • Java 23种设计模式 之单例模式 7种实现方式
  • MySQL QA
  • node-glob通配符
  • Octave 入门
  • Redis 中的布隆过滤器
  • vue 配置sass、scss全局变量
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 爬虫模拟登陆 SegmentFault
  • 线性表及其算法(java实现)
  • 一些关于Rust在2019年的思考
  • Spark2.4.0源码分析之WorldCount 默认shuffling并行度为200(九) ...
  • !!【OpenCV学习】计算两幅图像的重叠区域
  • #【QT 5 调试软件后,发布相关:软件生成exe文件 + 文件打包】
  • #gStore-weekly | gStore最新版本1.0之三角形计数函数的使用
  • #Linux(帮助手册)
  • #QT(串口助手-界面)
  • #基础#使用Jupyter进行Notebook的转换 .ipynb文件导出为.md文件
  • #我与Java虚拟机的故事#连载15:完整阅读的第一本技术书籍
  • (02)Hive SQL编译成MapReduce任务的过程
  • (14)Hive调优——合并小文件
  • (附源码)spring boot建达集团公司平台 毕业设计 141538
  • (附源码)计算机毕业设计ssm基于Internet快递柜管理系统
  • (十一)手动添加用户和文件的特殊权限
  • (数位dp) 算法竞赛入门到进阶 书本题集
  • (新)网络工程师考点串讲与真题详解
  • *Algs4-1.5.25随机网格的倍率测试-(未读懂题)
  • .NET LINQ 通常分 Syntax Query 和Syntax Method
  • .Net 代码性能 - (1)
  • .NET 的静态构造函数是否线程安全?答案是肯定的!
  • .NET 自定义中间件 判断是否存在 AllowAnonymousAttribute 特性 来判断是否需要身份验证
  • .net操作Excel出错解决
  • .net访问oracle数据库性能问题
  • /proc/vmstat 详解
  • [C#7] 1.Tuples(元组)
  • [flask] flask的基本介绍、flask快速搭建项目并运行
  • [flume$2]记录一个写自定义Flume拦截器遇到的错误
  • [FT]chatglm2微调