Bootstrap

java topn_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排序。

//IOrdered 参与排行榜排序的对象必须实现的接口。//参与排行榜排序的对象的类型publicinterfaceIOrdered{boolIsTopThan(TOrderedObj other);

}

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

接下来是TopNOrderedContainer实现的源码:

//TopNOrderedContainer 用于始终保持排行榜前N名的Object。该实现是线程安全的。///zhuweisky 2009.05.23//被排名的对象的标志类型///被排名的对象类型publicclassTopNOrderedContainerwhereTObj : IOrdered{privateTObj[] orderedArray=null;privateintvalidObjCount=0;privateSmartRWLockersmartRWLocker=newSmartRWLocker();#regionTopNumberprivateinttopNumber=10;publicintTopNumber

{get{returntopNumber; }set{ topNumber=value; }

}#endregion#regionCtorpublicTopNOrderedContainer() { }publicTopNOrderedContainer(int_topNumber)

{this.topNumber=_topNumber;

}#endregion#regionInitializepublicvoidInitialize()

{if(this.topNumber<1)

{thrownewException("The value of TopNumber must greater than 0");

}this.orderedArray=newTObj[this.topNumber];

}#endregion#regionAdd ListpublicvoidAdd(IListlist)

{if(list==null)

{return;

}using(this.smartRWLocker.Lock(AccessMode.Write))

{foreach(TObj objinlist)

{this.DoAdd(obj);

}

}

}#endregion#regionAddpublicvoidAdd(TObj obj)

{using(this.smartRWLocker.Lock(AccessMode.Write))

{this.DoAdd(obj);

}

}#endregion#regionGetTopNpublicTObj[] GetTopN()

{using(this.smartRWLocker.Lock(AccessMode.Read))

{return(TObj[])this.orderedArray.Clone();

}

}#endregion#regionPrivate#regionDoAddprivatevoidDoAdd(TObj obj)

{if(obj==null)

{return;

}if(this.validObjCount

{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#regionAdjust//Adjust 调整posIndex处的对象到合适的位置。///与相邻前一个对象比较,如果当前对象更加Top,则与前一个对象交换位置。///privatevoidAdjust(intposIndex)

{

TObj obj=this.orderedArray[posIndex];for(intindex=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排序直接实现为一个静态方法,如:

publicstaticTObj[] GetTopN(IListlist)whereTObj : IOrdered

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

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

publicclassUserData: IOrdered{#regionUserIDprivatestringuserID;publicstringUserID

{get{returnuserID; }set{ userID=value; }

}#endregion#regionScoreprivateintscore;publicintScore

{get{returnscore; }set{ score=value; }

}#endregionpublicUserData(string_userID,int_score)

{this.userID=_userID;this.score=_score;

}#regionIOrdered 成员publicboolIsTopThan(UserDataother)

{returnthis.Score>other.Score;

}publicoverridestringToString()

{returnthis.score.ToString();

}#endregion}

privatevoidbutton4_Click(objectsender, EventArgs e)

{

Listlist=newList();for(inti=0; i<500000; i++)

{

list.Add(newUserData("User"+i.ToString(), i*i*i-3*i*i+4*i+8));

}

Listlist2=newList();for(inti=0; i<500000; i++)

{

list2.Add(newUserData("User"+i.ToString(), i*i*i-3*i*i+4*i+8));

}

Stopwatchstopwatch=newStopwatch();

stopwatch.Start();

list.Sort(this);

stopwatch.Stop();longms1=stopwatch.ElapsedMilliseconds;

stopwatch.Reset();

stopwatch.Start();

TopNOrderedContainercontainer=newTopNOrderedContainer(20);

container.Initialize();

container.Add(list2);

UserData[] res=container.GetTopN();

stopwatch.Stop();longms2=stopwatch.ElapsedMilliseconds;

}#regionIComparer 成员publicintCompare(UserDatax, UserDatay)

{return(y.Score-x.Score);

}#endregion

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

转至:http://www.cnblogs.com/zhuweisky/archive/2009/05/23/1487563.html

;