|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
优点:简单易学、开发速度快、有很多年“历史”,能找到非常多别人做好的程序来用、配合activeX功能强大,很多php做不到的asp+activeX能做到,例如银行安全控件算法
在网站建立中,分类算法的使用十分的广泛。在计划一个电子商铺时,要触及到商品分类;在计划公布体系时,要触及到栏目大概频道分类;在计划软件下载如许的程序时,要触及到软件的分类;云云等等。能够说,分类是一个很广泛的成绩。
我经常口试一些程序员,并且我几近毫无破例地要问他们一些关于分类算法的成绩。上面的举几个我经常扣问的成绩。你以为你能够很轻松地回覆么^_^.
1、分类算法经常体现为树的暗示和遍历成绩。那末,叨教:假如用数据库中的一个Table来表达树型分类,应当有几个字段?
2、怎样疾速地从这个Table恢复出一棵树;
3、怎样判别某个分类是不是是另外一个分类的子类;
4、怎样查找某个分类的一切产物;
5、怎样天生分类地点的路径。
6、怎样新增分类;
在不限定分类的级数和每级分类的个数时,这些成绩并非能够轻松回覆的。本文试图办理这些成绩。
分类的数据布局
我们晓得:分类的数据布局实践上是一棵树。在《数据布局》课程中,人人大概学过Tree的算法。因为在网站建立中我们大批利用数据库,以是我们将从Tree在数据库中的存储谈起。
为简化成绩,我们假定每一个节点只必要保存Name这一个信息。我们必要为每一个节点编号。编号的办法有良多种。在数据库中经常使用的就是主动编号。这在Access、SQLServer、Oracle中都是如许。假定编号字段为ID。
为了暗示某个节点ID1是别的一个节点ID2的父节点,我们必要在数据库中再保存一个字段,申明这个分类是属于哪一个节点的儿子。把这个字段取名为FatherID。如这里的ID2,其FatherID就是ID1。
如许,我们就失掉了分类Catalog的数据表界说:
CreateTable[Catalog](
[ID][int]NOTNULL,
[Name][nvarchar](50)NOTNULL,
[FatherID][int]NOTNULL
);
商定:我们商定用-1作为最下面一层分类的父亲编码。编号为-1的分类。这是一个假造的分类。它在数据库中没有纪录。
怎样恢复出一棵树
下面的Catalog界说的最年夜上风,就在于用它能够轻松地恢复出一棵树-分类树。为了更分明地展现算法,我们先思索一个复杂的成绩:如何显现某个分类的下一级分类。我们晓得,要查询某个分类FID的下一级分类,SQL语句十分复杂:
selectNamefromcatalogwhereFatherID=FID
显现这些种别时,我们复杂地用<LI>来做到:
<%
REMoConn---数据库毗连,挪用GetChildren时已翻开
REMFID-----以后分类的编号
FunctionGetChildren(oConn,FID)
strSQL="selectID,NamefromcatalogwhereFatherID="&FID
setrsCatalog=oConn.Execute(strSQL)
%>
<UL>
<%
DowhilenotrsCatalog.Eof
%>
<LI><%=rsCatalog("Name")%>
<%
Loop
%>
</UL>
<%
rsCatalog.Close
EndFunction
%>
如今我们来看看怎样显现FID下的一切分类。这必要用到递回算法。我们只必要在GetChildren函数中复杂地对一切ID举行挪用:GetChildren(oConn,Catalog("ID"))就能够了。
<%
REMoConn---数据库毗连,已翻开
REMFID-----以后分类的编号
FunctionGetChildren(oConn,FID)
strSQL="selectNamefromcatalogwhereFatherID="&FID
setrsCatalog=oConn.Execute(strSQL)
%>
<UL>
<%
DowhilenotrsCatalog.Eof
%>
<LI><%=rsCatalog("Name")%>
<%=GetChildren(oConn,Catalog("ID"))%>
<%
Loop
%>
</UL>
<%
rsCatalog.Close
EndFunction
%>
修正后的GetChildren就能够完成显现FID分类的一切子分类的义务。要显现一切的分类,只必要云云挪用就能够了:
<%
REMstrConn--毗连数据库的字符串,请依据情形修正
setoConn=Server.CreateObject("ADODB.Connection")
oConn.OpenstrConn
=GetChildren(oConn,-1)
oConn.Close
%>
怎样查找某个分类的一切产物;
如今来办理我们在后面提出的第四个成绩。第三个成绩留作习题。我们假定产物的数据表以下界说:
CreateTableProduct(
[ID][int]NOTNULL,
[Name][nvchar]NOTNULL,
[FatherID][int]NOTNULL
);
个中,ID是产物的编号,Name是产物的称号,而FatherID是产物所属的分类。
对第四个成绩,很简单想到的举措是:先找到这个分类FID的一切子类,然后查询一切子类下的一切产物。完成这个算法实践上很庞大。代码大抵以下:
<%
FunctionGetAllID(oConn,FID)
DimstrTemp
IfFID=-1then
strTemp=""
else
strTemp=","
endif
strSQL="selectNamefromcatalogwhereFatherID="&FID
setrsCatalog=oConn.Execute(strSQL)
DowhilenotrsCatalog.Eof
strTemp=strTemp&rsCatalog("ID")&GetAllID(oConn,Catalog("ID"))REM递回挪用
Loop
rsCatalog.Close
GetAllID=strTemp
EndFunction
REMstrConn--毗连数据库的字符串,请依据情形修正
setoConn=Server.CreateObject("ADODB.Connection")
oConn.OpenstrConn
FID=Request.QueryString("FID")
strSQL="selecttop100*fromProductwhereFatherIDin("&GetAllID(oConn,FID)&")"
setrsProduct=oConn.Execute(strSQL)
%>
<UL><%
DowhilenotrsProduct.EOF
%>
<LI><%=rsProduct("Name")%>
<%
Loop
%>
</UL>
<%rsProduct.Close
oConn.Close
%>
这个算法有良多弱点。试枚举几个以下:
1、因为我们必要查询FID下的一切分类,当分类十分多时,算法将十分地不经济,并且,因为要机关一个很年夜的strSQL,试想假如有1000个分类,这个strSQL将很年夜,可否实行就是一个成绩。
2、我们晓得,在SQL中利用In子句的效力长短常低的。这个算法不成制止地要利用In子句,效力很低。
我发明80%以上的程序员宠爱如许的算法,并在良多体系中大批地利用。仔细的程序员会发明他们写出了很慢的程序,但苦于找不到缘故原由。他们重复地反省SQL的实行效力,进步呆板的层次,但效力的增添很少。
最基本的成绩就出在这个算法自己。算法定了,可以再优化的时机就未几了。我们上面来先容一种算法,效力将是下面算法的10倍以上。
分类编码算法
成绩就出在后面我们接纳了按次编码,这是一种最复杂的编码办法。人人晓得,复杂其实不意味着效力。实践上,编码迷信是程序员?的课程。上面,我们经由过程计划一种编码算法,使分类的编号ID中同时包括了其父类的信息。一个五级分类的例子以下:
此例中,用32(4+7+7+7+7)位整数来编码,个中,第一级分类有4位,能够表达16种分类。第二级到第五级分类分离有7位,能够表达128个子分类。
明显,假如我们失掉一个编码为1092787200的分类,我们就晓得:因为其编码为
01000001001000101001110000000000
以是它是第四级分类。其父类的二进制编码是01000001001000101000000000000000,十进制编号为1092780032。顺次我们还能够晓得,其父类的父类编码是01000001001000000000000000000000,其父类的父类的父类编码是01000000000000000000000000000000。(我是否是太罗嗦了J,但这一点很主要。再转头看看我们后面提到的第五个成绩。哈哈,这不就已失掉了分类1092787200地点的分类路径了吗?)。
如今我们在一样平常的情形上去会商种别编码成绩。设种别的条理为k,第i层的编码位数为Ni,那末总的编码位数为N(N1+N2+..+Nk)。我们就失掉任何一个种别的编码情势以下:
2^(N-(N1+N2+…+Ni))*j+父类编码
个中,i暗示第i层,j暗示以后层的第j个分类。
如许我们就把任何分类的编码分红了两个部分,个中一部分是它的层编码,一部分是它的父类编码。
由上面公式定一的k个编码我们称为特性码:(由于i能够取k个值,以是有k个)
2^N-2^(N-(N1+N2+…+Ni))
关于任何给定的种别ID,假如我们把ID和k个特性码"相与",失掉的非0编码,就是其一切父类的编码!
位编码算法
对任何按次编码的Catalog表,我们能够计划一个位编码算法,将一切的种别编码规格化为位编码。在详细完成时,我们先创立一个一时表:
CreateTempCatalog(
[OldID][int]NOTNULL,
[NewID][int]NOTNULL,
[OldFatherID][int]NOTNULL,
[NewFatherID][int]NOTNULL
);
在这个表中,我们保存一切本来的种别编号OldID和其父类编号OldFatherID,和从头盘算的满意位编码请求的响应编号NewID、NewFatherID。
程序以下:
<%
REMoConn---数据库毗连,已翻开
REMOldFather---本来的父类编号
REMNewFather---新的父类编号
REMN---编码总位数
REMNi--每级的编码位数数组
REMLevel--以后的级数
subFormatAllID(oConn,OldFather,NewFather,N,Nm,Nibyref,Level)
strSQL="selectCatalogID,FatherIDfromCatalogwhereFatherID="&OldFather
setrsCatalog=oConn.Execute(strSQL)
j=1
dowhilenotrsCatalog.EOF
i=2^(N-Nm)*j
ifLeveltheni=i+NewFather
OldCatalog=rsCatalog("CatalogID")
NewCatalog=i
REM写进一时表
strSQL="InsertintoTempCatalog(OldCatalogID,NewCatalogID,OldFatherID,NewFatherID)"
strSQL=strSQL&"values("&OldCatalog&","&NewCatalog&","&OldFather&","&NewFather&")"
Conn.ExecutestrSQL
REM递回挪用FormatAllID
Nm=Nm+Ni(Level+1)
FormatAllIDoConn,OldCatalog,NewCatalog,N,Nm,Ni,Level+1
rsCatalog.MoveNext
j=j+1
loop
rsCatalog.Close
endsub
%>
挪用这个算法的一个例子以下:
<%
REM界说编码参数,个中N为总位数,Ni为每级的位数。
DimN,Ni(5)
Ni(1)=4
N=Ni(1)
fori=2to5
Ni(i)=7
N=N+Ni(i)
next
REM翻开数据库,创立一时表
strSQL="CreateTempCatalog([OldID][int]NOTNULL,[NewID][int]NOTNULL,[OldFatherID][int]NOTNULL,[NewFatherID][int]NOTNULL);"
SetConn=Server.CreateObject("ADODB.Connection")
Conn.OpenApplication("strConn")
Conn.ExecutestrSQL
REM挪用规格化例程
FormatAllIDConn,-1,-1,N,Ni(1),Ni,0
REM------------------------------------------------------------------------
REM在此处更新一切相干表的种别编码为新的编码便可。
REM------------------------------------------------------------------------
REM封闭数据库
strSQL="droptableTempCatalog;"
Conn.ExecutestrSQL
Conn.Close
%>
第四个成绩
如今我们转头看看第四个成绩:如何失掉某个分类下的一切产物。因为接纳了位编码,如今成绩变得很复杂。我们很简单推算:某个产物属于某个种别的前提是Product.FatherID&(Catalog.ID的特性码)=Catalog.ID。个中"&"代表位与算法。这在SQLServer中是间接撑持的。
举例来讲:产物所属的种别为:1092787200,而以后种别为1092780032。以后种别对应的特性值为:4294950912,因为1092787200&4294950912=8537400,以是这个产物属于分类8537400。
我们后面已给出了盘算特性码的公式。特性码其实不多,并且很简单盘算,能够思索在Global.asa中Application_OnStart工夫触发时盘算出来,寄存在Application("Mark")数组中。
固然,有了特性码,我们还能够失掉加倍无效率的算法。我们晓得,固然我们接纳了位编码,实践上仍是一种按次编码的办法。体现出第I级的分类编码一定比第I+1级分类的编码要小。依据这个特性,我们还能够由FID失掉两个特性码,个中一个是本级位特性码FID0,一个是下级位特性码FID1。而产物属于某个分类FID的充实需要前提是:
Product.FatherID>FID0andProduct.FatherID<FID1
上面的程序显现分类FID下的一切产物。因为数据表Product已对FatherID举行索引,故查询速率极快:
<%
REMoConn---数据库毗连,已翻开
REMFID---以后分类
REMFIDMark---特性值数组,典范的情形下为Application("Mark")
REMk---数组元素个数,也是分类的级数
SubGetAllProduct(oConn,FID,FIDMarkbyref,k)
REM依据FID盘算出特性值FID0,FID1
fori=kto1
if(FIDandFIDMark=FID)thenexit
next
strSQL="selectNamefromProductwhereFatherID>"FIDMark(i)&"andFatherID<"FIDMark(i-1)
setrsProduct=oConn.Execute(strSQL)%>
<UL><%
DoWhileNotrsProduct.Eof%>
<LI><%=rsProduct("Name")
Loop%>
</UL><%
rsProduct.Close
EndSub
%>
对于中小型web应用来说,php有很强的竞争力,linux+apache+mysql+php(lamp)的组合几乎可以胜任绝大多数网站的解决方案,对于大型应用来讲,对于系统架构要求更高,需要有成熟的框架支持,jsp的struts是个不错的框架,国内介绍它的资料也非常多,应用逐渐广泛起来。asp就不用说了, |
|