zoukankan      html  css  js  c++  java
  • TopN算法与排行榜

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

  • 相关阅读:
    获取窗口句柄的几个办法
    我做的第一个网站
    学生信息管理系统
    程序题
    java冒泡排序
    模拟售票窗口,用4个线程模拟4个窗口在售票,共有8张票,用线程同步来实现
    设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1,写出程序。
    windows server 2012将计算机、回收站、文档等图标添加到桌面
    Javascript金额转化
    eclipse 每次切换工作空间都要重新配置
  • 原文地址:https://www.cnblogs.com/zhuweisky/p/1487563.html
Copyright © 2011-2022 走看看