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

测试的结果显示,使用List.Sort方法需要1287ms,而TopNOrderedContainer只花了78ms。

TopN算法与排行榜相关推荐

  1. python堆排序求topn_堆排序和topN算法

    堆排序和topN算法: topN算法,第一次调用topN,然后把海量数据一次和小顶堆第一个比较,如果>第一个元素,就交换,然后调用minHeapify方法排序一遍. 然后比较下一个数据. pub ...

  2. [Spark的TopN算法实现]

    一.TopN算法 MapReduce中的TopN算法是一个经典的算法,由于每个map都只是实现了本地的TopN算法,而假设map有M个,在归约的阶段只有M x N次运算,这个结果是可以接受的并不会造成 ...

  3. TopN算法实战 排序算法RangePartitioner解密

    1.基础TopN算法实战 2.分组TopN算法实战 3.排序算法RangePartitioner内幕解密 知识点: *只要是改变每一行列的数据,一般都是用Map操作 *RangePartitioner ...

  4. 算法高级(25)-分布式TopN算法玄机

    面试题:请问,某视频点播网站,希望实时展现热门视频,比如近8小时点击量最大的前100个视频.如果由你来开发这个功能,你怎么做? 一.思路分析 看到这样的一个问题,估计同学们会说,很简单呀,用Redis ...

  5. 如何在10亿个整数中找出前1000个最大的数(TopN算法)

    面试题目:如何在10亿个整数中找出前1000个最大的数. 我们知道排序算法有很多: 冒泡算法:通过两层for循环,外层第一次循环找到数组中最大的元素放置在倒数第一个位置,第二次循环找到第二大的元素放置 ...

  6. java 排名算法_排行榜的算法

    好久不来博客园了,前几天更新个人状态时,也把"技术博客"四个字改成了"荒废已久的博客". 好久不总结自己的工作和学习了,怎么说也过不去,就来这写一篇浅显的文章, ...

  7. css 边缘闪光_CSS_用CSS控制的闪烁效果,  一段文本或一张图片,它 - phpStudy...

    用CSS控制的闪烁效果 一段文本或一张图片,它的周围有一圈光晕,这圈光晕每一秒钟闪烁一次,而当鼠标移到上面时,立即停止闪烁,当鼠标移开时又继续闪烁.这种效果用于那些需要特别引起别人注意的内容上(如:警 ...

  8. 数据算法 --hadoop/spark数据处理技巧 --(二次排序问题和TopN问题)

    一.二次排序问题. MR/hadoop两种方案: 1.让reducer读取和缓存给个定键的所有值(例如,缓存到一个数组数据结构中,)然后对这些值完成一个reducer中排序.这种方法不具有可伸缩性,因 ...

  9. java mongo分组统计_mongodb 分组 topN

    [摘要] MongoDB 对于 TopN 功能的需求使用其 shell 脚本来实现有些复杂,而集算器 SPL 语言,则因其离散性.灵活性恰好能弥补 MongoDB 实现方面的不足.若想了解更多,请前往 ...

最新文章

  1. Java FAQ(6)
  2. Java客户端操作elasticsearch--创建索引(集群模式下)
  3. svn cleanup failed–previous operation has not finished; run cleanup if it was interrupted
  4. 澳大利亚 计算机 博士,澳大利亚迪肯大学招收计算机博士
  5. CALL SYMPUT与CALL SYMPUTX区别
  6. 使用ETags减少Web应用带宽和负载
  7. Python学习笔记(尚硅谷)
  8. 【动态规划】最大子段和问题,最大子矩阵和问题,最大m子段和问题
  9. 阶段3 2.Spring_04.Spring的常用注解_3 用于创建的Component注解
  10. 最简单的推荐系统实践
  11. Minitab正态能力分析算法资源合集
  12. 手机全屏html幻灯片,Jquery+css3,实现全屏撕裂幻灯片案例教程(zepto版本的 jquery.slitslider.js)【手机版】...
  13. 基于FPGA的CameraLink视频开发案例
  14. malloc与calloc的区别及实例
  15. java计算机毕业设计高校防疫物资管理系统MyBatis+系统+LW文档+源码+调试部署
  16. 在EXCEL电子表格中怎么把页面调成横向的
  17. 成语学习记录20180820-26
  18. Java中调整字距与行距的方法 其一(以DrawString为例)
  19. 科学计算机如何进行复数运算,教你如何用你的卡西欧学生计算器进行复数运算!...
  20. Kafka坑之一:Lag与HW、LEO

热门文章

  1. 程序员:我用代码给女朋友P图
  2. Nacos客户端配置
  3. Kafka消息的可靠性
  4. 服务降级-多版本支持
  5. MapStruct解决数据传输对象转换的繁琐
  6. 缓存-分布式锁-Redisson-读写锁补充
  7. HDFS的API操作-小文件的合并
  8. 私有属性和方法-子类对象不能直接访问
  9. Spring--SPeL
  10. python unitest框架_python单元测试框架Unitest