我在参与邮件系统的开发时,发现了一个非常有意思的问题,大家如果有时间的话,不妨听我讲讲,o(∩_∩)o...呵呵.
先描述一下问题吧.大家对我们开发的WebMail恐怕并不熟悉,所以我就简单的抽象出问题的题设:
------------------------------------------------------------------
邮件有两种,已读的和未读的,要求未读在邮件列表的前面,已读的放在列表的后面;未读的和已读的邮件中要分别按日期的降序排序(越新的邮件越考前),其中邮件信息在文件系统中,将从文件系统中读入的信息放在MailInfo javabean中,最
后要返回一个经过双排序规则排好序的列表List.
------------------------------------------------------------------
1.naive方法:
首先遍历一遍文件列表,生成javabean放入mailinfo_list列表;
然后按已读或者未读排序,得到一个经过已读和未读排序的列表;
最后再分割成两个,一个是未读列表,一个是已读列表,用自己拿手的排序算法(notice:而不是问题领域最需要的排序算法!)分别按日期降序排序.
其中,按是否已读排序可以这样做:两边分别设个指针向中间走,前面的一直向后走直到遇到第一个已读的邮件便停下来,尾部的指针一直向前走直到遇到第一个未读的停下来;然后交换,继续下一个循环直到两个指针相遇.
注意这里的日期是(MailInfo)mailinfo_list.get(i).getDate().
2.上面的方法太笨了,我们会笑话这种同学,搞不好按是否未读排序他也敢用个选择排序什么的.
那,来个入门级的:
改进一下naive方法:在读取文件信息并根据文件内容建立了一个MailInfo javabean后向mailinfo_list插入时,未读的头插,已读的尾插.哈哈,果然有用,这样建立了mailinfo_list后就已经是未读的靠前,已读的靠后放了.
然后在按日期排序时,使用传说中效率最高的折半插入算法(一般来说,基本有序的情况下折半查找的性能最好,你要用了散列表什么的另说).
哇,效率提高不少么(折半查找可是logN级别的,当数量大时明显好于线性搜索)!
3.有些同学可能觉得入门级的还是没什么可称道的,要来点特别的.
好,来个初级算法:
每生成一个新的MailInfo javabean后在插入到mailinfo_list时,便寻找适当位置:未读的在未读部分找,已读的在已读的部分找,保证未读部分的放前面,已读的部分放后面;
为表示未读部分,可以设置个当前未读邮件数newNum,那么在插入一个未读邮件时,在0~newNum 中折半查找插入位置,在读取一个已读邮件时,在newNum~mailinfo_list.size()中折半查找插入位置;
当邮件非常多时,只在插入时访问一次列表就找到了适当位置并插入,插入过程中不破坏列表的双有序性,这么做可以说基本可行的,而且是个联机算法.
4.噢,研究研究,我们要想个中级算法.
再思考一下:问题领域真的这么复杂么?
既然邮件信息文件都是线程在监听socket端口收到新邮件时生成的,那么名字肯定有特征,要保证文件名不会重复的话指不定已经把时间信息作为文件名的一部分了,这样可能邮件已经有序了呢!
我在实现了初级算法时,测试了一下,果然,哈哈邮件列表默认就是按时间排序的(当然要头插)!
好了,我们只关心未读和已读就好了,反正默认是按时间排序.
可是,问题领域真的这么简单么?
:文件可以从其他文件夹中移到当前文件夹,我们不能保证名字的日期性,新建立的文件并不一定是最新的;
:可能有多个mail服务器在监听不同的端口然后调用ftsclient写入文件系统的,文件名字有除时间外的其他信息;
:我们系统有一天要支持分布式文件存放了,本地文件的生成日期不具有了全局的统一性;
:我们要换服务器了...
要保证不同服务(WebMail,EBS,FTS,Xmail...)之间的松耦合性,这些就要考虑.
那,恩,按日期排序时还用折半查找么?
中级算法的答案是:不用,我们应该用类似插入排序的方法.
从第一个开始判断就可以了,既然默认是按时间降序排序,那么在绝大多数情况下,和第一个比较后发现比第一个靠前,直接在头部插入那么就已经是按日期降序排列了;在很少的情况下可能会从第一个开始依次判断下去,直到找到合适的位置.
这里的trick就是:设置一个当前未读邮件数newNum,未读的要在0~newNum 中依次判断插入位置,在读取一个已读邮件时,在newNum~mailinfo_list.size()中依次判断插入位置.
事实上.我们用基本上只进一次循环就找到合适插入位置来保证了文件在多个文件夹中自由转移的统一处理,保证文件名命名规则变换时仍能正常工作,保证基本不用遍历列表(只和第一个比)就完成了插入,维护了列表的双有序性.
这个故事告诉我们:不要相信那些看起来高效的算法,符合给定问题领域的方案才是好方案.
OK,谁能给个高级算法?
// 将邮件信息类实例添加到列表中,如果是未读的,那么放在前面,否则放在后面,按日期排序

if (msginfo_ll.size() == 0) ...{
msginfo_ll.add(mi);
if (!mi.IsRead())
newNum++;

} else ...{

if (mi.IsRead()) ...{// 已读
int j = newNum;

/** *//**
* 因为邮件基本有序,所以用类似插入法排序即可,节省时间
*
* @author M.Liang Liu
*/

while (j < msginfo_ll.size()) ...{
MailInfo newmi = (MailInfo) msginfo_ll.get(j);
if (DateHelper.dateCompare(newmi.GetDatetime(), mi
.GetDatetime()) < 0)
break;
j++;
}
msginfo_ll.add(j, mi);

} else ...{// 未读
int n = 0;

/** *//**
* 因为未读邮件较少,用户类似插入排序算法即可
*
* @author M.Liang Liu
*/

while (n < newNum) ...{
MailInfo newmi = (MailInfo) msginfo_ll.get(n);
if (DateHelper.dateCompare(newmi.GetDatetime(), mi
.GetDatetime()) < 0)
break;
n++;
}
msginfo_ll.add(n, mi);
newNum++;
}
}
}
// 设置标志位
fOpen = true;
return;
}
发表于 @ 2007年05月24日 14:23:00|评论(loading...)